-
-
Notifications
You must be signed in to change notification settings - Fork 371
/
documentLinks.ts
172 lines (163 loc) · 6.09 KB
/
documentLinks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { fsPathToUri, notEmpty, uriToFsPath } from '@volar/shared';
import * as jsonc from 'jsonc-parser';
import * as upath from 'upath';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { DocumentLink, Range } from 'vscode-languageserver/node';
import type { SourceFile } from '../sourceFile';
import type { TsApiRegisterOptions } from '../types';
import * as sharedLs from '../utils/sharedLs';
export function register({ documentContext, sourceFiles, vueHost }: TsApiRegisterOptions) {
return async (uri: string) => {
const sourceFile = sourceFiles.get(uri);
if (!sourceFile) return;
const document = sourceFile.getTextDocument();
const tsResult = getTsResult(sourceFile);
const tsResult2 = getTsResult2(sourceFile);
const htmlResult = getHtmlResult(sourceFile);
const cssResult = await getCssResult(sourceFile);
return [
...cssResult,
...htmlResult,
...tsResult,
...tsResult2,
];
function getTsResult(sourceFile: SourceFile) {
let result: DocumentLink[] = [];
for (const sourceMap of sourceFile.getTsSourceMaps()) {
// TODO: move to vscode-typescript-languageservice
const scriptContent = sourceMap.mappedDocument.getText();
const root = jsonc.parseTree(scriptContent);
if (!root) continue;
const scriptDoc = TextDocument.create(uri, 'typescript', 0, scriptContent);
result = result.concat([
getExtendsLink(scriptDoc, root),
...getFilesLinks(scriptDoc, root),
...getReferencesLinks(scriptDoc, root)
].filter(notEmpty));
}
return result;
function getExtendsLink(document: TextDocument, root: jsonc.Node): DocumentLink | undefined {
const extendsNode = jsonc.findNodeAtLocation(root, ['extends']);
if (!isPathValue(extendsNode)) {
return undefined;
}
if (extendsNode.value.startsWith('.')) {
return DocumentLink.create(
getRange(document, extendsNode),
fsPathToUri(upath.join(upath.dirname(uriToFsPath(document.uri)), extendsNode.value + (extendsNode.value.endsWith('.json') ? '' : '.json')))
);
}
const workspaceFolderPath = vueHost.getCurrentDirectory();
return DocumentLink.create(
getRange(document, extendsNode),
fsPathToUri(upath.join(workspaceFolderPath, 'node_modules', extendsNode.value + (extendsNode.value.endsWith('.json') ? '' : '.json')))
);
}
function getFilesLinks(document: TextDocument, root: jsonc.Node) {
return mapChildren(
jsonc.findNodeAtLocation(root, ['files']),
child => pathNodeToLink(document, child));
}
function getReferencesLinks(document: TextDocument, root: jsonc.Node) {
return mapChildren(
jsonc.findNodeAtLocation(root, ['references']),
child => {
const pathNode = jsonc.findNodeAtLocation(child, ['path']);
if (!isPathValue(pathNode)) {
return undefined;
}
return DocumentLink.create(getRange(document, pathNode),
upath.basename(pathNode.value).endsWith('.json')
? getFileTarget(document, pathNode)
: getFolderTarget(document, pathNode));
});
}
function pathNodeToLink(
document: TextDocument,
node: jsonc.Node | undefined
): DocumentLink | undefined {
return isPathValue(node)
? DocumentLink.create(getRange(document, node), getFileTarget(document, node))
: undefined;
}
function isPathValue(extendsNode: jsonc.Node | undefined): extendsNode is jsonc.Node {
return extendsNode
&& extendsNode.type === 'string'
&& extendsNode.value
&& !(extendsNode.value as string).includes('*'); // don't treat globs as links.
}
function getFileTarget(document: TextDocument, node: jsonc.Node): string {
return fsPathToUri(upath.join(upath.dirname(uriToFsPath(document.uri)), node!.value));
}
function getFolderTarget(document: TextDocument, node: jsonc.Node): string {
return fsPathToUri(upath.join(upath.dirname(uriToFsPath(document.uri)), node!.value, 'tsconfig.json'));
}
function getRange(document: TextDocument, node: jsonc.Node) {
const offset = node!.offset;
const start = document.positionAt(offset + 1);
const end = document.positionAt(offset + (node!.length - 1));
return Range.create(start, end);
}
function mapChildren<R>(node: jsonc.Node | undefined, f: (x: jsonc.Node) => R): R[] {
return node && node.type === 'array' && node.children
? node.children.map(f)
: [];
}
}
function getTsResult2(sourceFile: SourceFile) {
const result: DocumentLink[] = [];
for (const sourceMap of sourceFile.getTsSourceMaps()) {
for (const maped of sourceMap) {
if (!maped.data.capabilities.displayWithLink) {
continue;
}
result.push({
range: {
start: document.positionAt(maped.sourceRange.start),
end: document.positionAt(maped.sourceRange.end),
},
target: uri, // TODO
});
}
}
return result;
}
function getHtmlResult(sourceFile: SourceFile) {
const result: DocumentLink[] = [];
for (const sourceMap of [...sourceFile.getHtmlSourceMaps(), ...sourceFile.getPugSourceMaps()]) {
const links = sourceMap.language === 'html'
? sharedLs.htmlLs.findDocumentLinks(sourceMap.mappedDocument, documentContext)
: sharedLs.pugLs.findDocumentLinks(sourceMap.pugDocument, documentContext)
for (const link of links) {
const vueRange = sourceMap.getSourceRange(link.range.start, link.range.end);
if (vueRange) {
result.push({
...link,
range: vueRange,
});
}
}
}
return result;
}
async function getCssResult(sourceFile: SourceFile) {
const sourceMaps = sourceFile.getCssSourceMaps();
const result: DocumentLink[] = [];
for (const sourceMap of sourceMaps) {
const cssLs = sharedLs.getCssLs(sourceMap.mappedDocument.languageId);
if (!cssLs || !sourceMap.stylesheet) continue;
const links = await cssLs.findDocumentLinks2(sourceMap.mappedDocument, sourceMap.stylesheet, documentContext);
for (const link of links) {
const vueRange = sourceMap.getSourceRange(link.range.start, link.range.end);
if (vueRange) {
result.push({
...link,
range: vueRange,
});
}
}
}
return result;
}
}
}