From 66e411833f157d1f4f9912663201b3c2beffee0f Mon Sep 17 00:00:00 2001 From: "lukasz.zazulak" Date: Fri, 8 Aug 2025 13:34:08 +0200 Subject: [PATCH 1/6] feat(ls): migrate disallowing equivalent paths in OAS2 --- packages/apidom-ls/src/config/codes.ts | 1 + .../lint/equivalent-paths-not-allowed.ts | 17 +++++++++++ .../openapi/path-template/lint/index.ts | 3 +- .../services/validation/linter-functions.ts | 20 +++++++++++++ .../oas/equivalent-paths-not-allowed.yaml | 28 +++++++++++++++++ packages/apidom-ls/test/validate.ts | 30 +++++++++++++++++++ 6 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 0ce246d7c3..3e90472cc9 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -667,6 +667,7 @@ enum ApilintCodes { OPENAPI2_PATH_TEMPLATE = 3040000, OPENAPI2_PATH_TEMPLATE_VALUE_WELL_FORMED = 3040100, OPENAPI2_PATH_TEMPLATE_VALUE_VALID, + OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, OPENAPI2_LICENSE = 3050000, OPENAPI2_LICENSE_FIELD_NAME_TYPE = 3050100, diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts new file mode 100644 index 0000000000..0ff6291fdc --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts @@ -0,0 +1,17 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import ApilintCodes from '../../../codes.ts'; +import { LinterMeta } from '../../../../apidom-language-types.ts'; +import { OpenAPI2 } from '../../target-specs.ts'; + +const equivalentPathsNotAllowedLint: LinterMeta = { + code: ApilintCodes.OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, + source: 'apilint', + message: 'Equivalent paths are not allowed.', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintNoEquivalentPaths', + marker: 'value', + targetSpecs: [...OpenAPI2], +}; + +export default equivalentPathsNotAllowedLint; diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts index 9150ac9932..a01b26e81a 100644 --- a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts @@ -1,6 +1,7 @@ import valueWellFormedLint from './value--well-formed.ts'; import valueValidLint from './value--valid.ts'; +import equivalentPathsNotAllowedLint from './equivalent-paths-not-allowed.ts'; -const lints = [valueWellFormedLint, valueValidLint]; +const lints = [valueWellFormedLint, valueValidLint, equivalentPathsNotAllowedLint]; export default lints; diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index 196d248c27..90329cf81f 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -1133,4 +1133,24 @@ export const standardLinterfunctions: FunctionItem[] = [ return true; }, }, + { + functionName: 'apilintNoEquivalentPaths', + function: (element: Element): boolean => { + const PATH_TEMPLATES_REGEX = /\{[^}]+\}/g; + const isFirstOccurrence = (currentKey: string, allKeys: unknown[]) => { + const normalize = (x: string) => x.replace(PATH_TEMPLATES_REGEX, '~~'); + const currentKeyNormalized = normalize(currentKey); + const firstIndex = allKeys.findIndex( + (e) => typeof e === 'string' && normalize(e) === currentKeyNormalized, + ); + + return allKeys[firstIndex] === currentKey; + }; + const paths = element.parent.parent; + + return isStringElement(element) && isObject(paths) + ? isFirstOccurrence(element.toValue(), paths.keys()) + : true; + }, + }, ]; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml b/packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml new file mode 100644 index 0000000000..412b7523ef --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml @@ -0,0 +1,28 @@ +swagger: '2.0' +info: + title: Test API + version: 1.0.0 +paths: + /items/{id}: + get: + summary: Get item by ID + parameters: + - name: id + in: path + required: true + type: string + responses: + 200: + description: OK + /items/{itemId}: + get: + summary: Get item by itemId + parameters: + - name: itemId + in: path + required: true + type: string + responses: + 200: + description: OK + diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index fb4564857a..f2fe379499 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3788,4 +3788,34 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + + it('oas 2.0 should not allow equivalent paths', async function () { + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'equivalent-paths-not-allowed.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'equivalent-paths-not-allowed.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc); + const expected: Diagnostic[] = [ + { + message: 'Equivalent paths are not allowed.', + severity: 1, + code: 3040102, + source: 'apilint', + range: { start: { line: 16, character: 2 }, end: { line: 16, character: 17 } }, + }, + ]; + assert.deepEqual(result, expected); + + languageService.terminate(); + }); }); From bfc17f84ae02ac9bea92456bb509add5df15de54 Mon Sep 17 00:00:00 2001 From: "lukasz.zazulak" Date: Fri, 8 Aug 2025 14:15:36 +0200 Subject: [PATCH 2/6] feat(ls): migrate disallowing equivalent paths in OAS2 --- .../src/config/openapi/path-template/lint/index.ts | 4 ++-- ...aths-not-allowed.ts => value--equivalent-not-allowed.ts} | 6 +++--- .../apidom-ls/src/services/validation/linter-functions.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename packages/apidom-ls/src/config/openapi/path-template/lint/{equivalent-paths-not-allowed.ts => value--equivalent-not-allowed.ts} (74%) diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts index a01b26e81a..a641aa5173 100644 --- a/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/index.ts @@ -1,7 +1,7 @@ import valueWellFormedLint from './value--well-formed.ts'; import valueValidLint from './value--valid.ts'; -import equivalentPathsNotAllowedLint from './equivalent-paths-not-allowed.ts'; +import valueEquivalentNotAllowedLint from './value--equivalent-not-allowed.ts'; -const lints = [valueWellFormedLint, valueValidLint, equivalentPathsNotAllowedLint]; +const lints = [valueWellFormedLint, valueValidLint, valueEquivalentNotAllowedLint]; export default lints; diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts similarity index 74% rename from packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts rename to packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts index 0ff6291fdc..d0ad919d30 100644 --- a/packages/apidom-ls/src/config/openapi/path-template/lint/equivalent-paths-not-allowed.ts +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts @@ -4,14 +4,14 @@ import ApilintCodes from '../../../codes.ts'; import { LinterMeta } from '../../../../apidom-language-types.ts'; import { OpenAPI2 } from '../../target-specs.ts'; -const equivalentPathsNotAllowedLint: LinterMeta = { +const valueEquivalentNotAllowedLint: LinterMeta = { code: ApilintCodes.OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, source: 'apilint', message: 'Equivalent paths are not allowed.', severity: DiagnosticSeverity.Error, - linterFunction: 'apilintNoEquivalentPaths', + linterFunction: 'apilintOpenAPIPathTemplateNoEquivalent', marker: 'value', targetSpecs: [...OpenAPI2], }; -export default equivalentPathsNotAllowedLint; +export default valueEquivalentNotAllowedLint; diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index 90329cf81f..3d84bd3138 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -1134,7 +1134,7 @@ export const standardLinterfunctions: FunctionItem[] = [ }, }, { - functionName: 'apilintNoEquivalentPaths', + functionName: 'apilintOpenAPIPathTemplateNoEquivalent', function: (element: Element): boolean => { const PATH_TEMPLATES_REGEX = /\{[^}]+\}/g; const isFirstOccurrence = (currentKey: string, allKeys: unknown[]) => { From b0851d2a8c86a9ceb8208d0280272e09de673459 Mon Sep 17 00:00:00 2001 From: "lukasz.zazulak" Date: Fri, 8 Aug 2025 14:33:50 +0200 Subject: [PATCH 3/6] feat(ls): migrate disallowing equivalent paths in OAS2 --- ...not-allowed.yaml => path-template-equivalent-not-allowed.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/apidom-ls/test/fixtures/validation/oas/{equivalent-paths-not-allowed.yaml => path-template-equivalent-not-allowed.yaml} (100%) diff --git a/packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml b/packages/apidom-ls/test/fixtures/validation/oas/path-template-equivalent-not-allowed.yaml similarity index 100% rename from packages/apidom-ls/test/fixtures/validation/oas/equivalent-paths-not-allowed.yaml rename to packages/apidom-ls/test/fixtures/validation/oas/path-template-equivalent-not-allowed.yaml From 41d1439ebe5d15d63aef55604d70ab5391db42d4 Mon Sep 17 00:00:00 2001 From: "lukasz.zazulak" Date: Fri, 8 Aug 2025 14:35:04 +0200 Subject: [PATCH 4/6] feat(ls): migrate disallowing equivalent paths in OAS2 --- packages/apidom-ls/test/validate.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index f2fe379499..b04e058182 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3792,11 +3792,17 @@ describe('apidom-ls-validate', function () { it('oas 2.0 should not allow equivalent paths', async function () { const spec = fs .readFileSync( - path.join(__dirname, 'fixtures', 'validation', 'oas', 'equivalent-paths-not-allowed.yaml'), + path.join( + __dirname, + 'fixtures', + 'validation', + 'oas', + 'path-template-equivalent-not-allowed.yaml', + ), ) .toString(); const doc: TextDocument = TextDocument.create( - 'equivalent-paths-not-allowed.yaml', + 'path-template-equivalent-not-allowed.yaml', 'yaml', 0, spec, From e7b9a6d9aadd498e3236f620609a54e322c868c9 Mon Sep 17 00:00:00 2001 From: lukaszzazulak <155975313+lukaszzazulak@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:25:07 +0200 Subject: [PATCH 5/6] feat(ls): Improve syntax Co-authored-by: robert-hebel-sb --- .../path-template/lint/value--equivalent-not-allowed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts index d0ad919d30..ed8731c0f3 100644 --- a/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts +++ b/packages/apidom-ls/src/config/openapi/path-template/lint/value--equivalent-not-allowed.ts @@ -7,11 +7,11 @@ import { OpenAPI2 } from '../../target-specs.ts'; const valueEquivalentNotAllowedLint: LinterMeta = { code: ApilintCodes.OPENAPI2_PATH_TEMPLATE_EQUIVALENT_NOT_ALLOWED, source: 'apilint', - message: 'Equivalent paths are not allowed.', + message: 'Equivalent paths are not allowed', severity: DiagnosticSeverity.Error, linterFunction: 'apilintOpenAPIPathTemplateNoEquivalent', marker: 'value', - targetSpecs: [...OpenAPI2], + targetSpecs: OpenAPI2, }; export default valueEquivalentNotAllowedLint; From 4f55542b908dac2acc7131e831d2d092a3e60d26 Mon Sep 17 00:00:00 2001 From: "lukasz.zazulak" Date: Wed, 13 Aug 2025 21:53:24 +0200 Subject: [PATCH 6/6] feat(ls): migrate scopes not resolved OAS2 --- packages/apidom-ls/test/validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index b04e058182..7c05df810c 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3813,7 +3813,7 @@ describe('apidom-ls-validate', function () { const result = await languageService.doValidation(doc); const expected: Diagnostic[] = [ { - message: 'Equivalent paths are not allowed.', + message: 'Equivalent paths are not allowed', severity: 1, code: 3040102, source: 'apilint',