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

feat(nxls): add namedInputs target links & fix namedInputs completion in nx.json #2368

Merged
merged 5 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { join } from 'path';
import { NxlsWrapper } from '../nxls-wrapper';
import {
e2eCwd,
modifyJsonFile,
newWorkspace,
simpleReactWorkspaceOptions,
uniq,
} from '../utils';
import { NxWorkspaceRefreshNotification } from '@nx-console/language-server/types';
import { readFileSync } from 'fs';
import { URI } from 'vscode-uri';
import { Position } from 'vscode-languageserver';
import { fileURLToPath } from 'url';

let nxlsWrapper: NxlsWrapper;
const workspaceName = uniq('workspace');

const projectJsonPath = join(
e2eCwd,
workspaceName,
'apps',
workspaceName,
'project.json'
);

describe('namedInput link completion - default', () => {
beforeAll(async () => {
newWorkspace({
name: workspaceName,
options: {
preset: 'next',
},
});
nxlsWrapper = new NxlsWrapper(true);
await nxlsWrapper.startNxls(join(e2eCwd, workspaceName));

nxlsWrapper.sendNotification({
method: 'textDocument/didOpen',
params: {
textDocument: {
uri: URI.file(projectJsonPath).toString(),
languageId: 'JSON',
version: 1,
text: readFileSync(projectJsonPath, 'utf-8'),
},
},
});
});
afterAll(async () => {
await nxlsWrapper.stopNxls();
});
describe('named input links', () => {
it('should return correct target link for input if it is a namedInput in nx.json', async () => {
modifyJsonFile(projectJsonPath, (data) => ({
...data,
targets: {
build: {
inputs: ['default'],
},
},
}));

nxlsWrapper.sendNotification({
method: 'textDocument/didChange',
params: {
textDocument: {
uri: URI.file(projectJsonPath).toString(),
languageId: 'JSON',
version: 2,
},
contentChanges: [
{
text: readFileSync(projectJsonPath, 'utf-8'),
},
],
},
});

const linkResponse = await nxlsWrapper.sendRequest({
method: 'textDocument/documentLink',
params: {
textDocument: {
uri: URI.file(projectJsonPath).toString(),
},
position: Position.create(0, 1),
},
});

const targetLink = (linkResponse.result as any[])[0].target;
// line 4 is where the `default` namedInput is defined in nx.json
expect(targetLink).toMatch(new RegExp(`#4$`));
expect(decodeURI(targetLink)).toContain(join(workspaceName, 'nx.json'));
});

it('should not return target link for input if it is not a namedInput in nx.json', async () => {
modifyJsonFile(projectJsonPath, (data) => ({
...data,
targets: {
build: {
inputs: ['src/file.js', 'other'],
},
},
}));

nxlsWrapper.sendNotification({
method: 'textDocument/didChange',
params: {
textDocument: {
uri: URI.file(projectJsonPath).toString(),
languageId: 'JSON',
version: 2,
},
contentChanges: [
{
text: readFileSync(projectJsonPath, 'utf-8'),
},
],
},
});

const linkResponse = await nxlsWrapper.sendRequest({
method: 'textDocument/documentLink',
params: {
textDocument: {
uri: URI.file(projectJsonPath).toString(),
},
position: Position.create(0, 1),
},
});

const targetLinks = linkResponse.result as any[];
expect(targetLinks.length).toBe(0);
});

it('should return correct target link for named input within nx.json', async () => {
const nxJsonPath = join(e2eCwd, workspaceName, 'nx.json');
modifyJsonFile(nxJsonPath, (data) => ({
...data,
namedInputs: {
default: ['one', 'two'],
one: ['src/file.js'],
},
}));

nxlsWrapper.sendNotification({
method: 'textDocument/didOpen',
params: {
textDocument: {
uri: URI.file(nxJsonPath).toString(),
languageId: 'JSON',
version: 0,
text: readFileSync(nxJsonPath, 'utf-8'),
},
},
});

const linkResponse = await nxlsWrapper.sendRequest({
method: 'textDocument/documentLink',
params: {
textDocument: {
uri: URI.file(nxJsonPath).toString(),
},
position: Position.create(0, 1),
},
});

const targetLink = (linkResponse.result as any[])[0].target;
// line 8 is where the `one` namedInput is defined in nx.json with default formatting
expect(targetLink).toMatch(new RegExp(`#8$`));
expect(decodeURI(targetLink)).toContain(join(workspaceName, 'nx.json'));
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getDefaultCompletionType,
isArrayNode,
lspLogger,
} from '@nx-console/language-server/utils';
import {
CompletionType,
Expand Down Expand Up @@ -132,12 +133,15 @@ function completionItems(
return targetsCompletion(workingPath, node, document, true);
}
case CompletionType.inputName: {
lspLogger.log(`inputName completion ${node.value}`);
return inputNameCompletion(workingPath, node, document);
}
case CompletionType.inputNameWithDeps: {
lspLogger.log(`inputNameWithDeps completion ${node.value}`);
return inputNameCompletion(workingPath, node, document, true);
}
case CompletionType.inferencePlugins: {
lspLogger.log(`inferencePlugins completion ${node.value}`);
return inferencePluginsCompletion(workingPath);
}
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'vscode-json-languageservice';
import { createRange } from './create-range';
import { targetLink } from './target-link';
import { namedInputLink } from './named-input-link';

export async function getDocumentLinks(
workingPath: string | undefined,
Expand Down Expand Up @@ -78,6 +79,14 @@ export async function getDocumentLinks(
}
break;
}
case CompletionType.inputName:
case CompletionType.inputNameWithDeps: {
const link = await namedInputLink(workingPath, node);
if (link) {
links.push(DocumentLink.create(range, link));
}
break;
}
case 'projectTarget': {
const link = await targetLink(workingPath, node);
if (link) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
findProperty,
getLanguageModelCache,
} from '@nx-console/language-server/utils';
import { readNxJson } from '@nx-console/shared/npm';
import { readFileSync } from 'fs';
import { join } from 'path';
import {
ASTNode,
JSONDocument,
Range,
TextDocument,
} from 'vscode-json-languageservice';
import { URI } from 'vscode-uri';
import { createRange } from './create-range';

let versionNumber = 0;

export async function namedInputLink(
workingPath: string,
node: ASTNode
): Promise<string | undefined> {
const nxJson = await readNxJson(workingPath);

const namedInput = Object.keys(nxJson.namedInputs ?? {}).find(
(input) => input === node.value
);

if (!namedInput) {
return;
}

const nxJsonPath = join(workingPath, 'nx.json');

const nxJsonContent = readFileSync(join(workingPath, 'nx.json'), 'utf8');

const languageModelCache = getLanguageModelCache();
const { document, jsonAst } = languageModelCache.retrieve(
TextDocument.create(nxJsonPath, 'json', versionNumber, nxJsonContent),
false
);
languageModelCache.dispose();
versionNumber++;

const range = findNamedInputRange(document, jsonAst, namedInput);

if (!range) {
return;
}

return URI.from({
scheme: 'file',
path: nxJsonPath,
fragment: `${range.start.line + 1}`,
}).toString();
}

function findNamedInputRange(
document: TextDocument,
jsonAst: JSONDocument,
namedInput: string
): Range | undefined {
if (!jsonAst.root) {
return;
}

const namedInputNode = findProperty(jsonAst.root, 'namedInputs');

if (!namedInputNode) {
return;
}

const namedInputProperty = findProperty(namedInputNode, namedInput);

if (!namedInputProperty) {
return;
}

return createRange(document, namedInputProperty);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import {
isStringNode,
lspLogger,
} from '@nx-console/language-server/utils';
import {
getNxVersion,
nxWorkspace,
} from '@nx-console/language-server/workspace';
import { nxWorkspace } from '@nx-console/language-server/workspace';
import { fileExists, readFile } from '@nx-console/shared/file-system';
import { parseTargetString } from '@nx-console/shared/utils';
import { join } from 'path';
Expand Down
5 changes: 4 additions & 1 deletion libs/shared/json-schema/src/lib/common-json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ export const inputs = (nxVersion: NxVersion): JSONSchema[] => [
export const namedInputs = (nxVersion: NxVersion): JSONSchema => ({
type: 'object',
additionalProperties: {
oneOf: inputs(nxVersion),
type: 'array',
items: {
oneOf: inputs(nxVersion),
},
},
});

Expand Down
3 changes: 2 additions & 1 deletion libs/shared/json-schema/src/lib/nx-json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
ProjectGraphProjectNode,
} from 'nx/src/devkit-exports';
import type { JSONSchema } from 'vscode-json-languageservice';
import { targets } from './common-json-schema';
import { namedInputs, targets } from './common-json-schema';
import { CompletionType } from './completion-type';
import { createBuildersAndExecutorsSchema } from './create-builders-and-executors-schema';
import { NxVersion } from '@nx-console/shared/nx-version';
Expand Down Expand Up @@ -105,6 +105,7 @@ function createJsonSchema(
},
},
},
namedInputs: namedInputs(nxVersion),
},
};
}
Expand Down
Loading