@flint.fyi/typescript-language 0.18.4 → 0.19.0
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/{lib/types/ast.d.ts → dist/index.d.mts} +777 -3
- package/dist/index.mjs +1104 -0
- package/package.json +9 -7
- package/lib/_virtual/_rolldown/runtime.js +0 -13
- package/lib/collectReferencedFilePaths.d.ts +0 -3
- package/lib/collectReferencedFilePaths.js +0 -38
- package/lib/convertTypeScriptDiagnosticToLanguageReport.d.ts +0 -17
- package/lib/convertTypeScriptDiagnosticToLanguageReport.js +0 -112
- package/lib/createTypeScriptServerHost.d.ts +0 -3
- package/lib/createTypeScriptServerHost.js +0 -101
- package/lib/directives/parseDirectivesFromTypeScriptFile.d.ts +0 -12
- package/lib/directives/parseDirectivesFromTypeScriptFile.js +0 -64
- package/lib/getFirstEnumValues.d.ts +0 -35
- package/lib/getFirstEnumValues.js +0 -46
- package/lib/getTSNodeRange.d.ts +0 -8
- package/lib/getTSNodeRange.js +0 -9
- package/lib/getTypeScriptFileCacheImpacts.d.ts +0 -3
- package/lib/getTypeScriptFileCacheImpacts.js +0 -11
- package/lib/index.d.ts +0 -26
- package/lib/index.js +0 -22
- package/lib/language.d.ts +0 -414
- package/lib/language.js +0 -117
- package/lib/nodes.d.ts +0 -203
- package/lib/normalizeRange.d.ts +0 -3
- package/lib/normalizeRange.js +0 -17
- package/lib/orderTypeScriptFilePaths.d.ts +0 -2
- package/lib/orderTypeScriptFilePaths.js +0 -90
- package/lib/package.js +0 -5
- package/lib/scope/identifierReferences.d.ts +0 -3
- package/lib/scope/identifierReferences.js +0 -89
- package/lib/scope/scopeManager.d.ts +0 -14
- package/lib/scope/scopeManager.js +0 -153
- package/lib/scope/scopes.d.ts +0 -7
- package/lib/scope/scopes.js +0 -64
- package/lib/scope/types.d.ts +0 -45
- package/lib/types/checker.d.ts +0 -22
- package/lib/utils/containsGlobalDeclarations.d.ts +0 -6
- package/lib/utils/containsGlobalDeclarations.js +0 -17
- package/lib/utils/createRuleTesterTSConfig.d.ts +0 -7
- package/lib/utils/createRuleTesterTSConfig.js +0 -16
- package/lib/utils/declarationIncludesGlobal.d.ts +0 -6
- package/lib/utils/declarationIncludesGlobal.js +0 -7
- package/lib/utils/declarationsIncludeGlobal.d.ts +0 -2
- package/lib/utils/declarationsIncludeGlobal.js +0 -7
- package/lib/utils/forEachChild.d.ts +0 -7
- package/lib/utils/forEachChild.js +0 -5
- package/lib/utils/getDeclarationsIfGlobal.d.ts +0 -6
- package/lib/utils/getDeclarationsIfGlobal.js +0 -8
- package/lib/utils/getModifyingReferences.d.ts +0 -10
- package/lib/utils/getModifyingReferences.js +0 -13
- package/lib/utils/hasSameTokens.d.ts +0 -6
- package/lib/utils/hasSameTokens.js +0 -30
- package/lib/utils/isBuiltinArrayMethod.d.ts +0 -13
- package/lib/utils/isBuiltinArrayMethod.js +0 -7
- package/lib/utils/isFunction.d.ts +0 -6
- package/lib/utils/isFunction.js +0 -6
- package/lib/utils/isGlobalDeclaration.d.ts +0 -6
- package/lib/utils/isGlobalDeclaration.js +0 -7
- package/lib/utils/isGlobalDeclarationOfName.d.ts +0 -10
- package/lib/utils/isGlobalDeclarationOfName.js +0 -21
- package/lib/utils/isGlobalVariable.d.ts +0 -12
- package/lib/utils/isGlobalVariable.js +0 -17
- package/lib/utils/isInlineArrayCreation.d.ts +0 -11
- package/lib/utils/isInlineArrayCreation.js +0 -43
- package/lib/utils/isStaticString.d.ts +0 -5
- package/lib/utils/isStaticString.js +0 -7
- package/lib/utils/isStringRawNoSubstitution.d.ts +0 -7
- package/lib/utils/isStringRawNoSubstitution.js +0 -9
- package/lib/utils/unwrapParentParenthesizedExpressions.d.ts +0 -6
- package/lib/utils/unwrapParentParenthesizedExpressions.js +0 -7
- package/lib/utils/unwrapParenthesizedNode.d.ts +0 -6
- package/lib/utils/unwrapParenthesizedNode.js +0 -7
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
import ts, { SyntaxKind, isClassDeclaration, isFunctionDeclaration, isIdentifier, isInterfaceDeclaration, isPropertySignature, isVariableDeclaration } from "typescript";
|
|
2
|
+
import { DirectivesCollector, commonlyIgnoredPaths, createLanguage, getColumnAndLineOfPosition, getPositionOfColumnAndLine } from "@flint.fyi/core";
|
|
3
|
+
import * as tsutils from "ts-api-utils";
|
|
4
|
+
import { FlintAssertionError, assert, normalizePath, nullThrows, pathKey } from "@flint.fyi/utils";
|
|
5
|
+
import * as path$1 from "node:path";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { createProjectService } from "@typescript-eslint/project-service";
|
|
8
|
+
import { debugForFile } from "debug-for-file";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import timers from "node:timers";
|
|
11
|
+
import { dirname, resolve } from "pathe";
|
|
12
|
+
import { WeakCachedFactory } from "cached-factory";
|
|
13
|
+
//#region src/convertTypeScriptDiagnosticToLanguageReport.ts
|
|
14
|
+
function convertTypeScriptDiagnosticToLanguageReport(diagnostic) {
|
|
15
|
+
return {
|
|
16
|
+
code: `TS${diagnostic.code}`,
|
|
17
|
+
source: "typescript",
|
|
18
|
+
text: formatReport(diagnostic),
|
|
19
|
+
...diagnostic.file !== void 0 && diagnostic.start !== void 0 && { range: {
|
|
20
|
+
begin: diagnostic.start,
|
|
21
|
+
end: diagnostic.start + (diagnostic.length ?? 0)
|
|
22
|
+
} }
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function color(text, formatStyle) {
|
|
26
|
+
return formatStyle + text + resetEscapeSequence;
|
|
27
|
+
}
|
|
28
|
+
function formatReport(diagnostic) {
|
|
29
|
+
let output = "";
|
|
30
|
+
if (diagnostic.file !== void 0) {
|
|
31
|
+
output += formatLocation(diagnostic.file, diagnostic.start);
|
|
32
|
+
output += " - ";
|
|
33
|
+
}
|
|
34
|
+
output += color(`TS${diagnostic.code}`, COLOR.Grey);
|
|
35
|
+
output += ": ";
|
|
36
|
+
output += ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
37
|
+
if (diagnostic.file !== void 0) {
|
|
38
|
+
output += "\n";
|
|
39
|
+
output += formatCodeSpan(diagnostic.file, diagnostic.start, diagnostic.length, "", COLOR.Red);
|
|
40
|
+
}
|
|
41
|
+
if (diagnostic.relatedInformation) {
|
|
42
|
+
output += "\n";
|
|
43
|
+
for (const { file, length, messageText, start } of diagnostic.relatedInformation) {
|
|
44
|
+
const indent = " ";
|
|
45
|
+
if (file) {
|
|
46
|
+
output += "\n";
|
|
47
|
+
output += " " + formatLocation(file, start);
|
|
48
|
+
output += formatCodeSpan(file, start, length, indent, COLOR.Cyan);
|
|
49
|
+
}
|
|
50
|
+
output += "\n";
|
|
51
|
+
output += indent + ts.flattenDiagnosticMessageText(messageText, "\n");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
const gutterStyleSequence = "\x1B[7m";
|
|
57
|
+
const ellipsis = "...";
|
|
58
|
+
const gutterSeparator = " ";
|
|
59
|
+
const resetEscapeSequence = "\x1B[0m";
|
|
60
|
+
const COLOR = {
|
|
61
|
+
Blue: "\x1B[94m",
|
|
62
|
+
Cyan: "\x1B[96m",
|
|
63
|
+
Grey: "\x1B[90m",
|
|
64
|
+
Red: "\x1B[91m",
|
|
65
|
+
Yellow: "\x1B[93m"
|
|
66
|
+
};
|
|
67
|
+
function displayFilename(name) {
|
|
68
|
+
if (name.startsWith("./")) return name.slice(2);
|
|
69
|
+
return name.slice(process.cwd().length + 1);
|
|
70
|
+
}
|
|
71
|
+
function formatCodeSpan(file, start, length, indent, squiggleColor) {
|
|
72
|
+
const { column: firstLineChar, line: firstLine } = getColumnAndLineOfPosition(file, start);
|
|
73
|
+
const { column: lastLineChar, line: lastLine } = getColumnAndLineOfPosition(file, start + length);
|
|
74
|
+
const lastLineInFile = getColumnAndLineOfPosition(file, file.text.length).line;
|
|
75
|
+
const hasMoreThanFiveLines = lastLine - firstLine >= 4;
|
|
76
|
+
let gutterWidth = (lastLine + 1 + "").length;
|
|
77
|
+
if (hasMoreThanFiveLines) gutterWidth = Math.max(3, gutterWidth);
|
|
78
|
+
let context = "";
|
|
79
|
+
for (let i = firstLine; i <= lastLine; i++) {
|
|
80
|
+
context += "\n";
|
|
81
|
+
if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) {
|
|
82
|
+
context += indent + color(ellipsis.padStart(gutterWidth), gutterStyleSequence) + " \n";
|
|
83
|
+
i = lastLine - 1;
|
|
84
|
+
}
|
|
85
|
+
const lineStart = getPositionOfColumnAndLine(file, {
|
|
86
|
+
column: 0,
|
|
87
|
+
line: i
|
|
88
|
+
});
|
|
89
|
+
const lineEnd = i < lastLineInFile ? getPositionOfColumnAndLine(file, {
|
|
90
|
+
column: 0,
|
|
91
|
+
line: i + 1
|
|
92
|
+
}) : file.text.length;
|
|
93
|
+
let lineContent = file.text.slice(lineStart, lineEnd);
|
|
94
|
+
lineContent = lineContent.trimEnd();
|
|
95
|
+
lineContent = lineContent.replace(/\t/g, " ");
|
|
96
|
+
context += indent + color((i + 1 + "").padStart(gutterWidth), gutterStyleSequence) + gutterSeparator;
|
|
97
|
+
context += lineContent + "\n";
|
|
98
|
+
context += indent + color("".padStart(gutterWidth), gutterStyleSequence) + gutterSeparator;
|
|
99
|
+
context += squiggleColor;
|
|
100
|
+
if (i === firstLine) {
|
|
101
|
+
const lastCharForLine = i === lastLine ? lastLineChar : void 0;
|
|
102
|
+
context += lineContent.slice(0, firstLineChar).replace(/\S/g, " ");
|
|
103
|
+
context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~");
|
|
104
|
+
} else if (i === lastLine) context += lineContent.slice(0, lastLineChar).replace(/./g, "~");
|
|
105
|
+
else context += lineContent.replace(/./g, "~");
|
|
106
|
+
context += resetEscapeSequence;
|
|
107
|
+
}
|
|
108
|
+
return context;
|
|
109
|
+
}
|
|
110
|
+
function formatLocation(file, start) {
|
|
111
|
+
const { column, line } = getColumnAndLineOfPosition(file, start);
|
|
112
|
+
const relativeFileName = displayFilename(file.fileName);
|
|
113
|
+
let output = "";
|
|
114
|
+
output += color(relativeFileName, COLOR.Cyan);
|
|
115
|
+
output += ":";
|
|
116
|
+
output += color(`${line + 1}`, COLOR.Yellow);
|
|
117
|
+
output += ":";
|
|
118
|
+
output += color(`${column + 1}`, COLOR.Yellow);
|
|
119
|
+
return output;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/normalizeRange.ts
|
|
123
|
+
function normalizeRange(original, sourceFile) {
|
|
124
|
+
const onCharacters = isNode(original) ? {
|
|
125
|
+
begin: original.getStart(sourceFile),
|
|
126
|
+
end: original.getEnd()
|
|
127
|
+
} : original;
|
|
128
|
+
return {
|
|
129
|
+
begin: getColumnAndLineOfPosition(sourceFile, onCharacters.begin),
|
|
130
|
+
end: getColumnAndLineOfPosition(sourceFile, onCharacters.end)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function isNode(value) {
|
|
134
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/directives/parseDirectivesFromTypeScriptFile.ts
|
|
138
|
+
function extractDirectivesFromTypeScriptFile(sourceFile) {
|
|
139
|
+
const directives = [];
|
|
140
|
+
tsutils.forEachComment(sourceFile, (fullText, sourceRange) => {
|
|
141
|
+
const commentText = fullText.slice(sourceRange.pos, sourceRange.end);
|
|
142
|
+
const match = /^\/\/\s*flint-(\S+)(?:\s+(.+))?/.exec(commentText);
|
|
143
|
+
if (!match) return;
|
|
144
|
+
let range = normalizeRange({
|
|
145
|
+
begin: sourceRange.pos,
|
|
146
|
+
end: sourceRange.end
|
|
147
|
+
}, sourceFile);
|
|
148
|
+
const matches = match.slice(1);
|
|
149
|
+
const type = nullThrows(matches[0], "First match is expected to be present by the regex match");
|
|
150
|
+
const selection = matches[1] ?? "";
|
|
151
|
+
if (type === "disable-next-line") range = extendRangeToNextCodeLine(sourceFile, range);
|
|
152
|
+
directives.push({
|
|
153
|
+
range,
|
|
154
|
+
selection,
|
|
155
|
+
type
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
return directives;
|
|
159
|
+
}
|
|
160
|
+
function parseDirectivesFromTypeScriptFile(sourceFile) {
|
|
161
|
+
const collector = new DirectivesCollector(sourceFile.statements.at(0)?.getStart(sourceFile) ?? sourceFile.text.length);
|
|
162
|
+
for (const { range, selection, type } of extractDirectivesFromTypeScriptFile(sourceFile)) collector.add(range, selection, type);
|
|
163
|
+
return collector.collect();
|
|
164
|
+
}
|
|
165
|
+
function computeNextCodeLine(sourceFile, directiveLine) {
|
|
166
|
+
const lineStarts = sourceFile.getLineStarts();
|
|
167
|
+
const nextLineStart = lineStarts[directiveLine + 1];
|
|
168
|
+
if (nextLineStart === void 0) return;
|
|
169
|
+
const scanner = ts.createScanner(sourceFile.languageVersion, true, sourceFile.languageVariant, sourceFile.text, void 0, nextLineStart);
|
|
170
|
+
if (scanner.scan() === SyntaxKind.EndOfFileToken) return;
|
|
171
|
+
const tokenPos = scanner.getTokenStart();
|
|
172
|
+
const codeLine = sourceFile.getLineAndCharacterOfPosition(tokenPos).line;
|
|
173
|
+
for (let line = directiveLine + 1; line < codeLine; line++) {
|
|
174
|
+
const start = lineStarts[line];
|
|
175
|
+
const end = lineStarts[line + 1] ?? sourceFile.text.length;
|
|
176
|
+
if (sourceFile.text.slice(start, end).trim() === "") return;
|
|
177
|
+
}
|
|
178
|
+
return codeLine;
|
|
179
|
+
}
|
|
180
|
+
function extendRangeToNextCodeLine(sourceFile, range) {
|
|
181
|
+
const codeLine = computeNextCodeLine(sourceFile, range.begin.line);
|
|
182
|
+
if (codeLine === void 0 || codeLine <= range.end.line + 1) return range;
|
|
183
|
+
const endPosition = nullThrows(sourceFile.getLineStarts()[codeLine], "Code line start is expected to be present by the computed code line") - 1;
|
|
184
|
+
const { character, line } = sourceFile.getLineAndCharacterOfPosition(endPosition);
|
|
185
|
+
return {
|
|
186
|
+
...range,
|
|
187
|
+
end: {
|
|
188
|
+
column: character,
|
|
189
|
+
line,
|
|
190
|
+
raw: endPosition
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/getTSNodeRange.ts
|
|
196
|
+
function getTSNodeRange(node, sourceFile) {
|
|
197
|
+
return {
|
|
198
|
+
begin: node.getStart(sourceFile),
|
|
199
|
+
end: node.getEnd()
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region package.json
|
|
204
|
+
var name = "@flint.fyi/typescript-language";
|
|
205
|
+
var version = "0.19.0";
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/createTypeScriptServerHost.ts
|
|
208
|
+
function serverHostMethodNotImplemented(methodName) {
|
|
209
|
+
throw new FlintAssertionError(`ts.ServerHost's method '${methodName}' is not implemented.`);
|
|
210
|
+
}
|
|
211
|
+
const UV_DIRENT_TYPE = {
|
|
212
|
+
UV_DIRENT_DIR: 2,
|
|
213
|
+
UV_DIRENT_FILE: 1
|
|
214
|
+
};
|
|
215
|
+
const DirentCtor = fs.Dirent;
|
|
216
|
+
function createTypeScriptServerHost(host) {
|
|
217
|
+
return {
|
|
218
|
+
...ts.sys,
|
|
219
|
+
args: [],
|
|
220
|
+
clearImmediate: timers.clearImmediate,
|
|
221
|
+
clearTimeout: timers.clearTimeout,
|
|
222
|
+
createDirectory() {
|
|
223
|
+
serverHostMethodNotImplemented("createDirectory");
|
|
224
|
+
},
|
|
225
|
+
directoryExists(directoryPath) {
|
|
226
|
+
return host.fileTypeSync(resolve(host.getCurrentDirectory(), directoryPath)) === "directory";
|
|
227
|
+
},
|
|
228
|
+
exit() {
|
|
229
|
+
serverHostMethodNotImplemented("exit");
|
|
230
|
+
},
|
|
231
|
+
fileExists(filePath) {
|
|
232
|
+
return host.fileTypeSync(resolve(host.getCurrentDirectory(), filePath)) === "file";
|
|
233
|
+
},
|
|
234
|
+
readDirectory(directoryPath, extensions, exclude, include, depth) {
|
|
235
|
+
const originalCwd = process.cwd.bind(process);
|
|
236
|
+
process.cwd = () => host.getCurrentDirectory();
|
|
237
|
+
const originalReadDirSync = fs.readdirSync;
|
|
238
|
+
const patchedReaddirSync = (readPath, options) => {
|
|
239
|
+
assert(typeof options === "object" && options != null && Object.keys(options).length === 1 && options.withFileTypes === true, `ts.sys.readDirectory passed unexpected options to fs.readdirSync: ${JSON.stringify(options)}`);
|
|
240
|
+
assert(typeof readPath === "string", "ts.sys.readDirectory passed unexpected path to fs.readdirSync");
|
|
241
|
+
try {
|
|
242
|
+
fs.readdirSync = originalReadDirSync;
|
|
243
|
+
return host.readDirectorySync(resolve(host.getCurrentDirectory(), readPath)).map((dirent) => new DirentCtor(dirent.name, dirent.type === "file" ? UV_DIRENT_TYPE.UV_DIRENT_FILE : UV_DIRENT_TYPE.UV_DIRENT_DIR, readPath));
|
|
244
|
+
} finally {
|
|
245
|
+
fs.readdirSync = patchedReaddirSync;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
fs.readdirSync = patchedReaddirSync;
|
|
249
|
+
try {
|
|
250
|
+
return ts.sys.readDirectory(directoryPath, extensions, exclude, include, depth);
|
|
251
|
+
} finally {
|
|
252
|
+
process.cwd = originalCwd;
|
|
253
|
+
fs.readdirSync = originalReadDirSync;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
readFile(filePath) {
|
|
257
|
+
return host.readFileSync(resolve(host.getCurrentDirectory(), filePath));
|
|
258
|
+
},
|
|
259
|
+
setImmediate: timers.setImmediate,
|
|
260
|
+
setTimeout: timers.setTimeout,
|
|
261
|
+
watchDirectory(directoryPath, callback, recursive = false) {
|
|
262
|
+
const watcher = host.watchDirectorySync(resolve(host.getCurrentDirectory(), directoryPath), (filePathAbsolute) => {
|
|
263
|
+
callback(filePathAbsolute);
|
|
264
|
+
}, {
|
|
265
|
+
ignoredPaths: commonlyIgnoredPaths,
|
|
266
|
+
recursive
|
|
267
|
+
});
|
|
268
|
+
return { close() {
|
|
269
|
+
watcher[Symbol.dispose]();
|
|
270
|
+
} };
|
|
271
|
+
},
|
|
272
|
+
watchFile(filePath, callback) {
|
|
273
|
+
const watcher = host.watchFileSync(resolve(host.getCurrentDirectory(), filePath), (event) => {
|
|
274
|
+
let eventKind;
|
|
275
|
+
switch (event) {
|
|
276
|
+
case "changed":
|
|
277
|
+
eventKind = ts.FileWatcherEventKind.Changed;
|
|
278
|
+
break;
|
|
279
|
+
case "created":
|
|
280
|
+
eventKind = ts.FileWatcherEventKind.Created;
|
|
281
|
+
break;
|
|
282
|
+
case "deleted":
|
|
283
|
+
eventKind = ts.FileWatcherEventKind.Deleted;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
callback(filePath, eventKind);
|
|
287
|
+
}, { ignoredPaths: commonlyIgnoredPaths });
|
|
288
|
+
return { close() {
|
|
289
|
+
watcher[Symbol.dispose]();
|
|
290
|
+
} };
|
|
291
|
+
},
|
|
292
|
+
write() {
|
|
293
|
+
serverHostMethodNotImplemented("write");
|
|
294
|
+
},
|
|
295
|
+
writeFile() {
|
|
296
|
+
serverHostMethodNotImplemented("writeFile");
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/getFirstEnumValues.ts
|
|
302
|
+
/**
|
|
303
|
+
* Removes TypeScript enum values that alias earlier values.
|
|
304
|
+
* @param original Any TypeScript enum with numeric values.
|
|
305
|
+
* @returns A version of the enum with only the first occurrence of each value.
|
|
306
|
+
* @example
|
|
307
|
+
* Given the following enum:
|
|
308
|
+
* ```ts
|
|
309
|
+
* enum Example {
|
|
310
|
+
* A = 1,
|
|
311
|
+
* B = 2,
|
|
312
|
+
* C = A
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
* Its original value looks like:
|
|
316
|
+
* ```ts
|
|
317
|
+
* {
|
|
318
|
+
* 1: 'C', // <-- Points to 'C', the alias of 'A'
|
|
319
|
+
* 2: 'B',
|
|
320
|
+
* A: 1,
|
|
321
|
+
* B: 2,
|
|
322
|
+
* C: 1,
|
|
323
|
+
* }
|
|
324
|
+
* ```
|
|
325
|
+
* Calling `getFirstEnumValues(Example)` would return:
|
|
326
|
+
* ```ts
|
|
327
|
+
* {
|
|
328
|
+
* 1: 'A', // <-- Corrected to the original 'A'
|
|
329
|
+
* 2: 'B',
|
|
330
|
+
* A: 1,
|
|
331
|
+
* B: 2,
|
|
332
|
+
* C: 1,
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
function getFirstEnumValues(original) {
|
|
337
|
+
const result = {};
|
|
338
|
+
for (const key in original) {
|
|
339
|
+
if (typeof original[key] !== "number") continue;
|
|
340
|
+
if (!(key in result)) result[key] = original[key];
|
|
341
|
+
if (!(original[key] in result)) result[original[key]] = key;
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/collectReferencedFilePaths.ts
|
|
347
|
+
function collectReferencedFilePaths(program, sourceFile) {
|
|
348
|
+
const modulePaths = /* @__PURE__ */ new Set();
|
|
349
|
+
function resolveModulePath(moduleSpecifier) {
|
|
350
|
+
const resolved = ts.resolveModuleName(moduleSpecifier, sourceFile.fileName, program.getCompilerOptions(), ts.sys);
|
|
351
|
+
if (resolved.resolvedModule?.isExternalLibraryImport === false) return path$1.relative(process.cwd(), resolved.resolvedModule.resolvedFileName);
|
|
352
|
+
}
|
|
353
|
+
function visit(node) {
|
|
354
|
+
let path;
|
|
355
|
+
if (isImportDeclaration(node)) path = node.moduleSpecifier.text;
|
|
356
|
+
else if (isImportCall(node)) path = node.arguments[0].text;
|
|
357
|
+
else if (isAwaitImportCall(node)) path = node.expression.arguments[0].text;
|
|
358
|
+
else if (isImportTypeNode(node)) path = node.argument.literal.text;
|
|
359
|
+
const resolvedPath = path && resolveModulePath(path);
|
|
360
|
+
if (resolvedPath) modulePaths.add(resolvedPath);
|
|
361
|
+
ts.forEachChild(node, visit);
|
|
362
|
+
}
|
|
363
|
+
visit(sourceFile);
|
|
364
|
+
return Array.from(modulePaths);
|
|
365
|
+
}
|
|
366
|
+
function isAwaitImportCall(node) {
|
|
367
|
+
return ts.isAwaitExpression(node) && isImportCall(node.expression);
|
|
368
|
+
}
|
|
369
|
+
function isImportCall(node) {
|
|
370
|
+
return ts.isCallExpression(node) && tsutils.isImportExpression(node.expression) && !!node.arguments.length && ts.isStringLiteral(nullThrows(node.arguments[0], "First argument is expected to be present by prior length check"));
|
|
371
|
+
}
|
|
372
|
+
function isImportDeclaration(node) {
|
|
373
|
+
return ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier);
|
|
374
|
+
}
|
|
375
|
+
function isImportTypeNode(node) {
|
|
376
|
+
return ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal);
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/utils/containsGlobalDeclarations.ts
|
|
380
|
+
/**
|
|
381
|
+
* Inspects top-level statements of a TS source file to determine
|
|
382
|
+
* if it introduces or modifies entities in the global scope.
|
|
383
|
+
*/
|
|
384
|
+
function containsGlobalDeclarations(sourceFileNode) {
|
|
385
|
+
const isModule = ts.isExternalModule(sourceFileNode);
|
|
386
|
+
return sourceFileNode.statements.some((statement) => {
|
|
387
|
+
if (ts.isModuleDeclaration(statement) && statement.name.text === "global") return true;
|
|
388
|
+
if (isModule) return false;
|
|
389
|
+
if (!ts.canHaveModifiers(statement)) return false;
|
|
390
|
+
return ts.getModifiers(statement)?.some((mod) => mod.kind === SyntaxKind.DeclareKeyword);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/getTypeScriptFileCacheImpacts.ts
|
|
395
|
+
function getTypeScriptFileCacheImpacts(file) {
|
|
396
|
+
return {
|
|
397
|
+
dependencies: ["tsconfig.json", ...collectReferencedFilePaths(file.services.program, file.services.sourceFile)],
|
|
398
|
+
invalidatesCache: containsGlobalDeclarations(file.services.sourceFile)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/orderTypeScriptFilePaths.ts
|
|
403
|
+
const tsConfigFileName = "tsconfig.json";
|
|
404
|
+
function orderTypeScriptFilePaths(filePaths, host) {
|
|
405
|
+
if (filePaths.length < 2) return [...filePaths];
|
|
406
|
+
const caseSensitiveFS = host.isCaseSensitiveFS();
|
|
407
|
+
const cwd = host.getCurrentDirectory();
|
|
408
|
+
const parseHost = {
|
|
409
|
+
...createTypeScriptServerHost(host),
|
|
410
|
+
onUnRecoverableConfigFileDiagnostic() {}
|
|
411
|
+
};
|
|
412
|
+
const configByDirectory = /* @__PURE__ */ new Map();
|
|
413
|
+
const parsedConfigByPath = /* @__PURE__ */ new Map();
|
|
414
|
+
const configByFile = /* @__PURE__ */ new Map();
|
|
415
|
+
function comparePaths(a, b) {
|
|
416
|
+
return pathKey(a, caseSensitiveFS).localeCompare(pathKey(b, caseSensitiveFS));
|
|
417
|
+
}
|
|
418
|
+
function findConfigFile(directoryPath) {
|
|
419
|
+
const directoryPathNormalized = normalizePath(directoryPath);
|
|
420
|
+
const directoryKey = pathKey(directoryPathNormalized, caseSensitiveFS);
|
|
421
|
+
if (configByDirectory.has(directoryKey)) return configByDirectory.get(directoryKey);
|
|
422
|
+
const configPath = ts.findConfigFile(directoryPathNormalized, (path) => parseHost.fileExists(path), tsConfigFileName);
|
|
423
|
+
const normalizedConfigPath = configPath == null ? void 0 : resolve(cwd, configPath);
|
|
424
|
+
configByDirectory.set(directoryKey, normalizedConfigPath);
|
|
425
|
+
return normalizedConfigPath;
|
|
426
|
+
}
|
|
427
|
+
function getParsedConfig(configPath) {
|
|
428
|
+
const configKey = pathKey(configPath, caseSensitiveFS);
|
|
429
|
+
if (parsedConfigByPath.has(configKey)) return parsedConfigByPath.get(configKey);
|
|
430
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, parseHost);
|
|
431
|
+
const parsedConfig = parsed == null ? void 0 : {
|
|
432
|
+
fileNames: parsed.fileNames.map((fileName) => resolve(cwd, fileName)),
|
|
433
|
+
references: (parsed.projectReferences ?? []).map((reference) => resolve(cwd, ts.resolveProjectReferencePath(reference))).sort(comparePaths)
|
|
434
|
+
};
|
|
435
|
+
parsedConfigByPath.set(configKey, parsedConfig);
|
|
436
|
+
return parsedConfig;
|
|
437
|
+
}
|
|
438
|
+
function collectConfigsTopologically(configPath) {
|
|
439
|
+
const orderedConfigs = [];
|
|
440
|
+
const seen = /* @__PURE__ */ new Set();
|
|
441
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
442
|
+
function visit(currentConfigPath) {
|
|
443
|
+
const configKey = pathKey(currentConfigPath, caseSensitiveFS);
|
|
444
|
+
if (seen.has(configKey) || visiting.has(configKey)) return;
|
|
445
|
+
visiting.add(configKey);
|
|
446
|
+
for (const reference of getParsedConfig(currentConfigPath)?.references ?? []) visit(reference);
|
|
447
|
+
visiting.delete(configKey);
|
|
448
|
+
seen.add(configKey);
|
|
449
|
+
orderedConfigs.push(currentConfigPath);
|
|
450
|
+
}
|
|
451
|
+
visit(configPath);
|
|
452
|
+
return orderedConfigs;
|
|
453
|
+
}
|
|
454
|
+
const fileInfos = filePaths.map((original) => {
|
|
455
|
+
const absolute = resolve(cwd, original);
|
|
456
|
+
const rootConfig = findConfigFile(dirname(absolute));
|
|
457
|
+
return {
|
|
458
|
+
absolute,
|
|
459
|
+
original,
|
|
460
|
+
rootConfig,
|
|
461
|
+
rootConfigKey: rootConfig == null ? void 0 : pathKey(rootConfig, caseSensitiveFS)
|
|
462
|
+
};
|
|
463
|
+
});
|
|
464
|
+
const rootConfigs = Array.from(new Set(fileInfos.map(({ rootConfig }) => rootConfig).filter((rootConfig) => rootConfig != null))).sort(comparePaths);
|
|
465
|
+
const fileInfoByPath = new Map(fileInfos.map((fileInfo) => [pathKey(fileInfo.absolute, caseSensitiveFS), fileInfo]));
|
|
466
|
+
const configRanks = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const rootConfig of rootConfigs) {
|
|
468
|
+
const rootConfigKey = pathKey(rootConfig, caseSensitiveFS);
|
|
469
|
+
for (const config of collectConfigsTopologically(rootConfig)) {
|
|
470
|
+
const configKey = pathKey(config, caseSensitiveFS);
|
|
471
|
+
if (!configRanks.has(configKey)) configRanks.set(configKey, configRanks.size);
|
|
472
|
+
for (const fileName of getParsedConfig(config)?.fileNames ?? []) {
|
|
473
|
+
const fileInfo = fileInfoByPath.get(pathKey(fileName, caseSensitiveFS));
|
|
474
|
+
if (fileInfo?.rootConfigKey !== rootConfigKey) continue;
|
|
475
|
+
const fileKey = pathKey(fileInfo.absolute, caseSensitiveFS);
|
|
476
|
+
if (!configByFile.has(fileKey)) configByFile.set(fileKey, config);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return fileInfos.toSorted((a, b) => {
|
|
481
|
+
const configA = configByFile.get(pathKey(a.absolute, caseSensitiveFS)) ?? a.rootConfig;
|
|
482
|
+
const configB = configByFile.get(pathKey(b.absolute, caseSensitiveFS)) ?? b.rootConfig;
|
|
483
|
+
return (configA == null ? Number.MAX_SAFE_INTEGER : configRanks.get(pathKey(configA, caseSensitiveFS)) ?? Number.MAX_SAFE_INTEGER) - (configB == null ? Number.MAX_SAFE_INTEGER : configRanks.get(pathKey(configB, caseSensitiveFS)) ?? Number.MAX_SAFE_INTEGER) || comparePaths(a.absolute, b.absolute);
|
|
484
|
+
}).map(({ original }) => original);
|
|
485
|
+
}
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region src/language.ts
|
|
488
|
+
const log = debugForFile(import.meta.filename);
|
|
489
|
+
const NodeSyntaxKinds = getFirstEnumValues(SyntaxKind);
|
|
490
|
+
const stateSymbol = Symbol.for("@flint.fyi/typescript-language/state");
|
|
491
|
+
const globalTyped = globalThis;
|
|
492
|
+
assert(globalTyped[stateSymbol] == null, `Two different versions of ${name} are imported: ${version} and ${globalTyped[stateSymbol]?.packageVersion}`);
|
|
493
|
+
const languageState = globalTyped[stateSymbol] = {
|
|
494
|
+
packageVersion: version,
|
|
495
|
+
volarCreateFile: null
|
|
496
|
+
};
|
|
497
|
+
function setVolarCreateFile(create) {
|
|
498
|
+
assert(languageState.volarCreateFile == null, "setVolarCreateFile is expected to be called only once");
|
|
499
|
+
languageState.volarCreateFile = create;
|
|
500
|
+
}
|
|
501
|
+
const typescriptLanguage = createLanguage({
|
|
502
|
+
about: { name: "TypeScript" },
|
|
503
|
+
createFileFactory: (host) => {
|
|
504
|
+
const { service } = createProjectService({ host: createTypeScriptServerHost(host) });
|
|
505
|
+
function createFile(data) {
|
|
506
|
+
log("Opening client file:", data.filePathAbsolute);
|
|
507
|
+
service.openClientFile(data.filePathAbsolute);
|
|
508
|
+
log("Retrieving client services:", data.filePathAbsolute);
|
|
509
|
+
const scriptInfo = nullThrows(service.getScriptInfo(data.filePathAbsolute), `Could not find script info for file: ${data.filePathAbsolute}`);
|
|
510
|
+
const program = nullThrows(nullThrows(service.getDefaultProjectForFile(scriptInfo.fileName, true), `Could not find default project for file: ${data.filePathAbsolute}`).getLanguageService(true).getProgram(), `Could not retrieve program for file: ${data.filePathAbsolute}`);
|
|
511
|
+
const sourceFile = nullThrows(program.getSourceFile(data.filePathAbsolute), `Could not retrieve source file for: ${data.filePathAbsolute}`);
|
|
512
|
+
const fileExtension = path.extname(data.filePathAbsolute);
|
|
513
|
+
if (typeScriptCoreSupportedExtensions.has(fileExtension)) return {
|
|
514
|
+
...parseDirectivesFromTypeScriptFile(sourceFile),
|
|
515
|
+
about: data,
|
|
516
|
+
language: typescriptLanguage,
|
|
517
|
+
services: {
|
|
518
|
+
program,
|
|
519
|
+
sourceFile,
|
|
520
|
+
typeChecker: program.getTypeChecker()
|
|
521
|
+
},
|
|
522
|
+
[Symbol.dispose]() {
|
|
523
|
+
service.closeClientFile(data.filePathAbsolute);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
if (languageState.volarCreateFile == null) throwUnknownLanguageExtension(data.filePathAbsolute);
|
|
527
|
+
return {
|
|
528
|
+
...languageState.volarCreateFile(data, program, sourceFile),
|
|
529
|
+
[Symbol.dispose]() {
|
|
530
|
+
service.closeClientFile(data.filePathAbsolute);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return { createFile };
|
|
535
|
+
},
|
|
536
|
+
getFileCacheImpacts: getTypeScriptFileCacheImpacts,
|
|
537
|
+
getLanguageReports(file) {
|
|
538
|
+
if ("__volarServices" in file) return file.__volarServices.getLanguageReports();
|
|
539
|
+
return ts.getPreEmitDiagnostics(file.services.program, file.services.sourceFile).map(convertTypeScriptDiagnosticToLanguageReport);
|
|
540
|
+
},
|
|
541
|
+
orderFilePaths: orderTypeScriptFilePaths,
|
|
542
|
+
runFileVisitors(file, options, runtime) {
|
|
543
|
+
if (!runtime.visitors) return;
|
|
544
|
+
if ("__volarServices" in file) {
|
|
545
|
+
file.__volarServices.runVisitors(file, options, runtime);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const { visitors } = runtime;
|
|
549
|
+
const visitorServices = {
|
|
550
|
+
options,
|
|
551
|
+
...file.services
|
|
552
|
+
};
|
|
553
|
+
const visit = (node) => {
|
|
554
|
+
const key = NodeSyntaxKinds[node.kind];
|
|
555
|
+
visitors[key]?.(node, visitorServices);
|
|
556
|
+
node.forEachChild(visit);
|
|
557
|
+
visitors[`${key}:exit`]?.(node, visitorServices);
|
|
558
|
+
};
|
|
559
|
+
visit(file.services.sourceFile);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
const typeScriptCoreSupportedExtensions = /* @__PURE__ */ new Set([
|
|
563
|
+
".cjs",
|
|
564
|
+
".cts",
|
|
565
|
+
".d.cts",
|
|
566
|
+
".d.mts",
|
|
567
|
+
".d.ts",
|
|
568
|
+
".js",
|
|
569
|
+
".json",
|
|
570
|
+
".jsx",
|
|
571
|
+
".mjs",
|
|
572
|
+
".mts",
|
|
573
|
+
".ts",
|
|
574
|
+
".tsx"
|
|
575
|
+
]);
|
|
576
|
+
const fileExtToFlintPlugin = {
|
|
577
|
+
".astro": "@flint.fyi/astro",
|
|
578
|
+
".gjs": "@flint.fyi/ember",
|
|
579
|
+
".gts": "@flint.fyi/ember",
|
|
580
|
+
".mdx": "@flint.fyi/mdx",
|
|
581
|
+
".svelte": "@flint.fyi/svelte",
|
|
582
|
+
".vue": "@flint.fyi/vue"
|
|
583
|
+
};
|
|
584
|
+
function throwUnknownLanguageExtension(filename) {
|
|
585
|
+
const pluginName = fileExtToFlintPlugin[path.extname(filename)];
|
|
586
|
+
const message = pluginName ? `Did you install & import ${pluginName}?` : "Unknown extension.";
|
|
587
|
+
throw new Error(`Cannot process ${filename}. ${message}`);
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/utils/forEachChild.ts
|
|
591
|
+
const forEachChild = ts.forEachChild;
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/scope/identifierReferences.ts
|
|
594
|
+
function isNonReferenceIdentifier(identifier) {
|
|
595
|
+
if (isIdentifierDeclaration(identifier) || isTypeReferenceIdentifier(identifier)) return true;
|
|
596
|
+
const { parent } = identifier;
|
|
597
|
+
switch (parent.kind) {
|
|
598
|
+
case SyntaxKind.BindingElement:
|
|
599
|
+
case SyntaxKind.ImportSpecifier: return parent.propertyName === identifier;
|
|
600
|
+
case SyntaxKind.BreakStatement:
|
|
601
|
+
case SyntaxKind.ContinueStatement:
|
|
602
|
+
case SyntaxKind.LabeledStatement: return parent.label === identifier;
|
|
603
|
+
case SyntaxKind.JsxAttribute:
|
|
604
|
+
case SyntaxKind.MethodDeclaration:
|
|
605
|
+
case SyntaxKind.MethodSignature:
|
|
606
|
+
case SyntaxKind.PropertyAccessExpression:
|
|
607
|
+
case SyntaxKind.PropertyAssignment:
|
|
608
|
+
case SyntaxKind.PropertyDeclaration:
|
|
609
|
+
case SyntaxKind.PropertySignature: return parent.name === identifier;
|
|
610
|
+
}
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
function isWriteReference(identifier) {
|
|
614
|
+
const { parent } = identifier;
|
|
615
|
+
if (isAssignmentTarget(identifier)) return true;
|
|
616
|
+
switch (parent.kind) {
|
|
617
|
+
case SyntaxKind.PostfixUnaryExpression:
|
|
618
|
+
case SyntaxKind.PrefixUnaryExpression: return parent.operand === identifier && (parent.operator === SyntaxKind.PlusPlusToken || parent.operator === SyntaxKind.MinusMinusToken);
|
|
619
|
+
default: return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function isAssignmentTarget(identifier) {
|
|
623
|
+
let current = identifier;
|
|
624
|
+
while (true) {
|
|
625
|
+
const parent = current.parent;
|
|
626
|
+
switch (parent.kind) {
|
|
627
|
+
case SyntaxKind.ArrayLiteralExpression:
|
|
628
|
+
case SyntaxKind.ObjectLiteralExpression:
|
|
629
|
+
case SyntaxKind.ShorthandPropertyAssignment:
|
|
630
|
+
current = parent;
|
|
631
|
+
continue;
|
|
632
|
+
case SyntaxKind.PropertyAssignment:
|
|
633
|
+
if (parent.name === current) return false;
|
|
634
|
+
current = parent;
|
|
635
|
+
continue;
|
|
636
|
+
case SyntaxKind.BinaryExpression: return parent.left === current && tsutils.isAssignmentKind(parent.operatorToken.kind);
|
|
637
|
+
case SyntaxKind.ForInStatement:
|
|
638
|
+
case SyntaxKind.ForOfStatement: return parent.initializer === current;
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function isIdentifierDeclaration(identifier) {
|
|
644
|
+
const { parent } = identifier;
|
|
645
|
+
switch (parent.kind) {
|
|
646
|
+
case SyntaxKind.BindingElement:
|
|
647
|
+
case SyntaxKind.Parameter:
|
|
648
|
+
case SyntaxKind.VariableDeclaration: return isIdentifierWithinParent(identifier, parent.name);
|
|
649
|
+
case SyntaxKind.ClassDeclaration:
|
|
650
|
+
case SyntaxKind.ClassExpression:
|
|
651
|
+
case SyntaxKind.FunctionDeclaration:
|
|
652
|
+
case SyntaxKind.FunctionExpression:
|
|
653
|
+
case SyntaxKind.InterfaceDeclaration:
|
|
654
|
+
case SyntaxKind.ModuleDeclaration:
|
|
655
|
+
case SyntaxKind.TypeAliasDeclaration: return parent.name === identifier;
|
|
656
|
+
case SyntaxKind.ImportClause:
|
|
657
|
+
case SyntaxKind.ImportSpecifier:
|
|
658
|
+
case SyntaxKind.NamespaceImport: return parent.name === identifier;
|
|
659
|
+
}
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
function isIdentifierWithinParent(identifier, parent) {
|
|
663
|
+
let current = identifier;
|
|
664
|
+
while (current !== parent) {
|
|
665
|
+
const next = current.parent;
|
|
666
|
+
if (!next) return false;
|
|
667
|
+
current = next;
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
function isTypeReferenceIdentifier(identifier) {
|
|
672
|
+
let current = identifier;
|
|
673
|
+
while (current.parent.kind === SyntaxKind.QualifiedName) current = current.parent;
|
|
674
|
+
const parent = current.parent;
|
|
675
|
+
if (parent.kind !== SyntaxKind.TypeReference) return false;
|
|
676
|
+
return isIdentifierWithinParent(identifier, parent.typeName);
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/scope/scopes.ts
|
|
680
|
+
function createScope(block, upper) {
|
|
681
|
+
const variablesByName = /* @__PURE__ */ new Map();
|
|
682
|
+
const scope = {
|
|
683
|
+
block,
|
|
684
|
+
childScopes: [],
|
|
685
|
+
references: [],
|
|
686
|
+
upper,
|
|
687
|
+
variables: [],
|
|
688
|
+
variablesByName
|
|
689
|
+
};
|
|
690
|
+
Object.defineProperty(scope, "variablesByName", {
|
|
691
|
+
enumerable: false,
|
|
692
|
+
value: variablesByName
|
|
693
|
+
});
|
|
694
|
+
upper?.childScopes.push(scope);
|
|
695
|
+
return scope;
|
|
696
|
+
}
|
|
697
|
+
function getReferencesInScope(scope) {
|
|
698
|
+
const references = [...scope.references];
|
|
699
|
+
for (const childScope of scope.childScopes) if (!isFunctionLike(childScope.block)) references.push(...getReferencesInScope(childScope));
|
|
700
|
+
return references;
|
|
701
|
+
}
|
|
702
|
+
function getVariableDeclarationScope(node, scope) {
|
|
703
|
+
if (node.parent.kind === SyntaxKind.VariableDeclarationList && !(node.parent.flags & ts.NodeFlags.BlockScoped)) return findContainingVariableScope(scope);
|
|
704
|
+
return scope;
|
|
705
|
+
}
|
|
706
|
+
function isScopeBoundary(node) {
|
|
707
|
+
return isFunctionLike(node) || node.kind === SyntaxKind.CatchClause || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.ForInStatement || node.kind === SyntaxKind.ForOfStatement || node.kind === SyntaxKind.ForStatement || isBlockScopeBoundary(node);
|
|
708
|
+
}
|
|
709
|
+
function resolveVariable(scope, name) {
|
|
710
|
+
let current = scope;
|
|
711
|
+
while (current) {
|
|
712
|
+
const variable = current.variablesByName.get(name);
|
|
713
|
+
if (variable) return variable;
|
|
714
|
+
current = current.upper;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function findContainingVariableScope(scope) {
|
|
718
|
+
let current = scope;
|
|
719
|
+
while (current.upper && !isVariableScopeBoundary(current.block)) current = current.upper;
|
|
720
|
+
return current;
|
|
721
|
+
}
|
|
722
|
+
function isBlockScopeBoundary(node) {
|
|
723
|
+
return node.kind === SyntaxKind.Block && node.parent.kind !== SyntaxKind.CatchClause && !isFunctionLike(node.parent);
|
|
724
|
+
}
|
|
725
|
+
function isFunctionLike(node) {
|
|
726
|
+
switch (node?.kind) {
|
|
727
|
+
case SyntaxKind.ArrowFunction:
|
|
728
|
+
case SyntaxKind.Constructor:
|
|
729
|
+
case SyntaxKind.FunctionDeclaration:
|
|
730
|
+
case SyntaxKind.FunctionExpression:
|
|
731
|
+
case SyntaxKind.GetAccessor:
|
|
732
|
+
case SyntaxKind.MethodDeclaration:
|
|
733
|
+
case SyntaxKind.SetAccessor: return true;
|
|
734
|
+
default: return false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function isVariableScopeBoundary(node) {
|
|
738
|
+
return node.kind === SyntaxKind.SourceFile || isFunctionLike(node);
|
|
739
|
+
}
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/scope/scopeManager.ts
|
|
742
|
+
const scopeManagers = new WeakCachedFactory(createScopeManager);
|
|
743
|
+
function getScopeManager(sourceFile) {
|
|
744
|
+
return scopeManagers.get(sourceFile);
|
|
745
|
+
}
|
|
746
|
+
function createScopeManager(sourceFile) {
|
|
747
|
+
const declarationVariablesByIdentifier = /* @__PURE__ */ new WeakMap();
|
|
748
|
+
const declaredVariablesByNode = /* @__PURE__ */ new WeakMap();
|
|
749
|
+
const nodeScopes = /* @__PURE__ */ new WeakMap();
|
|
750
|
+
const globalScope = createScope(sourceFile, void 0);
|
|
751
|
+
function addBindingName(scope, name, node, kind) {
|
|
752
|
+
if (name.kind === SyntaxKind.Identifier) return [addVariable(scope, name, node, kind)];
|
|
753
|
+
const variables = [];
|
|
754
|
+
for (const element of name.elements) if (element.kind !== SyntaxKind.OmittedExpression) variables.push(...addBindingName(scope, element.name, node, kind));
|
|
755
|
+
return variables;
|
|
756
|
+
}
|
|
757
|
+
function addImportVariables(scope, node) {
|
|
758
|
+
if (!node.importClause || node.importClause.isTypeOnly) return;
|
|
759
|
+
if (node.importClause.name) addVariable(scope, node.importClause.name, node, "import");
|
|
760
|
+
const { namedBindings } = node.importClause;
|
|
761
|
+
if (!namedBindings) return;
|
|
762
|
+
if (namedBindings.kind === SyntaxKind.NamespaceImport) {
|
|
763
|
+
addVariable(scope, namedBindings.name, node, "import");
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
for (const element of namedBindings.elements) {
|
|
767
|
+
if (element.isTypeOnly) continue;
|
|
768
|
+
addVariable(scope, element.name, node, "import");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function addParameters(scope, node) {
|
|
772
|
+
for (const parameter of node.parameters) {
|
|
773
|
+
const variables = addBindingName(scope, parameter.name, node, "parameter");
|
|
774
|
+
declaredVariablesByNode.set(parameter, variables);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function addVariable(scope, identifier, node, kind) {
|
|
778
|
+
let variable = scope.variablesByName.get(identifier.text);
|
|
779
|
+
if (!variable) {
|
|
780
|
+
variable = {
|
|
781
|
+
declarations: [],
|
|
782
|
+
definitions: [],
|
|
783
|
+
name: identifier.text,
|
|
784
|
+
references: [],
|
|
785
|
+
scope
|
|
786
|
+
};
|
|
787
|
+
scope.variablesByName.set(identifier.text, variable);
|
|
788
|
+
scope.variables.push(variable);
|
|
789
|
+
}
|
|
790
|
+
variable.declarations.push(identifier);
|
|
791
|
+
variable.definitions.push({
|
|
792
|
+
identifier,
|
|
793
|
+
kind,
|
|
794
|
+
node
|
|
795
|
+
});
|
|
796
|
+
declarationVariablesByIdentifier.set(identifier, variable);
|
|
797
|
+
const declaredVariables = declaredVariablesByNode.get(node) ?? [];
|
|
798
|
+
declaredVariables.push(variable);
|
|
799
|
+
declaredVariablesByNode.set(node, declaredVariables);
|
|
800
|
+
return variable;
|
|
801
|
+
}
|
|
802
|
+
function collectDeclarations(node, scope) {
|
|
803
|
+
const nodeScope = node !== sourceFile && isScopeBoundary(node) ? createScope(node, scope) : scope;
|
|
804
|
+
nodeScopes.set(node, nodeScope);
|
|
805
|
+
switch (node.kind) {
|
|
806
|
+
case SyntaxKind.ArrowFunction:
|
|
807
|
+
case SyntaxKind.Constructor:
|
|
808
|
+
case SyntaxKind.GetAccessor:
|
|
809
|
+
case SyntaxKind.MethodDeclaration:
|
|
810
|
+
case SyntaxKind.SetAccessor:
|
|
811
|
+
addParameters(nodeScope, node);
|
|
812
|
+
break;
|
|
813
|
+
case SyntaxKind.CatchClause:
|
|
814
|
+
if (node.variableDeclaration) addBindingName(nodeScope, node.variableDeclaration.name, node, "catch");
|
|
815
|
+
break;
|
|
816
|
+
case SyntaxKind.ClassDeclaration:
|
|
817
|
+
if (node.name) addVariable(nodeScope, node.name, node, "class");
|
|
818
|
+
break;
|
|
819
|
+
case SyntaxKind.ClassExpression:
|
|
820
|
+
if (node.name) addVariable(nodeScope, node.name, node, "class");
|
|
821
|
+
break;
|
|
822
|
+
case SyntaxKind.FunctionDeclaration:
|
|
823
|
+
if (node.name) addVariable(scope, node.name, node, "function");
|
|
824
|
+
addParameters(nodeScope, node);
|
|
825
|
+
break;
|
|
826
|
+
case SyntaxKind.FunctionExpression:
|
|
827
|
+
if (node.name) addVariable(nodeScope, node.name, node, "function");
|
|
828
|
+
addParameters(nodeScope, node);
|
|
829
|
+
break;
|
|
830
|
+
case SyntaxKind.ImportDeclaration:
|
|
831
|
+
addImportVariables(nodeScope, node);
|
|
832
|
+
break;
|
|
833
|
+
case SyntaxKind.VariableDeclaration:
|
|
834
|
+
if (node.parent.kind === SyntaxKind.CatchClause) {
|
|
835
|
+
forEachChild(node, (child) => {
|
|
836
|
+
collectDeclarations(child, nodeScope);
|
|
837
|
+
});
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
addBindingName(getVariableDeclarationScope(node, nodeScope), node.name, node, "variable");
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
forEachChild(node, (child) => {
|
|
844
|
+
collectDeclarations(child, nodeScope);
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
function collectReferences(node) {
|
|
848
|
+
if (node.kind === SyntaxKind.Identifier && !isNonReferenceIdentifier(node)) {
|
|
849
|
+
const scope = nodeScopes.get(node) ?? globalScope;
|
|
850
|
+
const variable = resolveVariable(scope, node.text);
|
|
851
|
+
const reference = {
|
|
852
|
+
from: scope,
|
|
853
|
+
identifier: node,
|
|
854
|
+
isWrite: isWriteReference(node),
|
|
855
|
+
text: node.text,
|
|
856
|
+
variable
|
|
857
|
+
};
|
|
858
|
+
scope.references.push(reference);
|
|
859
|
+
variable?.references.push(reference);
|
|
860
|
+
}
|
|
861
|
+
forEachChild(node, collectReferences);
|
|
862
|
+
}
|
|
863
|
+
collectDeclarations(sourceFile, globalScope);
|
|
864
|
+
collectReferences(sourceFile);
|
|
865
|
+
return {
|
|
866
|
+
findVariable(identifier) {
|
|
867
|
+
const declarationVariable = declarationVariablesByIdentifier.get(identifier);
|
|
868
|
+
if (declarationVariable) return declarationVariable;
|
|
869
|
+
return resolveVariable(nodeScopes.get(identifier) ?? globalScope, identifier.text);
|
|
870
|
+
},
|
|
871
|
+
getDeclaredVariables(node) {
|
|
872
|
+
return declaredVariablesByNode.get(node) ?? [];
|
|
873
|
+
},
|
|
874
|
+
getReferencesInScope(node) {
|
|
875
|
+
return getReferencesInScope(nodeScopes.get(node) ?? globalScope);
|
|
876
|
+
},
|
|
877
|
+
getScope(node) {
|
|
878
|
+
return nodeScopes.get(node) ?? globalScope;
|
|
879
|
+
},
|
|
880
|
+
globalScope
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region src/utils/createRuleTesterTSConfig.ts
|
|
885
|
+
function createRuleTesterTSConfig(defaultCompilerOptions) {
|
|
886
|
+
return {
|
|
887
|
+
"tsconfig.base.json": JSON.stringify({ compilerOptions: {
|
|
888
|
+
lib: ["esnext"],
|
|
889
|
+
moduleResolution: "bundler",
|
|
890
|
+
strict: true,
|
|
891
|
+
target: "esnext",
|
|
892
|
+
types: [],
|
|
893
|
+
...defaultCompilerOptions
|
|
894
|
+
} }, null, 2),
|
|
895
|
+
"tsconfig.json": JSON.stringify({ extends: "./tsconfig.base.json" }, null, 2)
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
//#endregion
|
|
899
|
+
//#region src/utils/declarationIncludesGlobal.ts
|
|
900
|
+
function declarationIncludesGlobal(declaration, program) {
|
|
901
|
+
const sourceFile = declaration.getSourceFile();
|
|
902
|
+
return program.isSourceFileDefaultLibrary(sourceFile) || /\/lib\.[^/]*\.d\.ts$/.test(sourceFile.fileName);
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/utils/declarationsIncludeGlobal.ts
|
|
906
|
+
function declarationsIncludeGlobal(declarations, program) {
|
|
907
|
+
return declarations.some((declaration) => declarationIncludesGlobal(declaration, program));
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/utils/getDeclarationsIfGlobal.ts
|
|
911
|
+
function getDeclarationsIfGlobal(node, typeChecker, program) {
|
|
912
|
+
const declarations = typeChecker.getSymbolAtLocation(node)?.getDeclarations();
|
|
913
|
+
return !!declarations && declarationsIncludeGlobal(declarations, program) ? declarations : void 0;
|
|
914
|
+
}
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/utils/getModifyingReferences.ts
|
|
917
|
+
/**
|
|
918
|
+
* Gets all references to a variable that modify it (assignments, increments, decrements).
|
|
919
|
+
* @returns An array of identifier nodes that modify the variable.
|
|
920
|
+
*/
|
|
921
|
+
function getModifyingReferences(identifier, sourceFile) {
|
|
922
|
+
const variable = getScopeManager(sourceFile).findVariable(identifier);
|
|
923
|
+
if (!variable || variable.declarations.length > 1) return [];
|
|
924
|
+
return variable.references.filter((reference) => reference.isWrite).map((reference) => reference.identifier);
|
|
925
|
+
}
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region src/utils/unwrapParenthesizedNode.ts
|
|
928
|
+
function unwrapParenthesizedNode(node) {
|
|
929
|
+
return node.kind === SyntaxKind.ParenthesizedExpression ? unwrapParenthesizedNode(node.expression) : node;
|
|
930
|
+
}
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/utils/getStaticValue.ts
|
|
933
|
+
function getStaticNumberValue(node) {
|
|
934
|
+
const value = getStaticValue(node)?.value;
|
|
935
|
+
return typeof value === "number" ? value : void 0;
|
|
936
|
+
}
|
|
937
|
+
function getStaticStringValue(node) {
|
|
938
|
+
const value = getStaticValue(node)?.value;
|
|
939
|
+
return typeof value === "string" ? value : void 0;
|
|
940
|
+
}
|
|
941
|
+
function getStaticValue(node) {
|
|
942
|
+
const unwrapped = unwrapParenthesizedNode(node);
|
|
943
|
+
switch (unwrapped.kind) {
|
|
944
|
+
case SyntaxKind.AsExpression:
|
|
945
|
+
case SyntaxKind.NonNullExpression:
|
|
946
|
+
case SyntaxKind.SatisfiesExpression:
|
|
947
|
+
case SyntaxKind.TypeAssertionExpression: return getStaticValue(unwrapped.expression);
|
|
948
|
+
case SyntaxKind.BigIntLiteral: return { value: BigInt(unwrapped.text.slice(0, -1)) };
|
|
949
|
+
case SyntaxKind.FalseKeyword: return { value: false };
|
|
950
|
+
case SyntaxKind.NoSubstitutionTemplateLiteral: return { value: unwrapped.text };
|
|
951
|
+
case SyntaxKind.NullKeyword: return { value: null };
|
|
952
|
+
case SyntaxKind.NumericLiteral: return { value: Number(unwrapped.text) };
|
|
953
|
+
case SyntaxKind.PrefixUnaryExpression: return getPrefixUnaryStaticValue(unwrapped);
|
|
954
|
+
case SyntaxKind.StringLiteral: return { value: unwrapped.text };
|
|
955
|
+
case SyntaxKind.TrueKeyword: return { value: true };
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function getPrefixUnaryStaticValue(node) {
|
|
959
|
+
const operand = getStaticValue(node.operand);
|
|
960
|
+
if (!operand) return;
|
|
961
|
+
switch (node.operator) {
|
|
962
|
+
case SyntaxKind.ExclamationToken: return { value: !operand.value };
|
|
963
|
+
case SyntaxKind.MinusToken:
|
|
964
|
+
if (typeof operand.value === "bigint" || typeof operand.value === "number") return { value: -operand.value };
|
|
965
|
+
return;
|
|
966
|
+
case SyntaxKind.PlusToken: return typeof operand.value === "number" ? { value: operand.value } : void 0;
|
|
967
|
+
case SyntaxKind.TildeToken: if (typeof operand.value === "bigint" || typeof operand.value === "number") return { value: ~operand.value };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/utils/hasSameTokens.ts
|
|
972
|
+
function hasSameTokens(nodeA, nodeB, sourceFile) {
|
|
973
|
+
const queueA = [unwrapParenthesizedNode(nodeA)];
|
|
974
|
+
const queueB = [unwrapParenthesizedNode(nodeB)];
|
|
975
|
+
while (true) {
|
|
976
|
+
const currentA = queueA.shift();
|
|
977
|
+
const currentB = queueB.shift();
|
|
978
|
+
if (!currentA || !currentB) break;
|
|
979
|
+
if (currentA.kind !== currentB.kind) return false;
|
|
980
|
+
if (ts.isTokenKind(currentA.kind)) {
|
|
981
|
+
if (!areSameToken(currentA, currentB, sourceFile)) return false;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const childrenA = currentA.getChildren(sourceFile);
|
|
985
|
+
const childrenB = currentB.getChildren(sourceFile);
|
|
986
|
+
if (childrenA.length !== childrenB.length) return false;
|
|
987
|
+
queueA.push(...childrenA);
|
|
988
|
+
queueB.push(...childrenB);
|
|
989
|
+
}
|
|
990
|
+
return queueA.length === queueB.length;
|
|
991
|
+
}
|
|
992
|
+
function areSameToken(nodeA, nodeB, sourceFile) {
|
|
993
|
+
if (ts.isIdentifier(nodeA) || ts.isPrivateIdentifier(nodeA) || ts.isNumericLiteral(nodeA) || ts.isBigIntLiteral(nodeA) || ts.isStringLiteral(nodeA) || ts.isNoSubstitutionTemplateLiteral(nodeA)) return nodeA.text === nodeB.text;
|
|
994
|
+
if (nodeA.kind === SyntaxKind.RegularExpressionLiteral) return sourceFile.text.slice(nodeA.getStart(sourceFile), nodeA.getEnd()) === sourceFile.text.slice(nodeB.getStart(sourceFile), nodeB.getEnd());
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/utils/isBuiltinArrayMethod.ts
|
|
999
|
+
function isBuiltinArrayMethod(name, node, typeChecker) {
|
|
1000
|
+
return node.expression.kind === SyntaxKind.PropertyAccessExpression && node.expression.name.text === name && typeChecker.isArrayType(typeChecker.getTypeAtLocation(node.expression.expression)) && node.parent.kind !== SyntaxKind.ExpressionStatement;
|
|
1001
|
+
}
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/utils/isFunction.ts
|
|
1004
|
+
function isFunction(node, typeChecker) {
|
|
1005
|
+
return !!typeChecker.getTypeAtLocation(node).getCallSignatures().length;
|
|
1006
|
+
}
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/utils/isGlobalDeclaration.ts
|
|
1009
|
+
function isGlobalDeclaration(node, typeChecker, program) {
|
|
1010
|
+
return !!getDeclarationsIfGlobal(node, typeChecker, program);
|
|
1011
|
+
}
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region src/utils/isGlobalDeclarationOfName.ts
|
|
1014
|
+
/**
|
|
1015
|
+
* TODO: Use a scope analyzer (#400).
|
|
1016
|
+
*/
|
|
1017
|
+
function isGlobalDeclarationOfName(node, name, typeChecker, program) {
|
|
1018
|
+
const declarations = typeChecker.getSymbolAtLocation(node)?.getDeclarations();
|
|
1019
|
+
if (!declarations) return false;
|
|
1020
|
+
return declarations.every((declaration) => {
|
|
1021
|
+
if (isVariableDeclaration(declaration) && declaration.initializer && isIdentifier(declaration.initializer)) return isGlobalDeclarationOfName(declaration.initializer, name, typeChecker, program);
|
|
1022
|
+
if (isPropertySignature(declaration)) return isGlobalDeclarationOfName(declaration.parent, name, typeChecker, program);
|
|
1023
|
+
return isDeclarationOfName(declaration, name) && declarationIncludesGlobal(declaration, program);
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function isDeclarationOfName(node, name) {
|
|
1027
|
+
if (isClassDeclaration(node) || isFunctionDeclaration(node) || isInterfaceDeclaration(node) || isVariableDeclaration(node)) return node.name && isIdentifier(node.name) && node.name.text === name;
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
//#endregion
|
|
1031
|
+
//#region src/utils/isGlobalVariable.ts
|
|
1032
|
+
/**
|
|
1033
|
+
* Checks if a node is a reference to a global variable (e.g., Object, undefined, NaN).
|
|
1034
|
+
* Global variables are those declared in TypeScript's lib files or are global identifiers
|
|
1035
|
+
* like undefined which have symbols but no user-defined declarations.
|
|
1036
|
+
* TODO: Use a scope manager (#400).
|
|
1037
|
+
*/
|
|
1038
|
+
function isGlobalVariable(node, typeChecker, program) {
|
|
1039
|
+
const symbol = typeChecker.getSymbolAtLocation(node);
|
|
1040
|
+
if (!symbol) return false;
|
|
1041
|
+
const declarations = symbol.getDeclarations();
|
|
1042
|
+
if (!declarations?.length) return true;
|
|
1043
|
+
return declarationsIncludeGlobal(declarations, program);
|
|
1044
|
+
}
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/utils/isInlineArrayCreation.ts
|
|
1047
|
+
const methodsReturningNewArray = /* @__PURE__ */ new Set([
|
|
1048
|
+
"concat",
|
|
1049
|
+
"entries",
|
|
1050
|
+
"filter",
|
|
1051
|
+
"flat",
|
|
1052
|
+
"flatMap",
|
|
1053
|
+
"from",
|
|
1054
|
+
"keys",
|
|
1055
|
+
"map",
|
|
1056
|
+
"of",
|
|
1057
|
+
"slice",
|
|
1058
|
+
"split",
|
|
1059
|
+
"values"
|
|
1060
|
+
]);
|
|
1061
|
+
const objectStaticMethods = /* @__PURE__ */ new Set([
|
|
1062
|
+
"entries",
|
|
1063
|
+
"keys",
|
|
1064
|
+
"values"
|
|
1065
|
+
]);
|
|
1066
|
+
/**
|
|
1067
|
+
* Checks if a node represents an inline array creation expression.
|
|
1068
|
+
* These are cases where a new array is created immediately before the method call,
|
|
1069
|
+
* so mutating methods like .sort() or .reverse() are safe to use.
|
|
1070
|
+
*/
|
|
1071
|
+
function isInlineArrayCreation(node) {
|
|
1072
|
+
if (ts.isArrayLiteralExpression(node)) return true;
|
|
1073
|
+
if (ts.isParenthesizedExpression(node)) return isInlineArrayCreation(node.expression);
|
|
1074
|
+
if (ts.isCallExpression(node)) {
|
|
1075
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
1076
|
+
const methodName = node.expression.name.text;
|
|
1077
|
+
if (ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Object" && objectStaticMethods.has(methodName)) return true;
|
|
1078
|
+
if (ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Array" && (methodName === "from" || methodName === "of")) return true;
|
|
1079
|
+
if (methodsReturningNewArray.has(methodName)) return true;
|
|
1080
|
+
}
|
|
1081
|
+
if (ts.isIdentifier(node.expression) && node.expression.text === "Array" && ts.isNewExpression(node.parent)) return true;
|
|
1082
|
+
}
|
|
1083
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "Array") return true;
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/utils/isStaticString.ts
|
|
1088
|
+
function isStaticString(node) {
|
|
1089
|
+
return node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
1090
|
+
}
|
|
1091
|
+
//#endregion
|
|
1092
|
+
//#region src/utils/isStringRawNoSubstitution.ts
|
|
1093
|
+
function isStringRawNoSubstitution(node) {
|
|
1094
|
+
if (node.kind !== SyntaxKind.TaggedTemplateExpression) return false;
|
|
1095
|
+
const tag = node.tag;
|
|
1096
|
+
return tag.kind === SyntaxKind.PropertyAccessExpression && tag.expression.kind === SyntaxKind.Identifier && tag.expression.text === "String" && tag.name.kind === SyntaxKind.Identifier && tag.name.text === "raw" && node.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
|
|
1097
|
+
}
|
|
1098
|
+
//#endregion
|
|
1099
|
+
//#region src/utils/unwrapParentParenthesizedExpressions.ts
|
|
1100
|
+
function unwrapParentParenthesizedExpressions(node) {
|
|
1101
|
+
return node.parent.kind === SyntaxKind.ParenthesizedExpression ? unwrapParentParenthesizedExpressions(node.parent) : node.parent;
|
|
1102
|
+
}
|
|
1103
|
+
//#endregion
|
|
1104
|
+
export { NodeSyntaxKinds, convertTypeScriptDiagnosticToLanguageReport, createRuleTesterTSConfig, declarationIncludesGlobal, extractDirectivesFromTypeScriptFile, forEachChild, getDeclarationsIfGlobal, getModifyingReferences, getScopeManager, getStaticNumberValue, getStaticStringValue, getStaticValue, getTSNodeRange, hasSameTokens, isBuiltinArrayMethod, isFunction, isGlobalDeclaration, isGlobalDeclarationOfName, isGlobalVariable, isInlineArrayCreation, isStaticString, isStringRawNoSubstitution, setVolarCreateFile, throwUnknownLanguageExtension, typescriptLanguage, unwrapParentParenthesizedExpressions, unwrapParenthesizedNode };
|