From 4576ad9070d7d2224f750ebedf45bffe1b564a3d Mon Sep 17 00:00:00 2001 From: ShikhaSaboo Date: Thu, 14 Aug 2025 16:05:41 +0530 Subject: [PATCH 1/3] feat: add validation ruls for tags to have unique name --- packages/apidom-ls/src/config/codes.ts | 1 + .../src/config/openapi/tag/documentation.ts | 2 + .../src/config/openapi/tag/lint/index.ts | 2 + .../config/openapi/tag/lint/name--unique.ts | 22 +++++++++++ .../services/validation/linter-functions.ts | 24 ++++++++++++ .../validation/oas/tags-unique-name.yaml | 18 +++++++++ packages/apidom-ls/test/validate.ts | 38 +++++++++++++++++++ 7 files changed, 107 insertions(+) create mode 100644 packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/tags-unique-name.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 0ce246d7c3..59859df6de 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -791,6 +791,7 @@ enum ApilintCodes { OPENAPI2_TAG_FIELD_NAME_REQUIRED, OPENAPI2_TAG_FIELD_DESCRIPTION_TYPE = 3190200, OPENAPI2_TAG_FIELD_EXTERNAL_DOCS_TYPE = 3190300, + OPENAPI2_TAG_FIELD_UNIQUE_NAME_VALUE_TYPE = 3190400, OPENAPI2_XML = 3200000, OPENAPI2_XML_FIELD_NAME_TYPE = 3200100, diff --git a/packages/apidom-ls/src/config/openapi/tag/documentation.ts b/packages/apidom-ls/src/config/openapi/tag/documentation.ts index 8ac06eccf5..d463222c3d 100644 --- a/packages/apidom-ls/src/config/openapi/tag/documentation.ts +++ b/packages/apidom-ls/src/config/openapi/tag/documentation.ts @@ -13,6 +13,8 @@ import { OpenAPI2, OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs.ts'; const documentation = [ { target: 'name', docs: '**Required.** The name of the tag.', targetSpecs: OpenAPI2 }, { target: 'name', docs: '**REQUIRED**. The name of the tag.', targetSpecs: OpenAPI3 }, + { target: 'name', docs: 'Tag Objects must have unique `name` field values.', targetSpecs: OpenAPI3 }, + { target: 'name', docs: 'Tag Objects must have unique `name` field values.', targetSpecs: OpenAPI2 }, { target: 'description', docs: 'A short description for the tag. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.', diff --git a/packages/apidom-ls/src/config/openapi/tag/lint/index.ts b/packages/apidom-ls/src/config/openapi/tag/lint/index.ts index 50124ce603..c7d8a06f6b 100644 --- a/packages/apidom-ls/src/config/openapi/tag/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/tag/lint/index.ts @@ -3,11 +3,13 @@ import nameTypeLint from './name--type.ts'; import nameRequiredLint from './name--required.ts'; import descriptionTypeLint from './description--type.ts'; import externalDocsTypeLint from './external-docs--type.ts'; +import nameUniqueLint from './name--unique.ts'; const lints = [ allowedFieldsLint, nameTypeLint, nameRequiredLint, + nameUniqueLint, descriptionTypeLint, externalDocsTypeLint, ]; diff --git a/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts b/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts new file mode 100644 index 0000000000..38c778d2a6 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts @@ -0,0 +1,22 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { OpenAPI2, OpenAPI3 } from '../../target-specs.ts'; + +const nameUniqueLint: LinterMeta = { + code: ApilintCodes.OPENAPI2_TAG_FIELD_UNIQUE_NAME_VALUE_TYPE, + source: 'apilint', + message: "Tag Objects must have unique `name` field values.", + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintPropertyUniqueSiblingValue', + linterParams: ['tags', 'name'], + marker: 'value', + target: 'name', + markerTarget: 'name', + data: {}, + targetSpecs: [...OpenAPI2, ...OpenAPI3], +}; + +export default nameUniqueLint; + diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index 196d248c27..9171020607 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -795,6 +795,30 @@ export const standardLinterfunctions: FunctionItem[] = [ return true; }, }, + { + functionName: 'apilintPropertyUniqueSiblingValue', + function: (element, elementOrClasses, key) => { + const value = toValue(element); + + const filterSiblingsOAS2 = ( + el: Element & { key?: { content?: string }; content: { value?: string } }, + ) => isString(el) && el.key?.content === key && toValue(el.content.value) === value; + + const filterSiblingsOAS3 = (el: Element) => + isObject(el) && el.hasKey(key) && toValue(el.get(key)) === value; + + const elements = filter((el: Element) => { + const classes: string[] = toValue(el.getMetaProperty('classes', [])); + + return ( + (elementOrClasses.includes(el.element) || + classes.every((v) => elementOrClasses.includes(v))) && + (filterSiblingsOAS2(el) || filterSiblingsOAS3(el)) + ); + }, element.parent?.parent?.parent); + return elements.length <= 1; + }, + }, { functionName: 'apilintChannelParameterExist', function: (element: Element): boolean => { diff --git a/packages/apidom-ls/test/fixtures/validation/oas/tags-unique-name.yaml b/packages/apidom-ls/test/fixtures/validation/oas/tags-unique-name.yaml new file mode 100644 index 0000000000..00203b8a16 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/tags-unique-name.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.0 +info: + title: test + version: 1.0.12 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: pet + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: \ No newline at end of file diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index fb4564857a..b29a6a1173 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3788,4 +3788,42 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + + it('oas - tags name should have unique values', async function () { + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'tags-unique-name.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/tags-unique-name.yaml', + 'yaml', + 0, + spec, + ); + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc); + const expected: Diagnostic[] = [ + { + message: 'Tag Objects must have unique `name` field values.', + severity: 1, + code: 3190400, + source: 'apilint', + data: {}, + range: { start: { line: 5, character: 10 }, end: { line: 5, character: 13 } }, + }, + { + message: 'Tag Objects must have unique `name` field values.', + severity: 1, + code: 3190400, + source: 'apilint', + data: {}, + range: { start: { line: 10, character: 10 }, end: { line: 10, character: 13 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }) }); From a8fb3f2c14117cb01a699390081c68245a1b29d5 Mon Sep 17 00:00:00 2001 From: ShikhaSaboo Date: Thu, 14 Aug 2025 17:17:35 +0530 Subject: [PATCH 2/3] chore: fix lint issues --- .../src/config/openapi/tag/documentation.ts | 12 ++++++++++-- .../config/openapi/tag/lint/name--unique.ts | 19 +++++++++---------- packages/apidom-ls/test/validate.ts | 8 +++----- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/apidom-ls/src/config/openapi/tag/documentation.ts b/packages/apidom-ls/src/config/openapi/tag/documentation.ts index d463222c3d..163a9e5d02 100644 --- a/packages/apidom-ls/src/config/openapi/tag/documentation.ts +++ b/packages/apidom-ls/src/config/openapi/tag/documentation.ts @@ -13,8 +13,16 @@ import { OpenAPI2, OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs.ts'; const documentation = [ { target: 'name', docs: '**Required.** The name of the tag.', targetSpecs: OpenAPI2 }, { target: 'name', docs: '**REQUIRED**. The name of the tag.', targetSpecs: OpenAPI3 }, - { target: 'name', docs: 'Tag Objects must have unique `name` field values.', targetSpecs: OpenAPI3 }, - { target: 'name', docs: 'Tag Objects must have unique `name` field values.', targetSpecs: OpenAPI2 }, + { + target: 'name', + docs: 'Tag Objects must have unique `name` field values.', + targetSpecs: OpenAPI3, + }, + { + target: 'name', + docs: 'Tag Objects must have unique `name` field values.', + targetSpecs: OpenAPI2, + }, { target: 'description', docs: 'A short description for the tag. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.', diff --git a/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts b/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts index 38c778d2a6..4ee41dae1c 100644 --- a/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts +++ b/packages/apidom-ls/src/config/openapi/tag/lint/name--unique.ts @@ -5,18 +5,17 @@ import { LinterMeta } from '../../../../apidom-language-types.ts'; import { OpenAPI2, OpenAPI3 } from '../../target-specs.ts'; const nameUniqueLint: LinterMeta = { - code: ApilintCodes.OPENAPI2_TAG_FIELD_UNIQUE_NAME_VALUE_TYPE, - source: 'apilint', - message: "Tag Objects must have unique `name` field values.", - severity: DiagnosticSeverity.Error, - linterFunction: 'apilintPropertyUniqueSiblingValue', - linterParams: ['tags', 'name'], - marker: 'value', + code: ApilintCodes.OPENAPI2_TAG_FIELD_UNIQUE_NAME_VALUE_TYPE, + source: 'apilint', + message: 'Tag Objects must have unique `name` field values.', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintPropertyUniqueSiblingValue', + linterParams: ['tags', 'name'], + marker: 'value', target: 'name', markerTarget: 'name', - data: {}, - targetSpecs: [...OpenAPI2, ...OpenAPI3], + data: {}, + targetSpecs: [...OpenAPI2, ...OpenAPI3], }; export default nameUniqueLint; - diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index b29a6a1173..863cea03cf 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3791,9 +3791,7 @@ describe('apidom-ls-validate', function () { it('oas - tags name should have unique values', async function () { const spec = fs - .readFileSync( - path.join(__dirname, 'fixtures', 'validation', 'oas', 'tags-unique-name.yaml'), - ) + .readFileSync(path.join(__dirname, 'fixtures', 'validation', 'oas', 'tags-unique-name.yaml')) .toString(); const doc: TextDocument = TextDocument.create( 'foo://bar/tags-unique-name.yaml', @@ -3804,7 +3802,7 @@ describe('apidom-ls-validate', function () { const languageService: LanguageService = getLanguageService(contextNoSchema); const result = await languageService.doValidation(doc); - const expected: Diagnostic[] = [ + const expected: Diagnostic[] = [ { message: 'Tag Objects must have unique `name` field values.', severity: 1, @@ -3825,5 +3823,5 @@ describe('apidom-ls-validate', function () { assert.deepEqual(result, expected); languageService.terminate(); - }) + }); }); From 4d28741fe73f64b99cb04b2a5ead6a2da66e702f Mon Sep 17 00:00:00 2001 From: ShikhaSaboo Date: Wed, 20 Aug 2025 11:45:15 +0530 Subject: [PATCH 3/3] chore: remove unwanted documentation for tag name --- .../apidom-ls/src/config/openapi/tag/documentation.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/apidom-ls/src/config/openapi/tag/documentation.ts b/packages/apidom-ls/src/config/openapi/tag/documentation.ts index 163a9e5d02..8ac06eccf5 100644 --- a/packages/apidom-ls/src/config/openapi/tag/documentation.ts +++ b/packages/apidom-ls/src/config/openapi/tag/documentation.ts @@ -13,16 +13,6 @@ import { OpenAPI2, OpenAPI30, OpenAPI31, OpenAPI3 } from '../target-specs.ts'; const documentation = [ { target: 'name', docs: '**Required.** The name of the tag.', targetSpecs: OpenAPI2 }, { target: 'name', docs: '**REQUIRED**. The name of the tag.', targetSpecs: OpenAPI3 }, - { - target: 'name', - docs: 'Tag Objects must have unique `name` field values.', - targetSpecs: OpenAPI3, - }, - { - target: 'name', - docs: 'Tag Objects must have unique `name` field values.', - targetSpecs: OpenAPI2, - }, { target: 'description', docs: 'A short description for the tag. [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) can be used for rich text representation.',