Skip to content

Commit

Permalink
feat(ls): fix completion paths, self and indirect refs
Browse files Browse the repository at this point in the history
  • Loading branch information
frantuma committed Jul 21, 2023
1 parent 9c1bd93 commit b275efa
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 33 deletions.
2 changes: 2 additions & 0 deletions packages/apidom-ls/src/apidom-language-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface ContentLanguage {
namespace: string;
format?: 'JSON' | 'YAML';
version?: string;
admitsRefsSiblings?: boolean;
}

export interface ValidationProviderResult {
Expand Down Expand Up @@ -279,6 +280,7 @@ export interface ValidationContext {
export interface CompletionContext {
maxNumberOfItems?: number;
enableLSPFilter?: boolean;
includeIndirectRefs?: boolean;
}

export interface DerefContext {
Expand Down
48 changes: 30 additions & 18 deletions packages/apidom-ls/src/services/completion/completion-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ export class DefaultCompletionService implements CompletionService {
api,
completionNode,
docNs,
contentLanguage.admitsRefsSiblings !== undefined && contentLanguage.admitsRefsSiblings,
specVersion,
nodeValueFromText,
completionParamsOrPosition,
Expand Down Expand Up @@ -877,6 +878,7 @@ export class DefaultCompletionService implements CompletionService {
doc: Element,
node: Element,
docNs: string,
admitsRefsSiblings: boolean,
specVersion: string,
nodeValue: string,
completionParamsOrPosition: CompletionParams | Position,
Expand All @@ -894,27 +896,37 @@ export class DefaultCompletionService implements CompletionService {
refElementType && refElementType.length > 0 ? refElementType : node.parent?.parent?.element;
if (!nodeElement) return result;

const pointers = localReferencePointers(doc, nodeElement, false);
const pointers = localReferencePointers(
doc,
nodeElement,
admitsRefsSiblings &&
completionContext !== undefined &&
completionContext?.includeIndirectRefs !== undefined &&
completionContext?.includeIndirectRefs,
);
// build completion item
let i = 97;

for (const p of pointers) {
const valueQuotes = yaml ? "'" : '"';
const sm = getSourceMap(p.node);
const item: CompletionItem = {
label: p.ref,
insertText: `${valueQuotes}${p.ref}$1${valueQuotes}`,
kind: 18,
documentation: textDocument.getText().substring(sm.offset, sm.endOffset),
// detail: 'DETAIL', // on top of right hand documentation panel
// labelDetails: {
// detail: 'LABELDETAILDETAIL', // right after label
// description: 'local', // right aligned in label panel
// },
insertTextFormat: 2,
sortText: `${String.fromCharCode(i)}`,
};
result.push(item);
i += 1;
if (p.node !== node.parent?.parent) {
const valueQuotes = yaml ? "'" : '"';
const sm = getSourceMap(p.node);
const item: CompletionItem = {
label: p.ref,
insertText: `${valueQuotes}${p.ref}$1${valueQuotes}`,
kind: 18,
documentation: textDocument.getText().substring(sm.offset, sm.endOffset),
// detail: 'DETAIL', // on top of right hand documentation panel
// labelDetails: {
// detail: 'LABELDETAILDETAIL', // right after label
// description: 'local', // right aligned in label panel
// },
insertTextFormat: 2,
sortText: `${String.fromCharCode(i)}`,
};
result.push(item);
i += 1;
}
}
// TODO also add to completion description target fragment so user can preview
try {
Expand Down
8 changes: 7 additions & 1 deletion packages/apidom-ls/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ export function logJson(label: string, message: unknown): void {
}

export function buildJsonPointer(path: string[]): string {
return `#/${path.join('/')}`;
const jsonPointer = path.map((s) => s.replaceAll('/', '~1')).join('/');
return `#/${jsonPointer}`;
}

interface FoundNode {
Expand Down Expand Up @@ -818,6 +819,7 @@ export async function findNamespace(
namespace: 'openapi',
version: versionMatch?.groups?.version_json,
format: 'JSON',
admitsRefsSiblings: true,
};
}
if (await openapi3_1AdapterYaml.detect(text)) {
Expand All @@ -826,6 +828,7 @@ export async function findNamespace(
namespace: 'openapi',
version: versionMatch?.groups?.version_yaml || versionMatch?.groups?.version_json,
format: 'YAML',
admitsRefsSiblings: true,
};
}
if (await adsAdapterJson.detect(text)) {
Expand All @@ -846,6 +849,7 @@ export async function findNamespace(
namespace: defaultContentLanguage.namespace,
version: defaultContentLanguage.version,
format: 'JSON',
admitsRefsSiblings: defaultContentLanguage.admitsRefsSiblings,
}
: {
namespace: 'apidom',
Expand All @@ -858,6 +862,7 @@ export async function findNamespace(
namespace: defaultContentLanguage.namespace,
version: defaultContentLanguage.version,
format: 'YAML',
admitsRefsSiblings: defaultContentLanguage.admitsRefsSiblings,
}
: {
namespace: 'apidom',
Expand All @@ -869,6 +874,7 @@ export async function findNamespace(
namespace: defaultContentLanguage.namespace,
version: defaultContentLanguage.version,
format: json ? 'JSON' : 'YAML',
admitsRefsSiblings: defaultContentLanguage.admitsRefsSiblings,
}
: {
namespace: 'apidom',
Expand Down
20 changes: 10 additions & 10 deletions packages/apidom-ls/test/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,11 @@ describe('apidom-ls-complete', function () {
},
},
{
label: '#/components/messages/userSignUp/headers/properties/appli...',
insertText:
"'#/components/messages/userSignUp/headers/properties/applicationInstanceId$1'",
label: '#/channels/user~1signin/subscribe/message/payload',
insertText: "'#/channels/user~1signin/subscribe/message/payload$1'",
kind: 18,
documentation:
'description: Unique identifier for a given instance of the publishing\n application\n type: string',
'type: object\n properties:\n user:\n $ref: "#/components/schemas/Category"',
insertTextFormat: 2,
sortText: 'i',
filterText: '"#/components/schemas/Category"',
Expand All @@ -527,16 +526,16 @@ describe('apidom-ls-complete', function () {
character: 51,
},
},
newText:
"'#/components/messages/userSignUp/headers/properties/applicationInstanceId$1'",
newText: "'#/channels/user~1signin/subscribe/message/payload$1'",
},
},
{
label: '#/channels/user/signin/subscribe/message/payload',
insertText: "'#/channels/user/signin/subscribe/message/payload$1'",
label: '#/components/messages/userSignUp/headers/properties/appli...',
insertText:
"'#/components/messages/userSignUp/headers/properties/applicationInstanceId$1'",
kind: 18,
documentation:
'type: object\n properties:\n user:\n $ref: "#/components/schemas/Category"',
'description: Unique identifier for a given instance of the publishing\n application\n type: string',
insertTextFormat: 2,
sortText: 'j',
filterText: '"#/components/schemas/Category"',
Expand All @@ -551,7 +550,8 @@ describe('apidom-ls-complete', function () {
character: 51,
},
},
newText: "'#/channels/user/signin/subscribe/message/payload$1'",
newText:
"'#/components/messages/userSignUp/headers/properties/applicationInstanceId$1'",
},
},
],
Expand Down
127 changes: 123 additions & 4 deletions packages/apidom-ls/test/completion-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,14 +478,134 @@ describe('apidom-ls-completion-provider', function () {
filterText: '',
},
{
label: '#/paths//users/get/responses/200/content/application/json...',
insertText: "'#/paths//users/get/responses/200/content/application/json/schema$1'",
label: 'http://example.com/#components/schemas/foo',
insertText: "'http://example.com/#components/schemas/foo$1'",
kind: 18,
insertTextFormat: 2,
sortText: 'ĭ',
filterText: '',
},
{
label: 'http://example.com/#components/schemas/bar',
insertText: "'http://example.com/#components/schemas/bar$1'",
kind: 18,
insertTextFormat: 2,
sortText: 'Į',
filterText: '',
},
] as CompletionItem[];

const resultAsync = await languageService.doCompletion(
docOpenapi,
{ line: 13, character: 24 },
completionContext,
);
assert.deepEqual(resultAsync!.items, expected as CompletionItem[]);
languageService.terminate();
languageService = getLanguageService(contextRef);
const result = await languageService.doCompletion(
docOpenapi,
{ line: 13, character: 24 },
completionContext,
);
assert.deepEqual(result!.items, expected as CompletionItem[]);
} finally {
languageService.terminate();
}
});

it('test completion ref provider with indirect', async function () {
const completionContext: CompletionContext = {
maxNumberOfItems: 100,
includeIndirectRefs: true,
};
let languageService: LanguageService = getLanguageService(contextAsyncRef);

try {
// valid spec
const docOpenapi: TextDocument = TextDocument.create(
'foo://bar/openapi.yaml',
'specOpenapi',
0,
specOpenapi,
);

const expected = [
{
label: '#/components/schemas/UserProfile',
insertText: "'#/components/schemas/UserProfile$1'",
kind: 18,
documentation: '"$ref":',
documentation:
'type: object\n properties:\n email:\n type: string\n x-nullable: true\n',
insertTextFormat: 2,
sortText: 'a',
filterText: '',
},
{
label: '#/components/schemas/User',
insertText: "'#/components/schemas/User$1'",
kind: 18,
documentation:
'type: object\n properties:\n id:\n type: integer\n name:\n type: string\n profile:\n "$ref": "#/components/schemas/UserProfile"\n summary: user profile reference summary\n description: user profile reference description\n profileExternalRef:\n "$ref": "http://example.com/test.yaml#/components/schemas/UserProfile"',
insertTextFormat: 2,
sortText: 'b',
filterText: '',
},
{
label: '#/components/schemas/UserProfile/properties/email',
insertText: "'#/components/schemas/UserProfile/properties/email$1'",
kind: 18,
documentation: 'type: string\n x-nullable: true\n',
insertTextFormat: 2,
sortText: 'c',
filterText: '',
},
{
label: '#/components/schemas/User/properties/profileExternalRef',
insertText: "'#/components/schemas/User/properties/profileExternalRef$1'",
kind: 18,
documentation: '"$ref": "http://example.com/test.yaml#/components/schemas/UserProfile"',
insertTextFormat: 2,
sortText: 'd',
filterText: '',
},
{
label: '#/components/schemas/User/properties/profile',
insertText: "'#/components/schemas/User/properties/profile$1'",
kind: 18,
documentation:
'"$ref": "#/components/schemas/UserProfile"\n summary: user profile reference summary\n description: user profile reference description',
insertTextFormat: 2,
sortText: 'e',
filterText: '',
},
{
label: '#/components/schemas/User/properties/name',
insertText: "'#/components/schemas/User/properties/name$1'",
kind: 18,
documentation: 'type: string',
insertTextFormat: 2,
sortText: 'f',
filterText: '',
},
{
label: '#/components/schemas/User/properties/id',
insertText: "'#/components/schemas/User/properties/id$1'",
kind: 18,
documentation: 'type: integer',
insertTextFormat: 2,
sortText: 'g',
filterText: '',
},
{
label: '#/paths/~1users/get/responses/201/content/application~1js...',
insertText: "'#/paths/~1users/get/responses/201/content/application~1json/schema$1'",
kind: 18,
documentation: '"$ref": "#/components/schemas/User"',
insertTextFormat: 2,
sortText: 'h',
filterText: '',
},
{
label: 'http://example.com/#components/schemas/foo',
insertText: "'http://example.com/#components/schemas/foo$1'",
Expand All @@ -509,7 +629,6 @@ describe('apidom-ls-completion-provider', function () {
{ line: 13, character: 24 },
completionContext,
);

assert.deepEqual(resultAsync!.items, expected as CompletionItem[]);
languageService.terminate();
languageService = getLanguageService(contextRef);
Expand Down

0 comments on commit b275efa

Please sign in to comment.