Skip to content
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
208 changes: 208 additions & 0 deletions apps/nxls-e2e/src/document-links/interpolated-path-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import {
e2eCwd,
modifyJsonFile,
newWorkspace,
uniq,
} from '@nx-console/shared-e2e-utils';
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { Position } from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { NxlsWrapper } from '../nxls-wrapper';

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

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

describe('interpolated path links', () => {
beforeAll(async () => {
newWorkspace({
name: workspaceName,
options: {
preset: 'next',
},
});
writeFileSync(
projectJsonPath,
JSON.stringify(
{
root: `apps/${workspaceName}`,
targets: {
build: {
inputs: ['{workspaceRoot}/nx.json', '{projectRoot}/project.json'],
},
},
},
null,
2,
),
);
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();
});

it('should return correct links for {workspaceRoot} and {projectRoot}', async () => {
const text = readFileSync(projectJsonPath, 'utf-8');
const lines = text.split('\n');

// Check workspace link
const workspaceLine = lines.findIndex((line) =>
line.includes('{workspaceRoot}/nx.json'),
);
const workspaceChar = lines[workspaceLine].indexOf(
'{workspaceRoot}/nx.json',
);

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

const workspaceLinks = workspaceLinkResponse.result as any[];
const workspaceLink = workspaceLinks.find(
(l) => l.target && l.target.endsWith('nx.json'),
);
expect(workspaceLink).toBeDefined();
expect(decodeURI(workspaceLink.target)).toContain(
join(workspaceName, 'nx.json'),
);

// Check project link
const projectLine = lines.findIndex((line) =>
line.includes('{projectRoot}/project.json'),
);
const projectChar = lines[projectLine].indexOf(
'{projectRoot}/project.json',
);

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

const projectLinks = projectLinkResponse.result as any[];
const projectLink = projectLinks.find(
(l) => l.target && l.target.endsWith('project.json'),
);
expect(projectLink).toBeDefined();
expect(decodeURI(projectLink.target)).toContain(
join(workspaceName, 'apps', workspaceName, 'project.json'),
);
});

it('should return correct links for negated {workspaceRoot} and {projectRoot}', async () => {
modifyJsonFile(projectJsonPath, (data) => ({
...data,
targets: {
build: {
inputs: ['!{workspaceRoot}/nx.json', '!{projectRoot}/project.json'],
},
},
}));

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

const text = readFileSync(projectJsonPath, 'utf-8');
const lines = text.split('\n');

// Check workspace link
const workspaceLine = lines.findIndex((line) =>
line.includes('!{workspaceRoot}/nx.json'),
);
const workspaceChar = lines[workspaceLine].indexOf(
'!{workspaceRoot}/nx.json',
);

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

const workspaceLinks = workspaceLinkResponse.result as any[];
const workspaceLink = workspaceLinks.find(
(l) => l.target && l.target.endsWith('nx.json'),
);
expect(workspaceLink).toBeDefined();
expect(decodeURI(workspaceLink.target)).toContain(
join(workspaceName, 'nx.json'),
);

// Check project link
const projectLine = lines.findIndex((line) =>
line.includes('!{projectRoot}/project.json'),
);
const projectChar = lines[projectLine].indexOf(
'!{projectRoot}/project.json',
);

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

const projectLinks = projectLinkResponse.result as any[];
const projectLink = projectLinks.find(
(l) => l.target && l.target.endsWith('project.json'),
);
expect(projectLink).toBeDefined();
expect(decodeURI(projectLink.target)).toContain(
join(workspaceName, 'apps', workspaceName, 'project.json'),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,93 @@ it('should get all document links for properties that have a X_COMPLETION_TYPE (
documentMapper.dispose();
});

it('should get document links for interpolated paths', async () => {
const { document, jsonAst } = documentMapper.retrieve(
TextDocument.create(
'file:///project.json',
'json',
0,
`
{
"root": "apps/my-app",
"interpolatedWorkspace": "{workspaceRoot}/libs/my-lib/src/index.ts",
"interpolatedProject": "{projectRoot}/src/main.ts",
"interpolatedGlob": "{projectRoot}/**/*.ts"
}
`,
),
);

const matchingSchemas = await languageService.getMatchingSchemas(
document,
jsonAst,
{
type: 'object',
properties: {
interpolatedWorkspace: { type: 'string' },
interpolatedProject: { type: 'string' },
interpolatedGlob: { type: 'string' },
},
},
);

const documentLinks = await getDocumentLinks(
'/workspace',
jsonAst,
document,
matchingSchemas,
);

expect(documentLinks.map((link) => link.target)).toEqual([
'file:///workspace/libs/my-lib/src/index.ts',
'file:///workspace/apps/my-app/src/main.ts',
]);

documentMapper.dispose();
});

it('should get document links for negated interpolated paths', async () => {
const { document, jsonAst } = documentMapper.retrieve(
TextDocument.create(
'file:///project.json',
'json',
0,
`
{
"interpolatedWorkspace": "!{workspaceRoot}/libs/my-lib/src/index.ts",
"interpolatedProject": "!{projectRoot}/src/main.ts"
}
`,
),
);

const matchingSchemas = await languageService.getMatchingSchemas(
document,
jsonAst,
{
type: 'object',
properties: {
interpolatedWorkspace: { type: 'string' },
interpolatedProject: { type: 'string' },
},
},
);

const documentLinks = await getDocumentLinks(
'/workspace',
jsonAst,
document,
matchingSchemas,
);

expect(documentLinks.map((link) => link.target)).toEqual([
'file:///workspace/libs/my-lib/src/index.ts',
'file:///workspace/apps/my-app/src/main.ts',
]);

documentMapper.dispose();
});

describe('project links', () => {
const mockProjectGraph = {
nodes: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { createRange } from './create-range';
import { targetLink } from './target-link';
import { namedInputLink } from './named-input-link';
import { projectLink } from './project-link';
import { interpolatedPathLink } from './interpolated-path-link';

export async function getDocumentLinks(
workingPath: string | undefined,
Expand Down Expand Up @@ -50,6 +51,21 @@ export async function getDocumentLinks(
}

if (!linkType) {
if (isStringNode(node)) {
const value = node.value;
if (
(value.startsWith('{workspaceRoot}') ||
value.startsWith('{projectRoot}') ||
value.startsWith('!{workspaceRoot}') ||
value.startsWith('!{projectRoot}')) &&
!value.includes('*')
) {
const link = await interpolatedPathLink(workingPath, node);
if (link) {
links.push(DocumentLink.create(createRange(document, node), link));
}
}
}
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
findProjectRoot,
isStringNode,
lspLogger,
} from '@nx-console/language-server-utils';
import { join } from 'path';
import { ASTNode } from 'vscode-json-languageservice';
import { URI } from 'vscode-uri';

export async function interpolatedPathLink(
workingPath: string,
node: ASTNode,
): Promise<string | undefined> {
if (!isStringNode(node)) {
return;
}

let value = node.value;
if (value.startsWith('!')) {
value = value.substring(1);
}

let path: string | undefined;

if (value.startsWith('{workspaceRoot}')) {
path = value.replace('{workspaceRoot}', workingPath);
} else if (value.startsWith('{projectRoot}')) {
const projectRoot = findProjectRoot(node);
if (projectRoot) {
path = value.replace('{projectRoot}', join(workingPath, projectRoot));
}
}

if (path) {
return URI.from({
scheme: 'file',
path: path,
}).toString();
}

return;
}