From 76a3eb54d7b72a93a59b058a8d594a0d4e89da09 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Tue, 5 Aug 2025 11:50:31 +0200 Subject: [PATCH 1/9] feat: migrate unique parameter name --- packages/apidom-ls/src/config/codes.ts | 1 + .../config/openapi/parameter/lint/index.ts | 2 + .../openapi/parameter/lint/unique-name.ts | 21 +++++++ .../oas/parameter-unique-name-3-0.yaml | 19 ++++++ packages/apidom-ls/test/validate.ts | 63 +++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 0ce246d7c3..1a854017cc 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -740,6 +740,7 @@ enum ApilintCodes { OPENAPI2_PARAMETER_FIELD_ENUM_TYPE = 3101800, OPENAPI2_PARAMETER_FIELD_MULTIPLE_OF_TYPE = 3101900, OPENAPI2_PARAMETER_FIELD_IN_PATH_TEMPLATE = 3102000, + OPENAPI2_PARAMETER_FIELD_NAME_UNIQUE = 3103000, OPENAPI2_ITEMS = 3110000, OPENAPI2_ITEMS_FIELD_TYPE_EQUALS = 3110100, diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts index 6e2bfd1f76..cdc231a134 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts @@ -42,6 +42,7 @@ import uniqueItemsTypeLint from './unique-items--type.ts'; import enumTypeLint from './enum--type.ts'; import multipleOfTypeLint from './multiple-of--type.ts'; import inPathTemplateLint from './in-path-template.ts'; +import uniqueNameLint from './unique-name.ts'; const lints = [ nameTypeLint, @@ -81,6 +82,7 @@ const lints = [ maxLengthTypeLint, minLengthTypeLint, uniqueItemsTypeLint, + uniqueNameLint, enumTypeLint, multipleOfTypeLint, requiredFields3_0__3_1Lint, diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts new file mode 100644 index 0000000000..ba812e9cf1 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts @@ -0,0 +1,21 @@ +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 parametersTypeLint: LinterMeta = { + code: ApilintCodes.OPENAPI2_PARAMETER_FIELD_NAME_UNIQUE, + source: 'apilint', + message: 'Name must be unique among all parameters', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintPropertyUniqueValue', + linterParams: [['parameters'], 'name'], + marker: 'key', + markerTarget: 'name', + target: 'name', + data: {}, + targetSpecs: [...OpenAPI2, ...OpenAPI3], +}; + +export default parametersTypeLint; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml new file mode 100644 index 0000000000..5bcb126e6d --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-3-0.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: 'test' +paths: + /pets: + get: + responses: + parameters: + - name: pathLevel + in: query + description: tags to filter by + schema: + type: string + - name: pathLevel + in: query + description: tags to filter by + schema: + type: string diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index fb4564857a..f0a23a5655 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3651,6 +3651,69 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 3.0 / yaml - parameter name should be unique', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'parameter-unique-name-3-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-unique-name-3-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 3103000, + data: {}, + message: 'Name must be unique among all parameters', + range: { + end: { + character: 14, + line: 9, + }, + start: { + character: 10, + line: 9, + }, + }, + severity: 1, + source: 'apilint', + }, + { + code: 3103000, + data: {}, + message: 'Name must be unique among all parameters', + range: { + end: { + character: 14, + line: 14, + }, + start: { + character: 10, + line: 14, + }, + }, + severity: 1, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.1 / yaml - parameter object should be defined within path template', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From 2da94c796bd9569991c41642431e48c8243d18d8 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Tue, 5 Aug 2025 16:32:04 +0200 Subject: [PATCH 2/9] feat: migrate unique parameter name --- .../openapi/parameter/lint/unique-name.ts | 2 +- .../services/validation/linter-functions.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts index ba812e9cf1..ce5c2c355d 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts @@ -9,7 +9,7 @@ const parametersTypeLint: LinterMeta = { source: 'apilint', message: 'Name must be unique among all parameters', severity: DiagnosticSeverity.Error, - linterFunction: 'apilintPropertyUniqueValue', + linterFunction: 'apilintPropertyUniqueSiblingValue', linterParams: [['parameters'], 'name'], marker: 'key', markerTarget: 'name', diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index 196d248c27..1bb32165e9 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -795,6 +795,24 @@ export const standardLinterfunctions: FunctionItem[] = [ return true; }, }, + { + functionName: 'apilintPropertyUniqueSiblingValue', + function: (element, elementOrClasses, key) => { + const value = toValue(element); + const elements = filter((el) => { + const classes: string[] = toValue(el.getMetaProperty('classes', [])); + + return ( + (elementOrClasses.includes(el.element) || + classes.every((v) => elementOrClasses.includes(v))) && + isObject(el) && + el.hasKey(key) && + toValue(el.get(key)) === value + ); + }, element.parent?.parent?.parent); + return elements.length <= 1; + }, + }, { functionName: 'apilintChannelParameterExist', function: (element: Element): boolean => { From 555703bfb4a4d29efcae3fdee5562994734d7f06 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 09:23:55 +0200 Subject: [PATCH 3/9] feat: migrate parameter named "Authorization" is ignored rule --- packages/apidom-ls/src/config/codes.ts | 1 + .../parameter/lint/in--authorization.ts | 31 +++++++++++ .../config/openapi/parameter/lint/index.ts | 2 + .../oas/parameter-in-authorization-3-0.yaml | 15 ++++++ packages/apidom-ls/test/validate.ts | 53 +++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 packages/apidom-ls/src/config/openapi/parameter/lint/in--authorization.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-in-authorization-3-0.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 1a854017cc..6a144fdf62 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -977,6 +977,7 @@ enum ApilintCodes { OPENAPI3_0_PARAMETER_FIELD_IN_TYPE = 5150300, OPENAPI3_0_PARAMETER_FIELD_IN_REQUIRED, OPENAPI3_0_PARAMETER_FIELD_IN_EQUALS, + OPENAPI3_0_PARAMETER_FIELD_IN_AUTHORIZATION, OPENAPI3_0_PARAMETER_FIELD_DESCRIPTION_TYPE = 5150400, OPENAPI3_0_PARAMETER_FIELD_REQUIRED_TYPE = 5150500, OPENAPI3_0_PARAMETER_FIELD_REQUIRED_REQUIRED, diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--authorization.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--authorization.ts new file mode 100644 index 0000000000..80a1ce274a --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--authorization.ts @@ -0,0 +1,31 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import { OpenAPI3 } from '../../target-specs.ts'; +import ApilintCodes from '../../../codes.ts'; + +const inAuthorizationLint = { + code: ApilintCodes.OPENAPI3_0_PARAMETER_FIELD_IN_AUTHORIZATION, + source: 'apilint', + message: + 'Header Parameter named "Authorization" is ignored. Use the "securitySchemes" and "security" sections instead to define authorization', + severity: DiagnosticSeverity.Warning, + linterParams: ['^(?!\\b(A|a)uthorization\\b).*$'], + linterFunction: 'apilintValueRegex', + marker: 'value', + target: 'name', + data: {}, + conditions: [ + { + targets: [ + { + path: 'in', + }, + ], + function: 'apilintContainsValue', + params: ['header'], + }, + ], + targetSpecs: OpenAPI3, +}; + +export default inAuthorizationLint; diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts index cdc231a134..6f69689ac0 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts @@ -43,12 +43,14 @@ import enumTypeLint from './enum--type.ts'; import multipleOfTypeLint from './multiple-of--type.ts'; import inPathTemplateLint from './in-path-template.ts'; import uniqueNameLint from './unique-name.ts'; +import inAuthorizationLint from './in--authorization.ts'; const lints = [ nameTypeLint, nameRequiredLint, inEquals2_0Lint, inEquals3_0__3_1Lint, + inAuthorizationLint, inRequiredLint, inValidLint, descriptionTypeLint, diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-authorization-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-authorization-3-0.yaml new file mode 100644 index 0000000000..8084abb481 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-authorization-3-0.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: 'test' +paths: + /: + get: + responses: + default: + description: 'test' + parameters: + - in: header + name: Authorization + content: + application/xml: {} diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index f0a23a5655..351895530d 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3714,6 +3714,59 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 3.0 / yaml - parameter Authorization is ignored', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join( + __dirname, + 'fixtures', + 'validation', + 'oas', + 'parameter-in-authorization-3-0.yaml', + ), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-in-authorization-3-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 5150303, + data: {}, + message: + 'Header Parameter named "Authorization" is ignored. Use the "securitySchemes" and "security" sections instead to define authorization', + range: { + end: { + character: 29, + line: 12, + }, + start: { + character: 16, + line: 12, + }, + }, + severity: 2, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.1 / yaml - parameter object should be defined within path template', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From 46f0f7a3039def5aeb1fa0f1e56c4c20b4ab56d7 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 09:48:03 +0200 Subject: [PATCH 4/9] feat: migrate parameter named "content-type" is ignored rule --- packages/apidom-ls/src/config/codes.ts | 1 + .../parameter/lint/in--content-type.ts | 31 ++++++++++++ .../config/openapi/parameter/lint/index.ts | 2 + .../oas/parameter-in-content-type-3-0.yaml | 15 ++++++ packages/apidom-ls/test/validate.ts | 47 +++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-in-content-type-3-0.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index 6a144fdf62..e096cf5321 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -978,6 +978,7 @@ enum ApilintCodes { OPENAPI3_0_PARAMETER_FIELD_IN_REQUIRED, OPENAPI3_0_PARAMETER_FIELD_IN_EQUALS, OPENAPI3_0_PARAMETER_FIELD_IN_AUTHORIZATION, + OPENAPI3_0_PARAMETER_FIELD_IN_CONTENT_TYPE, OPENAPI3_0_PARAMETER_FIELD_DESCRIPTION_TYPE = 5150400, OPENAPI3_0_PARAMETER_FIELD_REQUIRED_TYPE = 5150500, OPENAPI3_0_PARAMETER_FIELD_REQUIRED_REQUIRED, diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts new file mode 100644 index 0000000000..f793275ae6 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts @@ -0,0 +1,31 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import { OpenAPI3 } from '../../target-specs.ts'; +import ApilintCodes from '../../../codes.ts'; + +const inAuthorizationLint = { + code: ApilintCodes.OPENAPI3_0_PARAMETER_FIELD_IN_CONTENT_TYPE, + source: 'apilint', + message: + 'Header Parameter named "Content-Type" is ignored. The values for the "Content-Type" header are defined by `requestBody.content.`', + severity: DiagnosticSeverity.Warning, + linterParams: ['^(?!\\b(C|c)ontent-(T|t)ype\\b).*$'], + linterFunction: 'apilintValueRegex', + marker: 'value', + target: 'name', + data: {}, + conditions: [ + { + targets: [ + { + path: 'in', + }, + ], + function: 'apilintContainsValue', + params: ['header'], + }, + ], + targetSpecs: OpenAPI3, +}; + +export default inAuthorizationLint; diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts index 6f69689ac0..0480651d58 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts @@ -44,6 +44,7 @@ import multipleOfTypeLint from './multiple-of--type.ts'; import inPathTemplateLint from './in-path-template.ts'; import uniqueNameLint from './unique-name.ts'; import inAuthorizationLint from './in--authorization.ts'; +import inContentTypeLint from './in--content-type.ts'; const lints = [ nameTypeLint, @@ -92,6 +93,7 @@ const lints = [ allowedFields3_0Lint, allowedFields3_1Lint, inPathTemplateLint, + inContentTypeLint, ]; export default lints; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-content-type-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-content-type-3-0.yaml new file mode 100644 index 0000000000..605172396d --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-content-type-3-0.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: 'test' +paths: + /: + get: + responses: + default: + description: 'test' + parameters: + - in: header + name: content-type + content: + application/xml: {} diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index 351895530d..6b30750ea9 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3767,6 +3767,53 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 3.0 / yaml - parameter content-type is ignored', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'parameter-in-content-type-3-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-in-authorization-3-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 5150304, + data: {}, + message: + 'Header Parameter named "Content-Type" is ignored. The values for the "Content-Type" header are defined by `requestBody.content.`', + range: { + end: { + character: 28, + line: 12, + }, + start: { + character: 16, + line: 12, + }, + }, + severity: 2, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.1 / yaml - parameter object should be defined within path template', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From 028ab129327507bd5fe2e4ba4c0ca2524ac28323 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 09:54:52 +0200 Subject: [PATCH 5/9] feat: migrate parameter named "accept" is ignored rule --- .../openapi/parameter/lint/in--accept.ts | 31 ++++++++++++ .../parameter/lint/in--content-type.ts | 4 +- .../config/openapi/parameter/lint/index.ts | 2 + .../oas/parameter-in-accept-3-0.yaml | 15 ++++++ packages/apidom-ls/test/validate.ts | 47 +++++++++++++++++++ 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 packages/apidom-ls/src/config/openapi/parameter/lint/in--accept.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-in-accept-3-0.yaml diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--accept.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--accept.ts new file mode 100644 index 0000000000..f435f9eccd --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--accept.ts @@ -0,0 +1,31 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import { OpenAPI3 } from '../../target-specs.ts'; +import ApilintCodes from '../../../codes.ts'; + +const inAcceptLint = { + code: ApilintCodes.OPENAPI3_0_PARAMETER_FIELD_IN_CONTENT_TYPE, + source: 'apilint', + message: + 'Header Parameter named "Accept" is ignored. The values for the "Accept" header are defined by `requestBody.content.`', + severity: DiagnosticSeverity.Warning, + linterParams: ['^(?!\\b(A|a)ccept\\b).*$'], + linterFunction: 'apilintValueRegex', + marker: 'value', + target: 'name', + data: {}, + conditions: [ + { + targets: [ + { + path: 'in', + }, + ], + function: 'apilintContainsValue', + params: ['header'], + }, + ], + targetSpecs: OpenAPI3, +}; + +export default inAcceptLint; diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts index f793275ae6..d4db11f932 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--content-type.ts @@ -3,7 +3,7 @@ import { DiagnosticSeverity } from 'vscode-languageserver-types'; import { OpenAPI3 } from '../../target-specs.ts'; import ApilintCodes from '../../../codes.ts'; -const inAuthorizationLint = { +const inContentTypeLint = { code: ApilintCodes.OPENAPI3_0_PARAMETER_FIELD_IN_CONTENT_TYPE, source: 'apilint', message: @@ -28,4 +28,4 @@ const inAuthorizationLint = { targetSpecs: OpenAPI3, }; -export default inAuthorizationLint; +export default inContentTypeLint; diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts index 0480651d58..184b6cc639 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts @@ -45,6 +45,7 @@ import inPathTemplateLint from './in-path-template.ts'; import uniqueNameLint from './unique-name.ts'; import inAuthorizationLint from './in--authorization.ts'; import inContentTypeLint from './in--content-type.ts'; +import inAcceptLint from './in--accept.ts'; const lints = [ nameTypeLint, @@ -94,6 +95,7 @@ const lints = [ allowedFields3_1Lint, inPathTemplateLint, inContentTypeLint, + inAcceptLint, ]; export default lints; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-accept-3-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-accept-3-0.yaml new file mode 100644 index 0000000000..09a9d1b42c --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-accept-3-0.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: 'test' +paths: + /: + get: + responses: + default: + description: 'test' + parameters: + - in: header + name: accept + content: + application/xml: {} diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index 6b30750ea9..d105ef75a1 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3814,6 +3814,53 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 3.0 / yaml - parameter accept is ignored', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'parameter-in-accept-3-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-in-accept-3-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 5150304, + data: {}, + message: + 'Header Parameter named "Accept" is ignored. The values for the "Accept" header are defined by `requestBody.content.`', + range: { + end: { + character: 22, + line: 12, + }, + start: { + character: 16, + line: 12, + }, + }, + severity: 2, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.1 / yaml - parameter object should be defined within path template', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From f2c2bbdf8a56156bcedf5c8fe607fe0b2253c1d2 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 12:40:16 +0200 Subject: [PATCH 6/9] feat: migrate multiple body rule --- packages/apidom-ls/src/config/codes.ts | 1 + .../parameter/lint/in--multiple-body.ts | 27 ++++++++ .../config/openapi/parameter/lint/index.ts | 2 + .../services/validation/linter-functions.ts | 13 +++- .../oas/parameter-in-multiple-body.yaml | 19 ++++++ packages/apidom-ls/test/validate.ts | 63 +++++++++++++++++++ 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-in-multiple-body.yaml diff --git a/packages/apidom-ls/src/config/codes.ts b/packages/apidom-ls/src/config/codes.ts index e096cf5321..fbaad4db32 100644 --- a/packages/apidom-ls/src/config/codes.ts +++ b/packages/apidom-ls/src/config/codes.ts @@ -717,6 +717,7 @@ enum ApilintCodes { OPENAPI2_PARAMETER_FIELD_IN_EQUALS = 3100200, OPENAPI2_PARAMETER_FIELD_IN_REQUIRED, OPENAPI2_PARAMETER_FIELD_IN_VALID, + OPENAPI2_PARAMETER_FIELD_IN_MULTIPLE_BODY, OPENAPI2_PARAMETER_FIELD_DESCRIPTION_TYPE = 3100300, OPENAPI2_PARAMETER_FIELD_REQUIRED_TYPE = 3100400, OPENAPI2_PARAMETER_FIELD_REQUIRED_REQUIRED, diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts new file mode 100644 index 0000000000..aa43ed7f09 --- /dev/null +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts @@ -0,0 +1,27 @@ +import { DiagnosticSeverity } from 'vscode-languageserver-types'; + +import { OpenAPI2 } from '../../target-specs.ts'; +import ApilintCodes from '../../../codes.ts'; + +const inMultipleBody = { + code: ApilintCodes.OPENAPI2_PARAMETER_FIELD_IN_MULTIPLE_BODY, + source: 'apilint', + message: 'Multiple body parameters are not allowed', + severity: DiagnosticSeverity.Error, + linterFunction: 'apilintPropertyUniqueSiblingValue', + linterParams: [['parameters'], 'in'], + marker: 'key', + markerTarget: 'in', + target: 'in', + conditions: [ + { + targets: [{ path: 'in' }], + function: 'apilintContainsValue', + params: ['body'], + }, + ], + data: {}, + targetSpecs: OpenAPI2, +}; + +export default inMultipleBody; diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts index 184b6cc639..1ef0023df1 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/index.ts @@ -46,6 +46,7 @@ import uniqueNameLint from './unique-name.ts'; import inAuthorizationLint from './in--authorization.ts'; import inContentTypeLint from './in--content-type.ts'; import inAcceptLint from './in--accept.ts'; +import inMultipleBody from './in--multiple-body.ts'; const lints = [ nameTypeLint, @@ -96,6 +97,7 @@ const lints = [ inPathTemplateLint, inContentTypeLint, inAcceptLint, + inMultipleBody, ]; 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 1bb32165e9..a6b55fdb94 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -799,15 +799,22 @@ export const standardLinterfunctions: FunctionItem[] = [ functionName: 'apilintPropertyUniqueSiblingValue', function: (element, elementOrClasses, key) => { const value = toValue(element); + + const filterSiblingsOAS2 = (el: Element) => + isString(el) && + (el.parent.key as { content?: string })?.content === key && + el.content === value; + + const filterSiblingsOAS3 = (el: Element) => + isObject(el) && el.hasKey(key) && toValue(el.get(key)) === value; + const elements = filter((el) => { const classes: string[] = toValue(el.getMetaProperty('classes', [])); return ( (elementOrClasses.includes(el.element) || classes.every((v) => elementOrClasses.includes(v))) && - isObject(el) && - el.hasKey(key) && - toValue(el.get(key)) === value + (filterSiblingsOAS2(el) || filterSiblingsOAS3(el)) ); }, element.parent?.parent?.parent); return elements.length <= 1; diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-multiple-body.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-multiple-body.yaml new file mode 100644 index 0000000000..78f366d0b9 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-in-multiple-body.yaml @@ -0,0 +1,19 @@ +swagger: '2.0' +info: + title: 'test' + version: 1.0.0 +paths: + /: + get: + responses: + default: + description: 'test' + consumes: + - multipart/form-data + parameters: + - in: body + name: Else1 + schema: {} + - in: body + name: Else2 + schema: {} diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index d105ef75a1..eb606a9816 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3861,6 +3861,69 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 2.0 / yaml - Multiple body parameters are not allowed', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'parameter-in-multiple-body.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-in-multiple-body.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 3100203, + data: {}, + message: 'Multiple body parameters are not allowed', + range: { + end: { + character: 12, + line: 13, + }, + start: { + character: 10, + line: 13, + }, + }, + severity: 1, + source: 'apilint', + }, + { + code: 3100203, + data: {}, + message: 'Multiple body parameters are not allowed', + range: { + end: { + character: 12, + line: 16, + }, + start: { + character: 10, + line: 16, + }, + }, + severity: 1, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.1 / yaml - parameter object should be defined within path template', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From 21fe90c224684dc697d0a6cab5f25f86d61b8bf5 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 13:14:15 +0200 Subject: [PATCH 7/9] fix: adjust multiple body rule --- .../src/config/openapi/parameter/lint/in--multiple-body.ts | 2 +- .../apidom-ls/src/services/validation/linter-functions.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts index aa43ed7f09..80ac42001c 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/in--multiple-body.ts @@ -9,7 +9,7 @@ const inMultipleBody = { message: 'Multiple body parameters are not allowed', severity: DiagnosticSeverity.Error, linterFunction: 'apilintPropertyUniqueSiblingValue', - linterParams: [['parameters'], 'in'], + linterParams: ['parameters', 'in'], marker: 'key', markerTarget: 'in', target: 'in', diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index a6b55fdb94..b6ccce0cb6 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -800,10 +800,9 @@ export const standardLinterfunctions: FunctionItem[] = [ function: (element, elementOrClasses, key) => { const value = toValue(element); - const filterSiblingsOAS2 = (el: Element) => - isString(el) && - (el.parent.key as { content?: string })?.content === key && - el.content === value; + 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; From 1eb1a38fb8cc75797d44cd4d2d7eab3c2a33fc54 Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Wed, 6 Aug 2025 15:11:55 +0200 Subject: [PATCH 8/9] fix: parameter unique name validation for oas2 --- .../openapi/parameter/lint/unique-name.ts | 2 +- .../oas/parameter-unique-name-2-0.yaml | 21 +++++++ packages/apidom-ls/test/validate.ts | 63 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml diff --git a/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts index ce5c2c355d..15de868919 100644 --- a/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts +++ b/packages/apidom-ls/src/config/openapi/parameter/lint/unique-name.ts @@ -10,7 +10,7 @@ const parametersTypeLint: LinterMeta = { message: 'Name must be unique among all parameters', severity: DiagnosticSeverity.Error, linterFunction: 'apilintPropertyUniqueSiblingValue', - linterParams: [['parameters'], 'name'], + linterParams: ['parameters', 'name'], marker: 'key', markerTarget: 'name', target: 'name', diff --git a/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml new file mode 100644 index 0000000000..51fcd46379 --- /dev/null +++ b/packages/apidom-ls/test/fixtures/validation/oas/parameter-unique-name-2-0.yaml @@ -0,0 +1,21 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: 'test' +paths: + /pets: + get: + responses: + parameters: + - name: pathLevel + type: string + in: query + description: tags to filter by + schema: + type: string + - name: pathLevel + type: string + in: query + description: tags to filter by + schema: + type: string diff --git a/packages/apidom-ls/test/validate.ts b/packages/apidom-ls/test/validate.ts index eb606a9816..8e8249faf8 100644 --- a/packages/apidom-ls/test/validate.ts +++ b/packages/apidom-ls/test/validate.ts @@ -3714,6 +3714,69 @@ describe('apidom-ls-validate', function () { languageService.terminate(); }); + it('oas 2.0 / yaml - parameter name should be unique', async function () { + const validationContext: ValidationContext = { + comments: DiagnosticSeverity.Error, + maxNumberOfProblems: 100, + relatedInformation: false, + }; + + const spec = fs + .readFileSync( + path.join(__dirname, 'fixtures', 'validation', 'oas', 'parameter-unique-name-2-0.yaml'), + ) + .toString(); + const doc: TextDocument = TextDocument.create( + 'foo://bar/parameter-unique-name-2-0.yaml', + 'yaml', + 0, + spec, + ); + + const languageService: LanguageService = getLanguageService(contextNoSchema); + + const result = await languageService.doValidation(doc, validationContext); + const expected: Diagnostic[] = [ + { + code: 3103000, + data: {}, + message: 'Name must be unique among all parameters', + range: { + end: { + character: 14, + line: 9, + }, + start: { + character: 10, + line: 9, + }, + }, + severity: 1, + source: 'apilint', + }, + { + code: 3103000, + data: {}, + message: 'Name must be unique among all parameters', + range: { + end: { + character: 14, + line: 15, + }, + start: { + character: 10, + line: 15, + }, + }, + severity: 1, + source: 'apilint', + }, + ]; + assert.deepEqual(result, expected as Diagnostic[]); + + languageService.terminate(); + }); + it('oas 3.0 / yaml - parameter Authorization is ignored', async function () { const validationContext: ValidationContext = { comments: DiagnosticSeverity.Error, From 1581750e2444cb6caba696a5dbdc8f78d454e35f Mon Sep 17 00:00:00 2001 From: Robert Hebel Date: Mon, 11 Aug 2025 11:25:49 +0200 Subject: [PATCH 9/9] refactor: improve typings --- .../apidom-ls/src/services/validation/linter-functions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apidom-ls/src/services/validation/linter-functions.ts b/packages/apidom-ls/src/services/validation/linter-functions.ts index b6ccce0cb6..9171020607 100644 --- a/packages/apidom-ls/src/services/validation/linter-functions.ts +++ b/packages/apidom-ls/src/services/validation/linter-functions.ts @@ -801,13 +801,13 @@ export const standardLinterfunctions: FunctionItem[] = [ const value = toValue(element); const filterSiblingsOAS2 = ( - el: Element & { key: { content?: string }; content: { value?: string } }, + 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) => { + const elements = filter((el: Element) => { const classes: string[] = toValue(el.getMetaProperty('classes', [])); return (