Skip to content

Commit

Permalink
feat(apidom-ls): create schema rule for missing core keywords
Browse files Browse the repository at this point in the history
fix #3549
  • Loading branch information
frantuma committed Jan 17, 2024
1 parent dbdb2a1 commit 0e921ae
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 0 deletions.
17 changes: 17 additions & 0 deletions packages/apidom-ls/src/config/asyncapi/target-specs.ts
@@ -0,0 +1,17 @@
export const AsyncAPI200 = [{ namespace: 'asyncapi', version: '2.0.0' }];
export const AsyncAPI210 = [{ namespace: 'asyncapi', version: '2.1.0' }];
export const AsyncAPI220 = [{ namespace: 'asyncapi', version: '2.2.0' }];
export const AsyncAPI230 = [{ namespace: 'asyncapi', version: '2.3.0' }];
export const AsyncAPI240 = [{ namespace: 'asyncapi', version: '2.4.0' }];
export const AsyncAPI250 = [{ namespace: 'asyncapi', version: '2.5.0' }];
export const AsyncAPI260 = [{ namespace: 'asyncapi', version: '2.6.0' }];

export const AsyncAPI2 = [
...AsyncAPI200,
...AsyncAPI210,
...AsyncAPI220,
...AsyncAPI230,
...AsyncAPI240,
...AsyncAPI250,
...AsyncAPI260,
];
1 change: 1 addition & 0 deletions packages/apidom-ls/src/config/codes.ts
Expand Up @@ -66,6 +66,7 @@ enum ApilintCodes {
SCHEMA_EXAMPLE_DEPRECATED,
SCHEMA_TYPE_OPENAPI_3_0,
SCHEMA_NULLABLE_NOT_RECOMMENDED,
SCHEMA_MISSING_CORE_FIELDS,

DUPLICATE_KEYS = 14999,
NOT_ALLOWED_FIELDS = 15000,
Expand Down
8 changes: 8 additions & 0 deletions packages/apidom-ls/src/config/common/schema/lint/index.ts
Expand Up @@ -36,6 +36,10 @@ import minLengthTypeLint from './min-length--type';
import minPropertiesNonObjectLint from './min-properties--non-object';
import minPropertiesTypeLint from './min-properties--type';
import minimumPatternLint from './minimum--pattern';
import missingCoreFieldsOpenAPI2_0Lint from './missing-core-fields-openapi-2-0';
import missingCoreFieldsOpenAPI3_0Lint from './missing-core-fields-openapi-3-0';
import missingCoreFieldsOpenAPI3_1Lint from './missing-core-fields-openapi-3-1';
import missingCoreFieldsAsyncAPI2Lint from './missing-core-fields-asyncapi-2';
import multipleOfTypeLint from './multiple-of--type';
import notTypeLint from './not--type';
import nullableNotRecommendedLint from './nullable--not-recommended';
Expand Down Expand Up @@ -102,6 +106,10 @@ const schemaLints = [
minPropertiesNonObjectLint,
minPropertiesTypeLint,
minimumPatternLint,
missingCoreFieldsOpenAPI2_0Lint,
missingCoreFieldsOpenAPI3_0Lint,
missingCoreFieldsOpenAPI3_1Lint,
missingCoreFieldsAsyncAPI2Lint,
multipleOfTypeLint,
notTypeLint,
nullableNotRecommendedLint,
Expand Down
@@ -0,0 +1,73 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

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

// eslint-disable-next-line @typescript-eslint/naming-convention
const missingCoreFieldsAsyncAPI2Lint: LinterMeta = {
code: ApilintCodes.SCHEMA_MISSING_CORE_FIELDS,
source: 'apilint',
message: 'Schema does not include any Schema Object keywords',
severity: DiagnosticSeverity.Hint,
linterFunction: 'existAnyOfFields',
linterParams: [
[
'$id',
'$schema',
'$comment',
'$ref',
'if',
'then',
'else',
'contentEncoding',
'contentMediaType',
'contains',
'propertyNames',
'const',
'examples',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'patternProperties',
'dependencies',
'definitions',
'maxProperties',
'minProperties',
'required',
'properties',
'additionalProperties',
'enum',
'type',
'allOf',
'anyOf',
'oneOf',
'not',
'title',
'description',
'default',
'format',
'readOnly',
'writeOnly',
'discriminator',
'externalDocs',
'deprecated',
],
true,
'boolean',
],
marker: 'key',
targetSpecs: [...AsyncAPI2],
};

export default missingCoreFieldsAsyncAPI2Lint;
@@ -0,0 +1,55 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

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

// eslint-disable-next-line @typescript-eslint/naming-convention
const missingCoreFieldsOpenAPI2Lint: LinterMeta = {
code: ApilintCodes.SCHEMA_MISSING_CORE_FIELDS,
source: 'apilint',
message: 'Schema does not include any Schema Object keywords',
severity: DiagnosticSeverity.Hint,
linterFunction: 'existAnyOfFields',
linterParams: [
[
'$ref',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'properties',
'additionalProperties',
'enum',
'type',
'allOf',
'title',
'description',
'default',
'format',
'readOnly',
'discriminator',
'externalDocs',
'xml',
'example',
],
true,
'boolean',
],
marker: 'key',
targetSpecs: [...OpenAPI2],
};

export default missingCoreFieldsOpenAPI2Lint;
@@ -0,0 +1,61 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

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

// eslint-disable-next-line @typescript-eslint/naming-convention
const missingCoreFieldsOpenAPI3_0Lint: LinterMeta = {
code: ApilintCodes.SCHEMA_MISSING_CORE_FIELDS,
source: 'apilint',
message: 'Schema does not include any Schema Object keywords',
severity: DiagnosticSeverity.Hint,
linterFunction: 'existAnyOfFields',
linterParams: [
[
'$ref',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'properties',
'additionalProperties',
'enum',
'type',
'allOf',
'anyOf',
'oneOf',
'not',
'title',
'description',
'default',
'format',
'readOnly',
'nullable',
'discriminator',
'externalDocs',
'writeOnly',
'xml',
'example',
'deprecated',
],
true,
'boolean',
],
marker: 'key',
targetSpecs: [...OpenAPI30],
};

export default missingCoreFieldsOpenAPI3_0Lint;
@@ -0,0 +1,87 @@
import { DiagnosticSeverity } from 'vscode-languageserver-types';

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

// eslint-disable-next-line @typescript-eslint/naming-convention
const missingCoreFieldsOpenAPI3_1Lint: LinterMeta = {
code: ApilintCodes.SCHEMA_MISSING_CORE_FIELDS,
source: 'apilint',
message: 'Schema does not include any Schema Object keywords',
severity: DiagnosticSeverity.Hint,
linterFunction: 'existAnyOfFields',
linterParams: [
[
'$ref',
'$schema',
'$id',
'$vocabulary',
'$anchor',
'$dynamicAnchor',
'$defs',
'$comment',
'if',
'then',
'else',
'dependentSchemas',
'prefixItems',
'contains',
'patternProperties',
'propertyNames',
'unevaluatedProperties',
'unevaluatedItems',
'const',
'maxContains',
'minContains',
'dependencies',
'dependentRequired',
'examples',
'contentEncoding',
'contentMediaType',
'contentSchema',
'definitions',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'additionalItems',
'items',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'properties',
'additionalProperties',
'enum',
'type',
'allOf',
'anyOf',
'oneOf',
'not',
'title',
'description',
'default',
'format',
'readOnly',
'discriminator',
'externalDocs',
'writeOnly',
'xml',
'example',
'deprecated',
],
true,
'boolean',
],
marker: 'key',
targetSpecs: [...OpenAPI31],
};

export default missingCoreFieldsOpenAPI3_1Lint;
24 changes: 24 additions & 0 deletions packages/apidom-ls/src/services/validation/linter-functions.ts
Expand Up @@ -172,6 +172,30 @@ export const standardLinterfunctions: FunctionItem[] = [
return true;
},
},
{
functionName: 'existAnyOfFields',
function: (
element: Element,
keys: string[],
allowEmpty: boolean,
skipIfType?: string,
): boolean => {
if (element && isObject(element)) {
if (skipIfType && isType(element, skipIfType)) {
return true;
}
if (!element.keys() || element.keys().length === 0) {
return allowEmpty;
}
for (const key of keys) {
if (element.hasKey(key)) {
return true;
}
}
}
return false;
},
},
{
functionName: 'allowedFields',
function: (
Expand Down

0 comments on commit 0e921ae

Please sign in to comment.