@esportsplus/typescript 0.31.0 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/bin/tsc-lsp +3 -0
- package/build/cli/diagnostics.d.ts +5 -1
- package/build/cli/diagnostics.js +13 -8
- package/build/cli/tsc.d.ts +3 -1
- package/build/cli/tsc.js +77 -87
- package/build/compiler/coordinator.d.ts +1 -2
- package/build/compiler/coordinator.js +33 -22
- package/build/compiler/imports.d.ts +2 -0
- package/build/compiler/imports.js +9 -2
- package/build/compiler/language-service.d.ts +15 -4
- package/build/compiler/language-service.js +56 -24
- package/build/compiler/plugins/vite.js +7 -5
- package/build/compiler/sourcemap.d.ts +3 -1
- package/build/compiler/sourcemap.js +23 -5
- package/build/jsonc.d.ts +2 -0
- package/build/jsonc.js +85 -0
- package/build/lsp/bin.d.ts +1 -0
- package/build/lsp/bin.js +2 -0
- package/build/lsp/diagnostics.d.ts +10 -0
- package/build/lsp/diagnostics.js +69 -0
- package/build/lsp/index.d.ts +2 -0
- package/build/lsp/index.js +2 -0
- package/build/lsp/server.d.ts +4 -0
- package/build/lsp/server.js +130 -0
- package/build/lsp/workspace.d.ts +14 -0
- package/build/lsp/workspace.js +46 -0
- package/build/probe/adapter.d.ts +14 -0
- package/build/probe/adapter.js +42 -0
- package/build/probe/async/channel.d.ts +4 -0
- package/build/probe/async/channel.js +560 -0
- package/build/probe/async/value.d.ts +9 -0
- package/build/probe/async/value.js +16 -0
- package/build/probe/channels.d.ts +4 -0
- package/build/probe/channels.js +16 -0
- package/build/probe/exceptions/channel.d.ts +5 -0
- package/build/probe/exceptions/channel.js +657 -0
- package/build/probe/exceptions/jsdoc.d.ts +8 -0
- package/build/probe/exceptions/jsdoc.js +34 -0
- package/build/probe/exceptions/value.d.ts +39 -0
- package/build/probe/exceptions/value.js +158 -0
- package/build/probe/kernel/analyze.d.ts +8 -0
- package/build/probe/kernel/analyze.js +68 -0
- package/build/probe/kernel/ast.d.ts +11 -0
- package/build/probe/kernel/ast.js +49 -0
- package/build/probe/kernel/config.d.ts +5 -0
- package/build/probe/kernel/config.js +209 -0
- package/build/probe/kernel/fixpoint.d.ts +6 -0
- package/build/probe/kernel/fixpoint.js +206 -0
- package/build/probe/kernel/format.d.ts +4 -0
- package/build/probe/kernel/format.js +32 -0
- package/build/probe/kernel/graph.d.ts +4 -0
- package/build/probe/kernel/graph.js +507 -0
- package/build/probe/kernel/ids.d.ts +8 -0
- package/build/probe/kernel/ids.js +66 -0
- package/build/probe/kernel/program.d.ts +8 -0
- package/build/probe/kernel/program.js +13 -0
- package/build/probe/kernel/types.d.ts +134 -0
- package/build/probe/kernel/types.js +1 -0
- package/build/probe/overlay/base/async.jsonc +13 -0
- package/build/probe/overlay/base/exceptions.jsonc +54 -0
- package/build/probe/overlay/base/resources.jsonc +57 -0
- package/build/probe/overlay/load.d.ts +18 -0
- package/build/probe/overlay/load.js +302 -0
- package/build/probe/overlay/presets/express.jsonc +25 -0
- package/build/probe/overlay/presets/node.jsonc +17 -0
- package/build/probe/resources/channel.d.ts +4 -0
- package/build/probe/resources/channel.js +798 -0
- package/build/probe/resources/value.d.ts +9 -0
- package/build/probe/resources/value.js +36 -0
- package/build/tsconfig.d.ts +2 -0
- package/build/tsconfig.js +150 -0
- package/package.json +10 -4
- package/tsconfig.base.json +19 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SourceFile } from 'typescript/unstable/ast';
|
|
2
|
-
import
|
|
2
|
+
import type { FileSystem } from 'typescript/unstable/fs';
|
|
3
|
+
import { API, type Checker, type Program, type Project, type Snapshot } from 'typescript/unstable/sync';
|
|
3
4
|
type ScratchResult = {
|
|
4
5
|
checker: Checker;
|
|
5
6
|
program: Program;
|
|
@@ -9,20 +10,30 @@ type UpdateResult = {
|
|
|
9
10
|
checker: Checker;
|
|
10
11
|
program: Program;
|
|
11
12
|
};
|
|
13
|
+
type OpenProject = {
|
|
14
|
+
api: API;
|
|
15
|
+
dispose: () => void;
|
|
16
|
+
project: Project;
|
|
17
|
+
snapshot: Snapshot;
|
|
18
|
+
};
|
|
19
|
+
declare function open(configPath: string, overlay?: FileSystem): OpenProject;
|
|
12
20
|
declare const dispose: (root?: string) => void;
|
|
13
21
|
declare const findConfig: (startDir: string) => string | null;
|
|
14
|
-
declare const invalidate: (
|
|
22
|
+
declare const invalidate: (configFileName: string, fileName: string) => void;
|
|
15
23
|
declare const parse: (fileName: string, content: string) => SourceFile;
|
|
16
24
|
declare const scratch: (fileName: string, content: string) => ScratchResult;
|
|
17
|
-
declare const update: (
|
|
25
|
+
declare const update: (configFileName: string, fileName: string, content: string) => UpdateResult;
|
|
26
|
+
declare const updateMany: (configFileName: string, updates: Map<string, string>) => UpdateResult;
|
|
18
27
|
declare const _default: {
|
|
19
28
|
dispose: typeof dispose;
|
|
20
29
|
findConfig: typeof findConfig;
|
|
21
30
|
invalidate: typeof invalidate;
|
|
31
|
+
open: typeof open;
|
|
22
32
|
parse: typeof parse;
|
|
23
33
|
scratch: typeof scratch;
|
|
24
34
|
update: typeof update;
|
|
35
|
+
updateMany: typeof updateMany;
|
|
25
36
|
};
|
|
26
37
|
export default _default;
|
|
27
|
-
export { dispose, findConfig, invalidate, parse, scratch, update };
|
|
38
|
+
export { dispose, findConfig, invalidate, open, parse, scratch, update, updateMany };
|
|
28
39
|
export type { ScratchResult, UpdateResult };
|
|
@@ -31,12 +31,9 @@ function advanceScratch(id, content) {
|
|
|
31
31
|
entry.snapshot = snapshot;
|
|
32
32
|
return entry;
|
|
33
33
|
}
|
|
34
|
-
function createEntry(
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
throw new Error(`${PACKAGE_NAME}: tsconfig.json not found`);
|
|
38
|
-
}
|
|
39
|
-
let contents = new Map(), api = new API({ cwd: root, fs: overlayFileSystem(contents) }), snapshot = api.updateSnapshot({ openProjects: [configFileName] }), project = resolveProject(snapshot, configFileName);
|
|
34
|
+
function createEntry(configFileName) {
|
|
35
|
+
let root = path.dirname(configFileName);
|
|
36
|
+
let contents = new Map(), opened = open(configFileName, overlayFileSystem(contents)), { api, project, snapshot } = opened;
|
|
40
37
|
for (let diagnostic of project.program.getConfigFileParsingDiagnostics()) {
|
|
41
38
|
if (diagnostic.category === DiagnosticCategory.Error) {
|
|
42
39
|
snapshot.dispose();
|
|
@@ -64,17 +61,39 @@ function disposeScratch(entry) {
|
|
|
64
61
|
}
|
|
65
62
|
entry.api.close();
|
|
66
63
|
}
|
|
67
|
-
function getEntry(
|
|
68
|
-
let entry = cache.get(
|
|
64
|
+
function getEntry(configFileName) {
|
|
65
|
+
let entry = cache.get(configFileName);
|
|
69
66
|
if (!entry) {
|
|
70
|
-
entry = createEntry(
|
|
71
|
-
cache.set(
|
|
67
|
+
entry = createEntry(configFileName);
|
|
68
|
+
cache.set(configFileName, entry);
|
|
72
69
|
}
|
|
73
70
|
return entry;
|
|
74
71
|
}
|
|
75
72
|
function normalize(fileName) {
|
|
76
73
|
return fileName.replace(backslashes, '/');
|
|
77
74
|
}
|
|
75
|
+
function open(configPath, overlay) {
|
|
76
|
+
let configFileName = normalize(path.resolve(configPath)), entry = overlay === undefined ? getEntry(configFileName) : undefined, api = entry?.api ?? new API({ cwd: path.dirname(configFileName), fs: overlay }), snapshot = entry?.snapshot ?? api.updateSnapshot({ openProjects: [configFileName] }), project = entry?.project ?? resolveProject(snapshot, configFileName);
|
|
77
|
+
if (entry) {
|
|
78
|
+
return {
|
|
79
|
+
api,
|
|
80
|
+
dispose: () => dispose(configFileName),
|
|
81
|
+
project,
|
|
82
|
+
snapshot
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
api,
|
|
87
|
+
dispose: () => {
|
|
88
|
+
if (!snapshot.isDisposed()) {
|
|
89
|
+
snapshot.dispose();
|
|
90
|
+
}
|
|
91
|
+
api.close();
|
|
92
|
+
},
|
|
93
|
+
project,
|
|
94
|
+
snapshot,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
78
97
|
function overlayFileSystem(contents) {
|
|
79
98
|
return {
|
|
80
99
|
fileExists: (fileName) => {
|
|
@@ -101,7 +120,7 @@ function overlayFileSystem(contents) {
|
|
|
101
120
|
}
|
|
102
121
|
catch {
|
|
103
122
|
}
|
|
104
|
-
return { directories, files: [...files, ...extra] };
|
|
123
|
+
return { directories, files: [...new Set([...files, ...extra])] };
|
|
105
124
|
},
|
|
106
125
|
readFile: (fileName) => {
|
|
107
126
|
let content = contents.get(normalize(fileName));
|
|
@@ -192,8 +211,8 @@ const findConfig = (startDir) => {
|
|
|
192
211
|
current = parent;
|
|
193
212
|
}
|
|
194
213
|
};
|
|
195
|
-
const invalidate = (
|
|
196
|
-
let entry = cache.get(
|
|
214
|
+
const invalidate = (configFileName, fileName) => {
|
|
215
|
+
let entry = cache.get(configFileName);
|
|
197
216
|
if (!entry) {
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
@@ -211,11 +230,20 @@ const scratch = (fileName, content) => {
|
|
|
211
230
|
}
|
|
212
231
|
return { checker: entry.project.checker, program: entry.project.program, sourceFile: source };
|
|
213
232
|
};
|
|
214
|
-
const update = (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
const update = (configFileName, fileName, content) => {
|
|
234
|
+
return updateMany(configFileName, new Map([[fileName, content]]));
|
|
235
|
+
};
|
|
236
|
+
const updateMany = (configFileName, updates) => {
|
|
237
|
+
let entry = getEntry(configFileName), ids = [];
|
|
238
|
+
for (let [fileName, content] of updates) {
|
|
239
|
+
let id = normalize(fileName);
|
|
240
|
+
entry.contents.set(id, content);
|
|
241
|
+
ids.push(id);
|
|
242
|
+
}
|
|
243
|
+
if (ids.every(id => entry.seen.has(id))) {
|
|
244
|
+
for (let i = 0, n = ids.length; i < n; i++) {
|
|
245
|
+
entry.pending.add(ids[i]);
|
|
246
|
+
}
|
|
219
247
|
let changed = [...entry.pending];
|
|
220
248
|
entry.pending.clear();
|
|
221
249
|
advance(entry, changed);
|
|
@@ -223,13 +251,17 @@ const update = (root, fileName, content) => {
|
|
|
223
251
|
else {
|
|
224
252
|
recreate(entry);
|
|
225
253
|
entry.pending.clear();
|
|
226
|
-
|
|
254
|
+
for (let i = 0, n = ids.length; i < n; i++) {
|
|
255
|
+
entry.seen.add(ids[i]);
|
|
256
|
+
}
|
|
227
257
|
}
|
|
228
|
-
let
|
|
229
|
-
|
|
230
|
-
|
|
258
|
+
for (let i = 0, n = ids.length; i < n; i++) {
|
|
259
|
+
let id = ids[i], source = entry.project.program.getSourceFile(id), content = entry.contents.get(id);
|
|
260
|
+
if (!source || source.text !== content) {
|
|
261
|
+
throw new Error(`${PACKAGE_NAME}: failed to load ${id} into the program`);
|
|
262
|
+
}
|
|
231
263
|
}
|
|
232
264
|
return { checker: entry.project.checker, program: entry.project.program };
|
|
233
265
|
};
|
|
234
|
-
export default { dispose, findConfig, invalidate, parse, scratch, update };
|
|
235
|
-
export { dispose, findConfig, invalidate, parse, scratch, update };
|
|
266
|
+
export default { dispose, findConfig, invalidate, open, parse, scratch, update, updateMany };
|
|
267
|
+
export { dispose, findConfig, invalidate, open, parse, scratch, update, updateMany };
|
|
@@ -6,17 +6,19 @@ const FILE_REGEX = /\.[tj]sx?$/;
|
|
|
6
6
|
let contexts = new Map();
|
|
7
7
|
export default ({ name, onWatchChange, plugins }) => {
|
|
8
8
|
return ({ root } = {}) => {
|
|
9
|
+
let tsconfig = null;
|
|
9
10
|
return {
|
|
10
11
|
closeBundle() {
|
|
11
|
-
languageService.dispose(
|
|
12
|
+
languageService.dispose(tsconfig ?? '');
|
|
12
13
|
contexts.delete(root || '');
|
|
13
14
|
},
|
|
14
15
|
closeWatcher() {
|
|
15
|
-
languageService.dispose(
|
|
16
|
+
languageService.dispose(tsconfig ?? '');
|
|
16
17
|
contexts.delete(root || '');
|
|
17
18
|
},
|
|
18
19
|
configResolved(config) {
|
|
19
20
|
root ??= config.root;
|
|
21
|
+
tsconfig = languageService.findConfig(root);
|
|
20
22
|
},
|
|
21
23
|
enforce: 'pre',
|
|
22
24
|
name: `${name}/compiler/vite`,
|
|
@@ -25,13 +27,13 @@ export default ({ name, onWatchChange, plugins }) => {
|
|
|
25
27
|
return null;
|
|
26
28
|
}
|
|
27
29
|
try {
|
|
28
|
-
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'), { checker, program } = languageService.update(
|
|
30
|
+
let normalizedId = id.replace(DIRECTORY_SEPARATOR_REGEX, '/'), configPath = tsconfig ?? languageService.findConfig(root || ''), { checker, program } = languageService.update(configPath ?? '', normalizedId, code), sourceFile = program.getSourceFile(normalizedId) ?? languageService.parse(normalizedId, code);
|
|
29
31
|
let key = root || '', ctx = contexts.get(key);
|
|
30
32
|
if (!ctx) {
|
|
31
33
|
ctx = new Map();
|
|
32
34
|
contexts.set(key, ctx);
|
|
33
35
|
}
|
|
34
|
-
let result = coordinator.transform(plugins, code, sourceFile, { checker, program }, key, ctx);
|
|
36
|
+
let result = coordinator.transform(plugins, code, sourceFile, { checker, configPath: configPath ?? undefined, program }, key, ctx);
|
|
35
37
|
if (!result.changed) {
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
@@ -46,7 +48,7 @@ export default ({ name, onWatchChange, plugins }) => {
|
|
|
46
48
|
if (FILE_REGEX.test(id)) {
|
|
47
49
|
onWatchChange?.();
|
|
48
50
|
contexts.delete(root || '');
|
|
49
|
-
languageService.invalidate(
|
|
51
|
+
languageService.invalidate(tsconfig ?? '', id);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
};
|
|
@@ -28,6 +28,7 @@ type SourceMapV3 = {
|
|
|
28
28
|
sourcesContent?: (string | null)[];
|
|
29
29
|
version: 3;
|
|
30
30
|
};
|
|
31
|
+
declare function resolveOffset(mapping: PositionMapping, offset: number): number;
|
|
31
32
|
declare const buildGeneration: (beforeText: string, edits: Edit[]) => OffsetAnchor[];
|
|
32
33
|
declare const composeEmittedMap: (map: SourceMapV3, mapping: PositionMapping, transformedText: string, originalText: string) => SourceMapV3;
|
|
33
34
|
declare const decode: (mappings: string) => number[][][];
|
|
@@ -43,8 +44,9 @@ declare const _default: {
|
|
|
43
44
|
decode: typeof decode;
|
|
44
45
|
encode: typeof encode;
|
|
45
46
|
originalPositionFor: typeof originalPositionFor;
|
|
47
|
+
resolveOffset: typeof resolveOffset;
|
|
46
48
|
toSourceMapV3: typeof toSourceMapV3;
|
|
47
49
|
};
|
|
48
50
|
export default _default;
|
|
49
|
-
export { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, toSourceMapV3 };
|
|
51
|
+
export { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, resolveOffset, toSourceMapV3 };
|
|
50
52
|
export type { Edit, OffsetAnchor, PositionMapping, Segment, SourceMapV3 };
|
|
@@ -115,6 +115,24 @@ function resolveOffset(mapping, offset) {
|
|
|
115
115
|
}
|
|
116
116
|
return result;
|
|
117
117
|
}
|
|
118
|
+
function resolveOffsets(mapping) {
|
|
119
|
+
const cursors = mapping.generations.map(() => 0);
|
|
120
|
+
return (offset) => {
|
|
121
|
+
let result = offset;
|
|
122
|
+
for (let i = mapping.generations.length - 1; i >= 0; i--) {
|
|
123
|
+
const anchors = mapping.generations[i];
|
|
124
|
+
while (cursors[i] + 1 < anchors.length &&
|
|
125
|
+
result >= anchors[cursors[i] + 1].afterStart) {
|
|
126
|
+
cursors[i] += 1;
|
|
127
|
+
}
|
|
128
|
+
const anchor = anchors[cursors[i]];
|
|
129
|
+
result = anchor.identity
|
|
130
|
+
? anchor.beforeStart + (result - anchor.afterStart)
|
|
131
|
+
: anchor.beforeStart;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
118
136
|
function segmentAt(starts, offset, genColumn) {
|
|
119
137
|
let position = offsetToLineCol(starts, offset);
|
|
120
138
|
return { genColumn, originalColumn: position.column, originalLine: position.line, source: 0 };
|
|
@@ -223,16 +241,16 @@ const originalPositionFor = (mapping, transformedText, originalText, line, colum
|
|
|
223
241
|
return offsetToLineCol(lineStarts(originalText), resolveOffset(mapping, base + column));
|
|
224
242
|
};
|
|
225
243
|
const toSourceMapV3 = (mapping, transformedText, originalText, source) => {
|
|
226
|
-
let oStarts = lineStarts(originalText), segments = [], tStarts = lineStarts(transformedText);
|
|
244
|
+
let oStarts = lineStarts(originalText), resolve = resolveOffsets(mapping), segments = [], tStarts = lineStarts(transformedText);
|
|
227
245
|
for (let line = 0, n = tStarts.length; line < n; line++) {
|
|
228
|
-
let end = line + 1 < n ? tStarts[line + 1] - 1 : transformedText.length, previous =
|
|
246
|
+
let end = line + 1 < n ? tStarts[line + 1] - 1 : transformedText.length, previous = resolve(tStarts[line]), start = tStarts[line];
|
|
229
247
|
let row = [segmentAt(oStarts, previous, 0)];
|
|
230
248
|
for (let offset = start + 1; offset < end; offset++) {
|
|
231
249
|
let current = charClass(transformedText.charCodeAt(offset));
|
|
232
250
|
if (current !== CLASS_OTHER && current === charClass(transformedText.charCodeAt(offset - 1))) {
|
|
233
251
|
continue;
|
|
234
252
|
}
|
|
235
|
-
let resolved =
|
|
253
|
+
let resolved = resolve(offset);
|
|
236
254
|
if (resolved === previous) {
|
|
237
255
|
continue;
|
|
238
256
|
}
|
|
@@ -243,5 +261,5 @@ const toSourceMapV3 = (mapping, transformedText, originalText, source) => {
|
|
|
243
261
|
}
|
|
244
262
|
return { mappings: encode(segmentsToRaw(segments)), names: [], sources: [source], version: 3 };
|
|
245
263
|
};
|
|
246
|
-
export default { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, toSourceMapV3 };
|
|
247
|
-
export { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, toSourceMapV3 };
|
|
264
|
+
export default { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, resolveOffset, toSourceMapV3 };
|
|
265
|
+
export { buildGeneration, composeEmittedMap, decode, encode, originalPositionFor, resolveOffset, toSourceMapV3 };
|
package/build/jsonc.d.ts
ADDED
package/build/jsonc.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const stripJsonc = (text) => {
|
|
2
|
+
let escaped = false, inBlockComment = false, inLineComment = false, inString = false, stripped = '';
|
|
3
|
+
for (let i = 0, n = text.length; i < n; i++) {
|
|
4
|
+
let char = text[i], next = text[i + 1];
|
|
5
|
+
if (inLineComment) {
|
|
6
|
+
if (char === '\n') {
|
|
7
|
+
inLineComment = false;
|
|
8
|
+
stripped += char;
|
|
9
|
+
}
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (inBlockComment) {
|
|
13
|
+
if (char === '*' && next === '/') {
|
|
14
|
+
inBlockComment = false;
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (inString) {
|
|
20
|
+
stripped += char;
|
|
21
|
+
if (escaped) {
|
|
22
|
+
escaped = false;
|
|
23
|
+
}
|
|
24
|
+
else if (char === '\\') {
|
|
25
|
+
escaped = true;
|
|
26
|
+
}
|
|
27
|
+
else if (char === '"') {
|
|
28
|
+
inString = false;
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (char === '"') {
|
|
33
|
+
inString = true;
|
|
34
|
+
stripped += char;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (char === '/' && next === '/') {
|
|
38
|
+
inLineComment = true;
|
|
39
|
+
i++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === '/' && next === '*') {
|
|
43
|
+
inBlockComment = true;
|
|
44
|
+
i++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
stripped += char;
|
|
48
|
+
}
|
|
49
|
+
escaped = false;
|
|
50
|
+
inString = false;
|
|
51
|
+
let result = '';
|
|
52
|
+
for (let i = 0, n = stripped.length; i < n; i++) {
|
|
53
|
+
let char = stripped[i];
|
|
54
|
+
if (inString) {
|
|
55
|
+
result += char;
|
|
56
|
+
if (escaped) {
|
|
57
|
+
escaped = false;
|
|
58
|
+
}
|
|
59
|
+
else if (char === '\\') {
|
|
60
|
+
escaped = true;
|
|
61
|
+
}
|
|
62
|
+
else if (char === '"') {
|
|
63
|
+
inString = false;
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (char === '"') {
|
|
68
|
+
inString = true;
|
|
69
|
+
result += char;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (char === ',') {
|
|
73
|
+
let j = i + 1;
|
|
74
|
+
while (j < n && /\s/.test(stripped[j])) {
|
|
75
|
+
j++;
|
|
76
|
+
}
|
|
77
|
+
if (stripped[j] === ']' || stripped[j] === '}') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
result += char;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
export { stripJsonc };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/lsp/bin.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DiagnosticSeverity } from 'vscode-languageserver/node';
|
|
2
|
+
import type { CodeAction, Diagnostic as LspDiagnostic, Hover } from 'vscode-languageserver/node';
|
|
3
|
+
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
4
|
+
import type { Diagnostic as AnalyzeDiagnostic } from '../probe/kernel/types.js';
|
|
5
|
+
export type DocumentResolver = (fileName: string) => TextDocument | undefined;
|
|
6
|
+
declare function toLspDiagnostic(diagnostic: AnalyzeDiagnostic, severity: DiagnosticSeverity, resolve: DocumentResolver, uriOf: (fileName: string) => string): LspDiagnostic;
|
|
7
|
+
declare function groupByFile(diagnostics: ReadonlyArray<AnalyzeDiagnostic>, severity: DiagnosticSeverity, resolve: DocumentResolver, uriOf: (fileName: string) => string): Map<string, LspDiagnostic[]>;
|
|
8
|
+
declare function hoverAt(diagnostics: ReadonlyArray<AnalyzeDiagnostic>, offset: number, document: TextDocument): Hover | undefined;
|
|
9
|
+
declare function codeActionsAt(diagnostics: ReadonlyArray<AnalyzeDiagnostic>, startOffset: number, endOffset: number, document: TextDocument, uriOf: (fileName: string) => string): CodeAction[];
|
|
10
|
+
export { codeActionsAt, DiagnosticSeverity, groupByFile, hoverAt, toLspDiagnostic };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CodeActionKind, DiagnosticSeverity, MarkupKind } from 'vscode-languageserver/node';
|
|
2
|
+
function rangeOf(location, document) {
|
|
3
|
+
if (document) {
|
|
4
|
+
return { start: document.positionAt(location.pos), end: document.positionAt(location.end) };
|
|
5
|
+
}
|
|
6
|
+
let start = { line: Math.max(0, location.line - 1), character: Math.max(0, location.column - 1) };
|
|
7
|
+
return { start, end: { line: start.line, character: start.character + 1 } };
|
|
8
|
+
}
|
|
9
|
+
function relatedOf(diagnostic, resolve, uriOf) {
|
|
10
|
+
return diagnostic.related.map((related) => ({
|
|
11
|
+
location: { range: rangeOf(related.location, resolve(related.location.fileName)), uri: uriOf(related.location.fileName) },
|
|
12
|
+
message: related.message,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
function toLspDiagnostic(diagnostic, severity, resolve, uriOf) {
|
|
16
|
+
return {
|
|
17
|
+
code: diagnostic.channel,
|
|
18
|
+
message: diagnostic.message,
|
|
19
|
+
range: rangeOf(diagnostic.location, resolve(diagnostic.location.fileName)),
|
|
20
|
+
relatedInformation: relatedOf(diagnostic, resolve, uriOf),
|
|
21
|
+
severity,
|
|
22
|
+
source: 'analyze',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function groupByFile(diagnostics, severity, resolve, uriOf) {
|
|
26
|
+
let grouped = new Map();
|
|
27
|
+
for (let i = 0, n = diagnostics.length; i < n; i++) {
|
|
28
|
+
let diagnostic = diagnostics[i], fileName = diagnostic.location.fileName, list = grouped.get(fileName);
|
|
29
|
+
if (!list) {
|
|
30
|
+
list = [];
|
|
31
|
+
grouped.set(fileName, list);
|
|
32
|
+
}
|
|
33
|
+
list.push(toLspDiagnostic(diagnostic, severity, resolve, uriOf));
|
|
34
|
+
}
|
|
35
|
+
return grouped;
|
|
36
|
+
}
|
|
37
|
+
function hoverAt(diagnostics, offset, document) {
|
|
38
|
+
let hits = diagnostics.filter((diagnostic) => offset >= diagnostic.location.pos && offset <= diagnostic.location.end);
|
|
39
|
+
if (hits.length === 0) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
let lines = [];
|
|
43
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
44
|
+
let diagnostic = hits[i];
|
|
45
|
+
lines.push(`**${diagnostic.channel}** — ${diagnostic.message}`);
|
|
46
|
+
for (let related of diagnostic.related) {
|
|
47
|
+
lines.push(`- ${related.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { contents: { kind: MarkupKind.Markdown, value: lines.join('\n\n') }, range: rangeOf(hits[0].location, document) };
|
|
51
|
+
}
|
|
52
|
+
function codeActionsAt(diagnostics, startOffset, endOffset, document, uriOf) {
|
|
53
|
+
let actions = [];
|
|
54
|
+
for (let diagnostic of diagnostics) {
|
|
55
|
+
if (diagnostic.location.end < startOffset || diagnostic.location.pos > endOffset) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (let fix of diagnostic.fixes ?? []) {
|
|
59
|
+
let changes = {};
|
|
60
|
+
for (let edit of fix.edits) {
|
|
61
|
+
let uri = uriOf(edit.fileName), range = { start: document.positionAt(edit.pos), end: document.positionAt(edit.end) };
|
|
62
|
+
(changes[uri] ??= []).push({ newText: edit.newText, range });
|
|
63
|
+
}
|
|
64
|
+
actions.push({ edit: { changes }, kind: CodeActionKind.QuickFix, title: fix.title });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return actions;
|
|
68
|
+
}
|
|
69
|
+
export { codeActionsAt, DiagnosticSeverity, groupByFile, hoverAt, toLspDiagnostic };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as NodeFS from 'node:fs';
|
|
2
|
+
import * as NodePath from 'node:path';
|
|
3
|
+
import { CodeActionKind, createConnection, ProposedFeatures, StreamMessageReader, StreamMessageWriter, TextDocuments, TextDocumentSyncKind } from 'vscode-languageserver/node';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
6
|
+
import { codeActionsAt, DiagnosticSeverity, groupByFile, hoverAt } from './diagnostics.js';
|
|
7
|
+
import { AnalyzeWorkspace } from './workspace.js';
|
|
8
|
+
function pathKey(fileName) {
|
|
9
|
+
let resolved = NodePath.resolve(fileName);
|
|
10
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
11
|
+
}
|
|
12
|
+
function findTsconfig(rootDir) {
|
|
13
|
+
let candidate = NodePath.join(rootDir, 'tsconfig.json');
|
|
14
|
+
return NodeFS.existsSync(candidate) ? candidate : undefined;
|
|
15
|
+
}
|
|
16
|
+
function rootFromInitialize(params) {
|
|
17
|
+
let folder = params.workspaceFolders?.[0]?.uri ?? params.rootUri ?? undefined;
|
|
18
|
+
return folder ? fileURLToPath(folder) : undefined;
|
|
19
|
+
}
|
|
20
|
+
function createServer(connection) {
|
|
21
|
+
let analyzed = new Map(), documents = new TextDocuments(TextDocument), pending = new Set(), published = new Set(), timer, workspace;
|
|
22
|
+
function runAnalysis() {
|
|
23
|
+
timer = undefined;
|
|
24
|
+
if (!workspace || !workspace.config) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let changed = [...pending];
|
|
28
|
+
pending.clear();
|
|
29
|
+
try {
|
|
30
|
+
workspace.refresh(changed);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
connection.console.error(`analyze: snapshot refresh failed — ${error.message}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let result = workspace.analyze();
|
|
37
|
+
if (!result) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
analyzed = new Map();
|
|
41
|
+
for (let diagnostic of result.diagnostics) {
|
|
42
|
+
let key = pathKey(diagnostic.location.fileName), list = analyzed.get(key);
|
|
43
|
+
if (!list) {
|
|
44
|
+
list = [];
|
|
45
|
+
analyzed.set(key, list);
|
|
46
|
+
}
|
|
47
|
+
list.push(diagnostic);
|
|
48
|
+
}
|
|
49
|
+
let openByPath = new Map(documents.all().map((document) => [pathKey(fileURLToPath(document.uri)), document])), resolve = (fileName) => openByPath.get(pathKey(fileName)), severity = workspace.config.severity === 'warning' ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error, uriOf = (fileName) => openByPath.get(pathKey(fileName))?.uri ?? pathToFileURL(fileName).toString(), grouped = groupByFile(result.diagnostics, severity, resolve, uriOf), next = new Set();
|
|
50
|
+
for (let [fileName, diagnostics] of grouped) {
|
|
51
|
+
let uri = uriOf(fileName);
|
|
52
|
+
next.add(uri);
|
|
53
|
+
connection.sendDiagnostics({ diagnostics, uri });
|
|
54
|
+
}
|
|
55
|
+
for (let uri of published) {
|
|
56
|
+
if (!next.has(uri)) {
|
|
57
|
+
connection.sendDiagnostics({ diagnostics: [], uri });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
published = next;
|
|
61
|
+
}
|
|
62
|
+
function schedule(changed) {
|
|
63
|
+
for (let i = 0, n = changed.length; i < n; i++) {
|
|
64
|
+
pending.add(changed[i]);
|
|
65
|
+
}
|
|
66
|
+
if (timer !== undefined) {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
69
|
+
timer = setTimeout(runAnalysis, 300);
|
|
70
|
+
}
|
|
71
|
+
connection.onInitialize((params) => {
|
|
72
|
+
let root = rootFromInitialize(params);
|
|
73
|
+
if (root) {
|
|
74
|
+
let tsconfig = findTsconfig(root);
|
|
75
|
+
if (tsconfig) {
|
|
76
|
+
workspace = new AnalyzeWorkspace(tsconfig);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
capabilities: {
|
|
81
|
+
codeActionProvider: { codeActionKinds: [CodeActionKind.QuickFix] },
|
|
82
|
+
hoverProvider: true,
|
|
83
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
connection.onInitialized(() => {
|
|
88
|
+
if (workspace) {
|
|
89
|
+
schedule([]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
connection.onShutdown(() => {
|
|
93
|
+
workspace?.dispose();
|
|
94
|
+
workspace = undefined;
|
|
95
|
+
});
|
|
96
|
+
connection.onHover((params) => {
|
|
97
|
+
let document = documents.get(params.textDocument.uri);
|
|
98
|
+
if (!document) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
let diagnostics = analyzed.get(pathKey(fileURLToPath(params.textDocument.uri)));
|
|
102
|
+
return diagnostics ? hoverAt(diagnostics, document.offsetAt(params.position), document) ?? null : null;
|
|
103
|
+
});
|
|
104
|
+
connection.onCodeAction((params) => {
|
|
105
|
+
let document = documents.get(params.textDocument.uri);
|
|
106
|
+
if (!document) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
let target = pathKey(fileURLToPath(params.textDocument.uri)), diagnostics = analyzed.get(target);
|
|
110
|
+
if (!diagnostics) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
let uriOf = (fileName) => (pathKey(fileName) === target ? params.textDocument.uri : pathToFileURL(fileName).toString());
|
|
114
|
+
return codeActionsAt(diagnostics, document.offsetAt(params.range.start), document.offsetAt(params.range.end), document, uriOf);
|
|
115
|
+
});
|
|
116
|
+
documents.onDidOpen((event) => schedule([fileURLToPath(event.document.uri)]));
|
|
117
|
+
documents.onDidSave((event) => {
|
|
118
|
+
let fileName = fileURLToPath(event.document.uri);
|
|
119
|
+
if (workspace && NodePath.basename(fileName) === 'tsconfig.json') {
|
|
120
|
+
workspace.reloadConfig();
|
|
121
|
+
}
|
|
122
|
+
schedule([fileName]);
|
|
123
|
+
});
|
|
124
|
+
documents.listen(connection);
|
|
125
|
+
connection.listen();
|
|
126
|
+
}
|
|
127
|
+
function startServer() {
|
|
128
|
+
createServer(createConnection(ProposedFeatures.all, new StreamMessageReader(process.stdin), new StreamMessageWriter(process.stdout)));
|
|
129
|
+
}
|
|
130
|
+
export { createServer, startServer };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AnalyzeResult } from '../probe/kernel/analyze.js';
|
|
2
|
+
import type { AnalyzeConfig } from '../probe/kernel/types.js';
|
|
3
|
+
declare class AnalyzeWorkspace {
|
|
4
|
+
private api;
|
|
5
|
+
private configPath;
|
|
6
|
+
private snapshot;
|
|
7
|
+
config: AnalyzeConfig | undefined;
|
|
8
|
+
constructor(tsconfigPath: string);
|
|
9
|
+
reloadConfig(): void;
|
|
10
|
+
refresh(changed: ReadonlyArray<string>): void;
|
|
11
|
+
analyze(): AnalyzeResult | undefined;
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
14
|
+
export { AnalyzeWorkspace };
|