Skip to content

Commit c892835

Browse files
authored
perf(ls): add cache for linting functions (#5057)
1 parent d3037a5 commit c892835

File tree

8 files changed

+84
-31
lines changed

8 files changed

+84
-31
lines changed

packages/apidom-ls/src/config/asyncapi/message-trait/lint/message-id--unique.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const messageIdUniqueLint: LinterMeta = {
99
message: "messageID' must be unique among all messages",
1010
severity: DiagnosticSeverity.Error,
1111
linterFunction: 'apilintPropertyUniqueValue',
12-
linterParams: [['message', 'messageTrait'], 'messageId'],
12+
linterParams: [['message', 'messageTrait'], 'messageId', 'propertyValues'],
1313
marker: 'key',
1414
markerTarget: 'messageId',
1515
target: 'messageId',

packages/apidom-ls/src/config/asyncapi/message/lint/message-id--unique.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const messageIdUniqueLint: LinterMeta = {
99
message: "messageID' must be unique among all messages",
1010
severity: DiagnosticSeverity.Error,
1111
linterFunction: 'apilintPropertyUniqueValue',
12-
linterParams: [['message', 'messageTrait'], 'messageId'],
12+
linterParams: [['message', 'messageTrait'], 'messageId', 'propertyValues'],
1313
marker: 'key',
1414
markerTarget: 'messageId',
1515
target: 'messageId',

packages/apidom-ls/src/config/asyncapi/operation-trait/lint/operation-id--unique.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const operationIdUniqueLint: LinterMeta = {
99
message: "operationId' must be unique among all operations",
1010
severity: DiagnosticSeverity.Error,
1111
linterFunction: 'apilintPropertyUniqueValue',
12-
linterParams: [['operation', 'operationTrait'], 'operationId'],
12+
linterParams: [['operation', 'operationTrait'], 'operationId', 'propertyValues'],
1313
marker: 'key',
1414
markerTarget: 'operationId',
1515
target: 'operationId',

packages/apidom-ls/src/config/asyncapi/operation/lint/operation-id--unique.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const operationIdUniqueLint: LinterMeta = {
99
message: "operationId' must be unique among all operations",
1010
severity: DiagnosticSeverity.Error,
1111
linterFunction: 'apilintPropertyUniqueValue',
12-
linterParams: [['operation', 'operationTrait'], 'operationId'],
12+
linterParams: [['operation', 'operationTrait'], 'operationId', 'propertyValues'],
1313
marker: 'key',
1414
markerTarget: 'operationId',
1515
target: 'operationId',

packages/apidom-ls/src/config/common/schema/lint/$ref--not-used.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const $refNotUsedLint: LinterMeta = {
1010
message: 'Definition was declared but never used in document',
1111
severity: DiagnosticSeverity.Warning,
1212
linterFunction: 'apilintReferenceNotUsed',
13-
linterParams: ['string'],
13+
linterParams: ['referenceNames'],
1414
marker: 'key',
1515
data: {},
1616
targetSpecs: [...OpenAPI2, ...OpenAPI30],

packages/apidom-ls/src/config/openapi/operation/lint/operation-id--unique.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const operationIdUniqueLint: LinterMeta = {
1010
message: "operationId' must be unique among all operations",
1111
severity: DiagnosticSeverity.Error,
1212
linterFunction: 'apilintPropertyUniqueValue',
13-
linterParams: [['operation'], 'operationId'],
13+
linterParams: [['operation'], 'operationId', 'propertyValues'],
1414
marker: 'key',
1515
markerTarget: 'operationId',
1616
target: 'operationId',

packages/apidom-ls/src/services/validation/linter-functions.ts

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
ObjectElement,
1111
isArrayElement,
1212
includesClasses,
13+
isObjectElement,
14+
traverse,
1315
} from '@swagger-api/apidom-core';
1416
import { URIFragmentIdentifier } from '@swagger-api/apidom-json-pointer/modern';
1517
import { CompletionItem } from 'vscode-languageserver-types';
@@ -956,22 +958,42 @@ export const standardLinterfunctions: FunctionItem[] = [
956958
},
957959
{
958960
functionName: 'apilintPropertyUniqueValue',
959-
function: (element: Element, elementOrClasses: string[], key: string): boolean => {
961+
function: (
962+
element: Element,
963+
elementOrClasses: string[],
964+
key: string,
965+
propertyValues: Map<string, string[]>,
966+
): boolean => {
960967
const api = root(element);
961968
const value = toValue(element);
962-
const elements: ArraySlice = filter((el: Element) => {
963-
const classes: string[] = toValue(el.getMetaProperty('classes', []));
964-
return (
965-
(elementOrClasses.includes(el.element) ||
966-
classes.every((v) => elementOrClasses.includes(v))) &&
967-
isObject(el) &&
968-
el.hasKey(key) &&
969-
toValue(el.get(key)) === value
970-
);
971-
}, api);
972-
if (elements.length > 1) {
969+
const cacheKey = elementOrClasses.join(',');
970+
971+
if (!propertyValues.has(cacheKey)) {
972+
traverse((el: Element) => {
973+
const classes: ArrayElement = el.getMetaProperty('classes', []);
974+
if (
975+
(elementOrClasses.includes(el.element) ||
976+
classes.filter((classElement: Element) =>
977+
elementOrClasses.includes(toValue(classElement)),
978+
).length === classes.length) &&
979+
isObject(el) &&
980+
el.hasKey(key)
981+
) {
982+
const elValue = toValue(el.get(key));
983+
const cachedValues = propertyValues.get(cacheKey) ?? [];
984+
985+
cachedValues.push(elValue);
986+
propertyValues.set(cacheKey, cachedValues);
987+
}
988+
}, api);
989+
}
990+
991+
const cachedValues = propertyValues.get(cacheKey) ?? [];
992+
993+
if (cachedValues.filter((cachedValue) => cachedValue === value).length > 1) {
973994
return false;
974995
}
996+
975997
return true;
976998
},
977999
},
@@ -1327,7 +1349,7 @@ export const standardLinterfunctions: FunctionItem[] = [
13271349
},
13281350
{
13291351
functionName: 'apilintReferenceNotUsed',
1330-
function: (element: Element & { content?: { key?: string } }) => {
1352+
function: (element: Element & { content?: { key?: string } }, referenceNames: string[]) => {
13311353
const elParent: Element = element.parent?.parent?.parent?.parent;
13321354
if (
13331355
(typeof elParent?.hasKey !== 'function' || !elParent.hasKey('schemas')) &&
@@ -1336,17 +1358,29 @@ export const standardLinterfunctions: FunctionItem[] = [
13361358
return true;
13371359
}
13381360

1339-
const api = root(element);
1340-
const isReferenceElement = (el: Element & { content?: { key?: string } }) =>
1341-
toValue(el.content.key) === '$ref';
1342-
const referenceElements = filter((el) => {
1343-
return isReferenceElement(el);
1344-
}, api);
1345-
const referenceNames = referenceElements.map((refElement: Element) =>
1346-
// @ts-expect-error
1347-
toValue(refElement.content.value).split('/').at(-1),
1348-
);
1349-
// @ts-expect-error
1361+
if (referenceNames.length === 0) {
1362+
const api = root(element);
1363+
const isReferenceElement = (el: Element & { content?: { key?: string } }) => {
1364+
if (!isObjectElement(el) || !el.hasKey('$ref')) {
1365+
return false;
1366+
}
1367+
1368+
const $ref = el.get('$ref');
1369+
1370+
return isStringElement($ref) && toValue($ref).startsWith('#');
1371+
};
1372+
1373+
const referenceElements = filter((el) => {
1374+
return isReferenceElement(el);
1375+
}, api);
1376+
1377+
referenceNames.push(
1378+
...referenceElements.map((refElement: ObjectElement) =>
1379+
toValue(refElement.get('$ref')).split('/').at(-1),
1380+
),
1381+
);
1382+
}
1383+
13501384
return referenceNames.includes(toValue(element.parent.key));
13511385
},
13521386
},

packages/apidom-ls/src/services/validation/validation-service.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export class DefaultValidationService implements ValidationService {
7676

7777
private lintingRulesSemanticCache: Map<string, LinterMeta[]> = new Map();
7878

79+
private referenceNamesCache: string[] = [];
80+
81+
private propertyValuesCache: Map<string, string[]> = new Map();
82+
7983
public constructor() {
8084
this.validationEnabled = true;
8185
this.commentSeverity = undefined;
@@ -838,6 +842,9 @@ export class DefaultValidationService implements ValidationService {
838842
}
839843
}
840844

845+
this.referenceNamesCache = [];
846+
this.propertyValuesCache.clear();
847+
841848
return diagnostics;
842849
}
843850

@@ -894,7 +901,19 @@ export class DefaultValidationService implements ValidationService {
894901
Array.isArray(meta.linterParams) &&
895902
meta.linterParams.length > 0
896903
) {
897-
const params = [targetElement].concat(meta.linterParams);
904+
const params = [targetElement].concat(
905+
meta.linterParams.map((param) => {
906+
if (param === 'referenceNames') {
907+
return this.referenceNamesCache;
908+
}
909+
910+
if (param === 'propertyValues') {
911+
return this.propertyValuesCache;
912+
}
913+
914+
return param;
915+
}),
916+
);
898917
lintRes = lintFunc(...params) as boolean;
899918
} else {
900919
lintRes = lintFunc(targetElement) as boolean;

0 commit comments

Comments
 (0)