Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ls): fix validation for indirected local references #2959

Merged
merged 1 commit into from
Jul 18, 2023
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
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();
});
});