Skip to content

Commit

Permalink
fix(ls): fix validation for indirected local references
Browse files Browse the repository at this point in the history
  • Loading branch information
frantuma committed Jul 18, 2023
1 parent 742be72 commit 60f9215
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 18 deletions.
4 changes: 0 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/apidom-ls/src/apidom-language-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export enum Format {
export type Pointer = {
node: Element;
ref: string;
isRef: boolean;
};

export enum MergeStrategy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ export class DefaultCompletionService implements CompletionService {
refElementType && refElementType.length > 0 ? refElementType : node.parent?.parent?.element;
if (!nodeElement) return result;

const pointers = localReferencePointers(doc, nodeElement);
const pointers = localReferencePointers(doc, nodeElement, false);
// build completion item
let i = 97;
for (const p of pointers) {
Expand Down
15 changes: 8 additions & 7 deletions packages/apidom-ls/src/services/validation/validation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ export class DefaultValidationService implements ValidationService {
}
if (!result) return diagnostics;
const { api } = result;

if (api === undefined) return diagnostics;
const specVersion = getSpecVersion(api);

Expand All @@ -224,7 +223,7 @@ export class DefaultValidationService implements ValidationService {
if (refValueElement.toValue().startsWith('#')) {
let pointers = pointersMap[referencedElement];
if (!pointers) {
pointers = localReferencePointers(doc, referencedElement);
pointers = localReferencePointers(doc, referencedElement, true);
pointersMap[referencedElement] = pointers;
}
if (!pointers.some((p) => p.ref === refValueElement.toValue())) {
Expand All @@ -250,11 +249,13 @@ export class DefaultValidationService implements ValidationService {
} as LinterMetaData;
for (const p of pointers) {
// @ts-ignore
diagnostic.data.quickFix.push({
message: `update to ${p.ref}`,
action: 'updateValue',
functionParams: [p.ref],
});
if (refValueElement !== p.ref && !p.isRef) {
diagnostic.data.quickFix.push({
message: `update to ${p.ref}`,
action: 'updateValue',
functionParams: [p.ref],
});
}
}
// @ts-ignore
this.quickFixesMap[code] = diagnostic.data.quickFix;
Expand Down
31 changes: 25 additions & 6 deletions packages/apidom-ls/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,19 @@ export function buildJsonPointer(path: string[]): string {
return `#/${path.join('/')}`;
}

export function localReferencePointers(doc: Element, nodeElement: string): Pointer[] {
interface FoundNode {
element: Element;
isRef: boolean;
}

export function localReferencePointers(
doc: Element,
nodeElement: string,
includeRefs: boolean,
): Pointer[] {
const pointers: Pointer[] = [];
// traverse all doc to find nodes of the same type which are not a ref
const foundNodes: Element[] = [];
const foundNodes: FoundNode[] = [];
let nodePath: string[] = [];
function buildPointer(traverseNode: Element): void {
if (!traverseNode) return;
Expand All @@ -201,23 +210,33 @@ export function localReferencePointers(doc: Element, nodeElement: string): Point

// TODO check for reference-element class or type instead
function findRefNodes(traversedNode: Element): void {
if (traversedNode.element === nodeElement) {
if (includeRefs) {
const isRef =
nodeElement === traversedNode.getMetaProperty('referenced-element', '').toValue();
if (isRef || traversedNode.element === nodeElement) {
foundNodes.push({ element: traversedNode, isRef });
}
} else if (traversedNode.element === nodeElement) {
if (
!(
isObject(traversedNode) &&
traversedNode.get('$ref') &&
traversedNode.get('$ref').toValue().length > 0
)
) {
foundNodes.push(traversedNode);
foundNodes.push({ element: traversedNode, isRef: false });
}
}
}
traverse(findRefNodes, doc);
for (const foundNode of foundNodes) {
nodePath = [];
buildPointer(foundNode);
pointers.push({ node: foundNode, ref: buildJsonPointer(nodePath) });
buildPointer(foundNode.element);
pointers.push({
node: foundNode.element,
ref: buildJsonPointer(nodePath),
isRef: foundNode.isRef,
});
}
// TODO better sorting, NS plugin..
pointers.sort((a, b) => (a.ref.split('/').length > b.ref.split('/').length ? 1 : -1));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
openapi: 3.1.0
info:
title: deref
version: 1.0.0
servers:
- description: local
url: http://localhost:8082/
paths:
/a:
get:
operationId: aget
parameters:
- $ref: '#/components/parameters/userId'
post:
operationId: apost
/b:
get:
operationId: bget
parameters:
- $ref: '#/components/parameters/userId'
post:
operationId: bpost
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/foo'
components:
schemas:
foo:
type: string
bar:
$id: http://localhost:8082/
type: string
parameters:
userId:
$ref: '#/components/parameters/indirection1'
description: override
userIdBad:
$ref: '#/components/parameters/userIdRefBad'
description: override
indirection1:
$ref: '#/components/parameters/indirection2'
summary: indirect summary
indirection2:
$ref: '#/components/parameters/userIdRef'
summary: indirect summary
userIdRef:
name: userId
in: query
description: ID of the user
required: true
schema:
type: string
externalRef:
$ref: ./ex.json#/externalParameter
description: another ref
44 changes: 44 additions & 0 deletions packages/apidom-ls/test/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2997,4 +2997,48 @@ describe('apidom-ls-validate', function () {

languageService.terminate();
});

it('oas / yaml - test editor issue 3626 / inidrect ref', async function () {
const validationContext: ValidationContext = {
comments: DiagnosticSeverity.Error,
maxNumberOfProblems: 100,
relatedInformation: false,
};

const spec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'validation', 'oas', 'issue-editor-3626.yaml'))
.toString();
const doc: TextDocument = TextDocument.create(
'foo://bar/issue-editor-3626.yaml',
'yaml',
0,
spec,
);

const languageService: LanguageService = getLanguageService(contextNoSchema);

const result = await languageService.doValidation(doc, validationContext);
result[0].code = 'test';
const expected: Diagnostic[] = [
{
range: { start: { line: 39, character: 12 }, end: { line: 39, character: 50 } },
message: 'local reference not found',
severity: 1,
code: 'test',
source: 'apilint',
data: {
quickFix: [
{
message: 'update to #/components/parameters/userIdRef',
action: 'updateValue',
functionParams: ['#/components/parameters/userIdRef'],
},
],
},
},
];
assert.deepEqual(result, expected as Diagnostic[]);

languageService.terminate();
});
});

0 comments on commit 60f9215

Please sign in to comment.