Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/apidom-ls/src/config/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ enum ApilintCodes {

OPENAPI2_REFERENCE = 3240000,
OPENAPI2_REFERENCE_FIELD_$REF_FORMAT_URI = 3240100,
OPENAPI2_REFERENCE_NOT_USED = 3240300,

OPENAPI3_0 = 5000000,

Expand Down Expand Up @@ -1070,6 +1071,11 @@ enum ApilintCodes {
OPENAPI3_0_REFERENCE = 5260000,
OPENAPI3_0_REFERENCE_FIELD_$REF_FORMAT_URI = 5260100,
OPENAPI3_0_REFERENCE_FIELD_$REF_NO_SIBLINGS,
OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES = 5260200,
OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES_NAMING,
OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES_NAMING_SCHEMA,
OPENAPI3_0_REFERENCE_FIELD_$REF_HEADER = 5260300,
OPENAPI3_0_REFERENCE_FIELD_$REF_PARAMETER = 5260400,

OPENAPI3_0_LINK = 5270000,
OPENAPI3_0_LINK_FIELD_OPERATION_REF_FORMAT_URI = 5270100,
Expand Down
19 changes: 19 additions & 0 deletions packages/apidom-ls/src/config/common/schema/lint/$ref--not-used.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI2, OpenAPI3, OpenAPI31 } from '../../../openapi/target-specs.ts';

const $refNotUsedLint: LinterMeta = {
code: ApilintCodes.OPENAPI2_REFERENCE_NOT_USED,
source: 'apilint',
message: 'Definition was declared but never used in document',
severity: DiagnosticSeverity.Warning,
linterFunction: 'apilintReferenceNotUsed',
linterParams: ['string'],
marker: 'key',
data: {},
targetSpecs: [...OpenAPI2, ...OpenAPI3, ...OpenAPI31],
};

export default $refNotUsedLint;
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { OpenAPI2, OpenAPI3 } from '../../../openapi/target-specs.ts';
const $refValidLint: LinterMeta = {
code: ApilintCodes.SCHEMA_REF,
source: 'apilint',
message: "'$ref' value must be a valid URI-reference",
message: "'$ref' value must be an RFC3986-compliant URI reference",
severity: DiagnosticSeverity.Error,
linterFunction: 'apilintValidURI',
linterFunction: 'apilintValidURI_RFC3986',
marker: 'value',
target: '$ref',
data: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI3 } from '../../../openapi/target-specs.ts';

const $ref3RequestBodiesLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES,
source: 'apilint',
message:
'requestBody schema $refs must point to a position where a Schema Object can be legally placed',
severity: DiagnosticSeverity.Error,
linterFunction: 'parentExistFields',
linterParams: [['requestBody']],
conditions: [
{
targets: [{ path: '$ref' }],
function: 'apilintValueRegex',
params: ['^(?!.*#/components/schemas).*$'],
},
],
marker: 'value',
target: '$ref',
data: {},
targetSpecs: OpenAPI3,
};

export default $ref3RequestBodiesLint;
4 changes: 4 additions & 0 deletions packages/apidom-ls/src/config/common/schema/lint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import allowedFieldsOpenAPI3_0Lint from './allowed-fields-openapi-3-0.ts';
import $idFormatURILint from './$id--format-uri.ts';
import $refValidLint from './$ref--valid.ts';
import $refNoSiblingsLint from './$ref--no-siblings.ts';
import $ref3RequestBodiesLint from './$ref-3-0--request-bodies.ts';
import additionalItemsNonArrayLint from './additional-items--non-array.ts';
import additionalItemsTypeLint from './additional-items--type.ts';
import additionalItemsTypeOpenAPI3_1__AsyncAPI2Lint from './additional-items--type-openapi-3-1--asyncapi-2.ts';
Expand Down Expand Up @@ -81,13 +82,16 @@ import uniqueItemsNonArrayLint from './unique-items--non-array.ts';
import uniqueItemsTypeLint from './unique-items--type.ts';
import writeOnlyTypeLint from './write-only--type.ts';
import exampleDeprecatedLint from './example--deprecated.ts';
import $refNotUsedLint from './$ref--not-used.ts';

const schemaLints = [
allowedFieldsOpenAPI2_0Lint,
allowedFieldsOpenAPI3_0Lint,
$idFormatURILint,
$refValidLint,
$refNoSiblingsLint,
$refNotUsedLint,
$ref3RequestBodiesLint,
additionalItemsNonArrayLint,
additionalItemsTypeLint,
additionalItemsTypeOpenAPI3_1__AsyncAPI2Lint,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import valuesTypeLint from './values--type.ts';
import $refNotUsedLint from '../../../common/schema/lint/$ref--not-used.ts';

const lints = [valuesTypeLint];
const lints = [valuesTypeLint, $refNotUsedLint];

export default lints;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI3 } from '../../target-specs.ts';

const $ref3HeaderNamingLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_REFERENCE_FIELD_$REF_HEADER,
source: 'apilint',
message: 'OAS3 header $Ref should point to Header Object',
severity: DiagnosticSeverity.Error,
linterFunction: 'parentExistFields',
linterParams: [['header']],
conditions: [
{
targets: [{ path: '$ref' }],
function: 'apilintValueRegex',
params: ['^(?!.*#/components/headers).*$'],
},
],
marker: 'value',
target: '$ref',
data: {},
targetSpecs: OpenAPI3,
};

export default $ref3HeaderNamingLint;
2 changes: 2 additions & 0 deletions packages/apidom-ls/src/config/openapi/header/lint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import minItemsTypeLint from './min-items--type.ts';
import uniqueItemsTypeLint from './unique-items--type.ts';
import enumTypeLint from './enum--type.ts';
import multipleOfTypeLint from './multiple-of--type.ts';
import $ref3HeaderNamingLint from './$ref-3-0--header.ts';

const lints = [
$ref3HeaderNamingLint,
descriptionTypeLint,
requiredTypeLint,
deprecatedTypeLint,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI3 } from '../../target-specs.ts';

const $ref3ParameterNamingLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_REFERENCE_FIELD_$REF_PARAMETER,
source: 'apilint',
message: 'OAS3 parameter $Ref should point to Parameter Object',
severity: DiagnosticSeverity.Error,
linterFunction: 'apilintValueRegex',
linterParams: ['^(.*#/components/parameters).*$'],
marker: 'value',
target: '$ref',
conditions: [
{
targets: [{ path: '$ref' }],
function: 'parentExistFields',
params: [['paths']],
},
],
data: {},
targetSpecs: OpenAPI3,
};

export default $ref3ParameterNamingLint;
2 changes: 2 additions & 0 deletions packages/apidom-ls/src/config/openapi/parameter/lint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ 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';
import $ref3ParameterNamingLint from './$ref-3-0--parameter.ts';

const lints = [
$ref3ParameterNamingLint,
nameTypeLint,
nameRequiredLint,
inEquals2_0Lint,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI3 } from '../../target-specs.ts';

const $ref3RequestBodiesNamingSchemaLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES_NAMING_SCHEMA,
source: 'apilint',
message:
"requestBody $refs cannot point to '#/components/schemas/…', they must point to '#/components/requestBodies/…'",
severity: DiagnosticSeverity.Error,
linterFunction: 'parentExistFields',
linterParams: [['requestBodies']],
conditions: [
{
targets: [{ path: '$ref' }],
function: 'apilintValueRegex',
params: ['^(.*#/components/schemas).*$'],
},
],
marker: 'value',
target: '$ref',
data: {},
targetSpecs: OpenAPI3,
};

export default $ref3RequestBodiesNamingSchemaLint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import ApilintCodes from '../../../codes.ts';
import { LinterMeta } from '../../../../apidom-language-types.ts';
import { OpenAPI3 } from '../../target-specs.ts';

const $ref3RequestBodiesNamingLint: LinterMeta = {
code: ApilintCodes.OPENAPI3_0_REFERENCE_FIELD_$REF_REQUEST_BODIES_NAMING,
source: 'apilint',
message: 'requestBody $refs must point to a position where a requestBody can be legally placed',
severity: DiagnosticSeverity.Error,
linterFunction: 'parentExistFields',
linterParams: [['requestBodies']],
conditions: [
{
targets: [{ path: '$ref' }],
function: 'apilintValueRegex',
params: ['^(?!.*#/components/(requestBodies|schemas)).*$'],
},
],
marker: 'value',
target: '$ref',
data: {},
targetSpecs: OpenAPI3,
};

export default $ref3RequestBodiesNamingLint;
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import descriptionTypeLint from './description--type.ts';
import contentValuesTypeLint from './content--values-type.ts';
import contentRequiredLint from './content--required.ts';
import requiredTypeLint from './required--type.ts';
import $ref3RequestBodiesNamingLint from './$ref-3-0--request-bodies-naming.ts';
import $ref3RequestBodiesNamingSchemaLint from './$ref-3-0--request-bodies-naming-schema.ts';

const lints = [
$ref3RequestBodiesNamingLint,
$ref3RequestBodiesNamingSchemaLint,
descriptionTypeLint,
contentRequiredLint,
contentValuesTypeLint,
Expand Down
8 changes: 8 additions & 0 deletions packages/apidom-ls/src/config/openapi/schema/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ import uniqueItemsNonArrayLint from '../../common/schema/lint/unique-items--non-
import uniqueItemsTypeLint from '../../common/schema/lint/unique-items--type.ts';
import writeOnlyTypeLint from '../../common/schema/lint/write-only--type.ts';
import exampleDeprecatedLint from '../../common/schema/lint/example--deprecated.ts';
import $refNotUsedLint from '../../common/schema/lint/$ref--not-used.ts';
import $ref3RequestBodiesLint from '../../common/schema/lint/$ref-3-0--request-bodies.ts';
import $refNoSiblingsLint from '../../common/schema/lint/$ref--no-siblings.ts';
import $refValidLint from '../../common/schema/lint/$ref--valid.ts';
import { OpenAPI31 } from '../target-specs.ts';

const schemaLints = [
Expand Down Expand Up @@ -154,6 +158,10 @@ const schemaLints = [
uniqueItemsTypeLint,
writeOnlyTypeLint,
exampleDeprecatedLint,
$refNotUsedLint,
$refNoSiblingsLint,
$refValidLint,
$ref3RequestBodiesLint,
];

export default schemaLints;
62 changes: 62 additions & 0 deletions packages/apidom-ls/src/services/validation/linter-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isArrayElement,
includesClasses,
} from '@swagger-api/apidom-core';
import { URIFragmentIdentifier } from '@swagger-api/apidom-json-pointer/modern';
import { CompletionItem } from 'vscode-languageserver-types';
import {
test as testPathTemplate,
Expand Down Expand Up @@ -176,6 +177,20 @@ export const standardLinterfunctions: FunctionItem[] = [
return true;
},
},
{
functionName: 'parentExistFields',
function: (element: Element, keys: string[]): boolean => {
const parent = element?.parent?.parent?.parent?.parent;
if (parent && isObject(parent)) {
for (const key of keys) {
if (!parent.hasKey(key)) {
return false;
}
}
}
return true;
},
},
{
functionName: 'existAnyOfFields',
function: (element: Element, keys: string[], allowEmpty: boolean): boolean => {
Expand Down Expand Up @@ -561,6 +576,28 @@ export const standardLinterfunctions: FunctionItem[] = [
return true;
},
},
{
functionName: 'apilintValidURI_RFC3986',
function: (element: Element, absolute = false): boolean => {
if (element) {
if (!isString(element)) {
return false;
}
try {
// eslint-disable-next-line no-new
const url = new URL(toValue(element), absolute ? undefined : 'http://example.com');

const rfc3986Fragment = url.hash ? url.hash.slice(1) : url.pathname;
const rest = URIFragmentIdentifier.to(rfc3986Fragment).slice(1);

return rest === rfc3986Fragment;
} catch (e) {
return false;
}
}
return true;
},
},
{
functionName: 'apicompleteDiscriminator',
function: (element: Element): CompletionItem[] => {
Expand Down Expand Up @@ -1113,6 +1150,31 @@ export const standardLinterfunctions: FunctionItem[] = [
return true;
},
},
{
functionName: 'apilintReferenceNotUsed',
function: (element: Element & { content?: { key?: string } }) => {
const elParent: Element = element.parent?.parent?.parent?.parent;
if (
(typeof elParent?.hasKey !== 'function' || !elParent.hasKey('schemas')) &&
toValue(element?.parent?.parent?.parent?.key) !== 'definitions'
) {
return true;
}

const api = root(element);
const isReferenceElement = (el: Element & { content?: { key?: string } }) =>
toValue(el.content.key) === '$ref';
const referenceElements = filter((el) => {
return isReferenceElement(el);
}, api);
const referenceNames = referenceElements.map((refElement: Element) =>
// @ts-expect-error
toValue(refElement.content.value).split('/').at(-1),
);
// @ts-expect-error
return referenceNames.includes(toValue(element.parent.key));
},
},
{
functionName: 'apilintOpenAPIParameterInPathTemplate',
function: (element: Element) => {
Expand Down
Loading