@cplieger/deadset-ts 7.2.0-dev.3 → 7.2.0-dev.4
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/dist/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +10 -6
- package/dist/src/component-files.js +96 -31
- package/dist/src/configuration-files.js +4 -3
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/dependencies.js +2 -2
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +68 -86
- package/dist/src/edges.js +1 -1
- package/dist/src/intra-function-parts.js +17 -6
- package/dist/src/inventory.js +11 -0
- package/dist/src/json-document.js +5 -5
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +7 -2
- package/dist/src/paths.js +9 -0
- package/dist/src/references.js +25 -12
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/serialization-contract.js +43 -12
- package/dist/src/session.js +19 -1
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +2 -2
- package/dist/src/suppress-inline.js +3 -1
- package/dist/src/template-field.js +9 -6
- package/dist/src/type-errors.js +82 -28
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +3 -1
- package/dist/src/version-range.js +10 -5
- package/dist/src/workspace-resolution.js +19 -3
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +32 -6
- package/src/component-files.ts +116 -31
- package/src/configuration-files.ts +14 -6
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +27 -27
- package/src/dependencies.ts +2 -2
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +92 -87
- package/src/edges.ts +1 -1
- package/src/intra-function-parts.ts +21 -5
- package/src/inventory.ts +12 -0
- package/src/json-document.ts +7 -7
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +10 -2
- package/src/paths.ts +10 -0
- package/src/references.ts +28 -16
- package/src/reflective-lookup.ts +9 -7
- package/src/reporters/template.ts +21 -13
- package/src/serialization-contract.ts +53 -13
- package/src/session.ts +22 -1
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +2 -2
- package/src/suppress-inline.ts +3 -1
- package/src/template-field.ts +9 -6
- package/src/type-errors.ts +94 -35
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +6 -1
- package/src/version-range.ts +10 -5
- package/src/workspace-resolution.ts +24 -4
- package/src/workspace.ts +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* https://github.com/microsoft/TypeScript/blob/f9f8d01292562242b6e7c7142e46ed7e470926f9/tsc/internal/contentmapper/hostimpl.go
|
|
7
7
|
*/
|
|
8
8
|
import { componentModule } from "../src/component-files.js";
|
|
9
|
+
import { extensionOf } from "../src/paths.js";
|
|
9
10
|
/** The JSON-RPC code of a method this mapper does not serve. */
|
|
10
11
|
const METHOD_NOT_FOUND = -32601;
|
|
11
12
|
/** The JSON-RPC code of a request whose parameters are not the protocol's. */
|
|
@@ -42,13 +43,15 @@ function answer(message) {
|
|
|
42
43
|
case "closeProject":
|
|
43
44
|
return reply(null);
|
|
44
45
|
case "transform": {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
: undefined;
|
|
46
|
+
const params = typeof message.params === "object" && message.params !== null ? message.params : {};
|
|
47
|
+
const { content, fileName } = params;
|
|
48
48
|
if (typeof content !== "string") {
|
|
49
49
|
return fail(INVALID_PARAMS, "transform: params.content is not a string");
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
if (typeof fileName !== "string") {
|
|
52
|
+
return fail(INVALID_PARAMS, "transform: params.fileName is not a string");
|
|
53
|
+
}
|
|
54
|
+
const module = componentModule(content, extensionOf(fileName));
|
|
52
55
|
return reply({ text: module.text, extension: module.extension, mappings: module.mappings });
|
|
53
56
|
}
|
|
54
57
|
default:
|
package/dist/src/analysis.js
CHANGED
|
@@ -32,7 +32,7 @@ import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
|
|
|
32
32
|
import { inventory } from "./inventory.js";
|
|
33
33
|
import { packageEntries, readManifest } from "./manifest.js";
|
|
34
34
|
import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
35
|
-
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
35
|
+
import { dirnamePath, extensionOf, joinPath, normalizePath, relativePath, resolvePath, } from "./paths.js";
|
|
36
36
|
import { byPosition, isComponentFile } from "./position.js";
|
|
37
37
|
import { LANGUAGE } from "./report.js";
|
|
38
38
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
@@ -175,6 +175,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
175
175
|
const failures = [];
|
|
176
176
|
const unbuildable = [];
|
|
177
177
|
const setupDropped = [];
|
|
178
|
+
const librariesUnfound = [];
|
|
178
179
|
const consumed = new Map();
|
|
179
180
|
const heldFiles = new Set();
|
|
180
181
|
let outsideTests = [];
|
|
@@ -227,10 +228,6 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
227
228
|
: resolvePath(host.workingDirectory(), consumer.path),
|
|
228
229
|
paths: pathsOf(engine, project.configFile),
|
|
229
230
|
});
|
|
230
|
-
if (reading.failing.length > 0) {
|
|
231
|
-
failures.push(...reading.failing);
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
231
|
if (consumer !== undefined && reading.missing.length > 0) {
|
|
235
232
|
unbuildable.push(...reading.missing.map((one) => ({
|
|
236
233
|
setupClass: "missing-consumer",
|
|
@@ -258,6 +255,10 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
258
255
|
}
|
|
259
256
|
return undefined;
|
|
260
257
|
}
|
|
258
|
+
librariesUnfound.push(...raw.unfound.map((one) => ({
|
|
259
|
+
configFile: project.configFile,
|
|
260
|
+
error: renderDiagnostic(one, (path) => relativePath(absoluteRoot, path) ?? path),
|
|
261
|
+
})));
|
|
261
262
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
262
263
|
const read = () => {
|
|
263
264
|
const held = inventory(project, host, targetRoot);
|
|
@@ -326,6 +327,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
326
327
|
notBuilt,
|
|
327
328
|
unanswered: session.unanswered,
|
|
328
329
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
330
|
+
librariesUnfound,
|
|
329
331
|
conventionsApplied: conventions.applied,
|
|
330
332
|
conventionUses: conventions.uses,
|
|
331
333
|
memberDirs,
|
|
@@ -439,7 +441,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
439
441
|
testFiles: resolved.testFilePaths,
|
|
440
442
|
evidence,
|
|
441
443
|
componentFiles: components.map(pathOf),
|
|
442
|
-
componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
|
|
444
|
+
componentWarnings: components.flatMap((file) => readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
|
|
443
445
|
path: pathOf(file),
|
|
444
446
|
line: lineOf(file.originalText, warning.offset),
|
|
445
447
|
reason: warning.reason,
|
|
@@ -528,6 +530,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
528
530
|
notBuilt: read.notBuilt,
|
|
529
531
|
unanswered: read.unanswered,
|
|
530
532
|
componentsUnread: read.componentsUnread,
|
|
533
|
+
librariesUnfound: read.librariesUnfound,
|
|
531
534
|
conventionsApplied: read.conventionsApplied,
|
|
532
535
|
conventionUses: read.conventionUses,
|
|
533
536
|
memberDirs: read.memberDirs,
|
|
@@ -796,6 +799,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
|
|
|
796
799
|
notBuilt: read.notBuilt,
|
|
797
800
|
unanswered: read.unanswered,
|
|
798
801
|
componentsUnread: read.componentsUnread,
|
|
802
|
+
librariesUnfound: read.librariesUnfound,
|
|
799
803
|
conventionsApplied: read.conventionsApplied,
|
|
800
804
|
},
|
|
801
805
|
configured: read.projects.map((one) => one.configured),
|
|
@@ -109,17 +109,17 @@ function scriptTagAt(text, offset) {
|
|
|
109
109
|
/^<script[\s/>]/iu.test(text.slice(offset, offset + 8)));
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
|
-
* The
|
|
113
|
-
*
|
|
114
|
-
*
|
|
112
|
+
* The braces opening at `from`, quoted strings skipped. The scan stops unclosed at the
|
|
113
|
+
* end, or before a `<script` start tag that begins a line, so a stray brace in markup
|
|
114
|
+
* never hides a block.
|
|
115
115
|
*/
|
|
116
|
-
function
|
|
116
|
+
function scanBraces(text, from) {
|
|
117
117
|
let depth = 0;
|
|
118
118
|
let at = from;
|
|
119
119
|
while (at < text.length) {
|
|
120
120
|
const code = text.charCodeAt(at);
|
|
121
121
|
if (scriptTagAt(text, at)) {
|
|
122
|
-
return
|
|
122
|
+
return { end: at, closed: false };
|
|
123
123
|
}
|
|
124
124
|
if (code === 0x22 || code === 0x27 || code === 0x60) {
|
|
125
125
|
at = pastQuoted(text, at);
|
|
@@ -131,24 +131,84 @@ function pastBraces(text, from) {
|
|
|
131
131
|
else if (code === 0x7d) {
|
|
132
132
|
depth -= 1;
|
|
133
133
|
if (depth === 0) {
|
|
134
|
-
return at + 1;
|
|
134
|
+
return { end: at + 1, closed: true };
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
at += 1;
|
|
138
138
|
}
|
|
139
|
-
return
|
|
139
|
+
return { end: text.length, closed: false };
|
|
140
|
+
}
|
|
141
|
+
/** The offset past the `}` matching the `{` at `from`, or -1 where {@link scanBraces} finds none. */
|
|
142
|
+
function pastBraces(text, from) {
|
|
143
|
+
const braces = scanBraces(text, from);
|
|
144
|
+
return braces.closed ? braces.end : -1;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The dialect each framework's parser reads: Vue's tokenizer, Svelte's parser and Astro's
|
|
148
|
+
* tokenizer, the last for any other extension too. `.vue` also closes `--!>`, which Vue
|
|
149
|
+
* does not, so a block Vue drops there is still read.
|
|
150
|
+
* https://github.com/vuejs/core/blob/main/packages/compiler-core/src/tokenizer.ts
|
|
151
|
+
* https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/phases/1-parse/state/element.js
|
|
152
|
+
* https://github.com/withastro/compiler/blob/main/internal/token.go
|
|
153
|
+
*/
|
|
154
|
+
function dialectOf(extension) {
|
|
155
|
+
switch (extension.toLowerCase()) {
|
|
156
|
+
case ".svelte":
|
|
157
|
+
return { shortComments: false, braceIsText: false };
|
|
158
|
+
case ".vue":
|
|
159
|
+
return { shortComments: true, braceIsText: true };
|
|
160
|
+
default:
|
|
161
|
+
return { shortComments: true, braceIsText: false };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** The offset past the comment whose `<!--` is at `lt`, or the text's length where none ends it. */
|
|
165
|
+
function pastComment(text, lt, dialect) {
|
|
166
|
+
if (!dialect.shortComments) {
|
|
167
|
+
const close = text.indexOf("-->", lt + 4);
|
|
168
|
+
return close < 0 ? text.length : close + 3;
|
|
169
|
+
}
|
|
170
|
+
const dashes = text.indexOf("-->", lt + 2);
|
|
171
|
+
const bang = text.indexOf("--!>", lt + 2);
|
|
172
|
+
if (bang >= 0 && (dashes < 0 || bang < dashes)) {
|
|
173
|
+
return bang + 4;
|
|
174
|
+
}
|
|
175
|
+
return dashes < 0 ? text.length : dashes + 3;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Where an unmatched `{` of a start tag ends its braced value: at the first `>`, or where
|
|
179
|
+
* the brace scan stopped where that comes first.
|
|
180
|
+
*/
|
|
181
|
+
function unmatchedBraceEnd(text, from, braces) {
|
|
182
|
+
const close = text.indexOf(">", from);
|
|
183
|
+
return close >= 0 && close < braces.end ? close : braces.end;
|
|
140
184
|
}
|
|
141
|
-
/**
|
|
142
|
-
|
|
185
|
+
/**
|
|
186
|
+
* The start tag whose `<` is at `from`, to its `>`. A quoted or braced value may hold `>`.
|
|
187
|
+
* Past an unmatched `{`, the tag also ends before a `<script` start tag that begins a line.
|
|
188
|
+
*/
|
|
189
|
+
function readStartTag(text, from, nameStop, dialect) {
|
|
143
190
|
const name = text.slice(from + 1, nameStop).toLowerCase();
|
|
144
191
|
const attributes = [];
|
|
145
192
|
let at = nameStop;
|
|
146
193
|
let selfClosing = false;
|
|
194
|
+
let unmatched = false;
|
|
195
|
+
/** The braces opening at `at`, and whether they make a braced value rather than text. */
|
|
196
|
+
const bracesAt = () => {
|
|
197
|
+
if (text.charCodeAt(at) !== 0x7b) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const braces = scanBraces(text, at);
|
|
201
|
+
return { braces, value: braces.closed || !dialect.braceIsText };
|
|
202
|
+
};
|
|
203
|
+
const bracedEnd = (braces) => braces.closed ? braces.end : unmatchedBraceEnd(text, at, braces);
|
|
147
204
|
while (at < text.length) {
|
|
148
205
|
const code = text.charCodeAt(at);
|
|
149
206
|
if (code === 0x3e) {
|
|
150
207
|
return { name, end: at + 1, selfClosing, attributes };
|
|
151
208
|
}
|
|
209
|
+
if (unmatched && scriptTagAt(text, at)) {
|
|
210
|
+
return { name, end: at, selfClosing, attributes };
|
|
211
|
+
}
|
|
152
212
|
if (isSpace(code)) {
|
|
153
213
|
at += 1;
|
|
154
214
|
continue;
|
|
@@ -160,12 +220,13 @@ function readStartTag(text, from, nameStop) {
|
|
|
160
220
|
}
|
|
161
221
|
selfClosing = false;
|
|
162
222
|
const start = at;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
223
|
+
const bare = bracesAt();
|
|
224
|
+
unmatched ||= bare?.braces.closed === false;
|
|
225
|
+
if (bare?.value === true) {
|
|
226
|
+
at = bracedEnd(bare.braces);
|
|
166
227
|
attributes.push({
|
|
167
228
|
name: "",
|
|
168
|
-
value: text.slice(start + 1, at - 1),
|
|
229
|
+
value: text.slice(start + 1, bare.braces.closed ? at - 1 : at),
|
|
169
230
|
start,
|
|
170
231
|
end: at,
|
|
171
232
|
valueStart: start + 1,
|
|
@@ -191,17 +252,19 @@ function readStartTag(text, from, nameStop) {
|
|
|
191
252
|
at += 1;
|
|
192
253
|
}
|
|
193
254
|
const opening = text.charCodeAt(at);
|
|
255
|
+
const braced = bracesAt();
|
|
256
|
+
unmatched ||= braced?.braces.closed === false;
|
|
194
257
|
if (opening === 0x22 || opening === 0x27) {
|
|
195
|
-
|
|
258
|
+
// An attribute value has no escapes: a backslash before its quote is text.
|
|
259
|
+
const close = text.indexOf(text.charAt(at), at + 1);
|
|
196
260
|
valueStart = at + 1;
|
|
197
|
-
value = text.slice(at + 1,
|
|
198
|
-
at =
|
|
261
|
+
value = text.slice(at + 1, close < 0 ? text.length : close);
|
|
262
|
+
at = close < 0 ? text.length : close + 1;
|
|
199
263
|
}
|
|
200
|
-
else if (
|
|
201
|
-
const
|
|
202
|
-
const stop = past < 0 ? text.length : past;
|
|
264
|
+
else if (braced?.value === true) {
|
|
265
|
+
const stop = bracedEnd(braced.braces);
|
|
203
266
|
valueStart = at + 1;
|
|
204
|
-
value = text.slice(at + 1,
|
|
267
|
+
value = text.slice(at + 1, braced.braces.closed ? stop - 1 : stop);
|
|
205
268
|
at = stop;
|
|
206
269
|
}
|
|
207
270
|
else {
|
|
@@ -305,9 +368,11 @@ function scriptsInside(text, from, to) {
|
|
|
305
368
|
* The scan keeps a stack of open elements. A comment, a doctype or processing instruction and
|
|
306
369
|
* an expression in braces are text, and an end tag closes back to the nearest open element of
|
|
307
370
|
* its name. A raw-text element at the top level runs to its own end tag, and only a `script`
|
|
308
|
-
* element whose start tag begins a line at the top level is a block.
|
|
371
|
+
* element whose start tag begins a line at the top level is a block. The file's extension
|
|
372
|
+
* names the framework whose reading of a comment and of a stray brace applies.
|
|
309
373
|
*/
|
|
310
|
-
export function readComponent(text) {
|
|
374
|
+
export function readComponent(text, extension) {
|
|
375
|
+
const dialect = dialectOf(extension);
|
|
311
376
|
const blocks = [];
|
|
312
377
|
const imports = [];
|
|
313
378
|
const warnings = [];
|
|
@@ -337,8 +402,7 @@ export function readComponent(text) {
|
|
|
337
402
|
}
|
|
338
403
|
const lt = at;
|
|
339
404
|
if (text.startsWith("<!--", lt)) {
|
|
340
|
-
|
|
341
|
-
at = close < 0 ? text.length : close + 3;
|
|
405
|
+
at = pastComment(text, lt, dialect);
|
|
342
406
|
continue;
|
|
343
407
|
}
|
|
344
408
|
const next = text.charCodeAt(lt + 1);
|
|
@@ -362,7 +426,7 @@ export function readComponent(text) {
|
|
|
362
426
|
at = lt + 1;
|
|
363
427
|
continue;
|
|
364
428
|
}
|
|
365
|
-
const tag = readStartTag(text, lt, nameStop);
|
|
429
|
+
const tag = readStartTag(text, lt, nameStop, dialect);
|
|
366
430
|
const rawText = open.length === 0 && RAW_TEXT.has(tag.name);
|
|
367
431
|
for (const attribute of rawText ? [] : tag.attributes) {
|
|
368
432
|
if (attribute.value !== "") {
|
|
@@ -435,9 +499,9 @@ export function readComponent(text) {
|
|
|
435
499
|
}
|
|
436
500
|
}
|
|
437
501
|
/**
|
|
438
|
-
* Whether the module's text exports a default at its top level: an `export default
|
|
439
|
-
* or an export clause naming `default`, outside every comment,
|
|
440
|
-
* regular expression, and outside every bracket.
|
|
502
|
+
* Whether the module's text exports a default at its top level: an `export default`, an
|
|
503
|
+
* `export * as default`, or an export clause naming `default`, outside every comment,
|
|
504
|
+
* string, template and regular expression, and outside every bracket.
|
|
441
505
|
*/
|
|
442
506
|
export function exportsDefault(text) {
|
|
443
507
|
const words = [];
|
|
@@ -525,7 +589,8 @@ export function exportsDefault(text) {
|
|
|
525
589
|
const matched = word.exec(text);
|
|
526
590
|
if (matched !== null) {
|
|
527
591
|
const name = matched[0];
|
|
528
|
-
|
|
592
|
+
const starAs = words.at(-3) === "export" && words.at(-2) === "*" && words.at(-1) === "as";
|
|
593
|
+
if (depth === 0 && (words.at(-1) === "export" || starAs) && name === "default") {
|
|
529
594
|
return true;
|
|
530
595
|
}
|
|
531
596
|
if (clause && name === "default") {
|
|
@@ -554,8 +619,8 @@ export function exportsDefault(text) {
|
|
|
554
619
|
return false;
|
|
555
620
|
}
|
|
556
621
|
/** The module one component file's text is read as, and the span map back to the file. */
|
|
557
|
-
export function componentModule(text) {
|
|
558
|
-
const { blocks, imports } = readComponent(text);
|
|
622
|
+
export function componentModule(text, fileExtension) {
|
|
623
|
+
const { blocks, imports } = readComponent(text, fileExtension);
|
|
559
624
|
const out = [];
|
|
560
625
|
let at = 0;
|
|
561
626
|
let language;
|
|
@@ -270,8 +270,9 @@ export function configurationFiles(host, targetRoot, files, parse) {
|
|
|
270
270
|
const dirs = new Set([root]);
|
|
271
271
|
for (const file of files.byPath.values()) {
|
|
272
272
|
const form = configurationModuleForm(basename(file.fileName));
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
const fileDir = dirnamePath(file.fileName);
|
|
274
|
+
if (form !== undefined && holdsManifest(fileDir)) {
|
|
275
|
+
modules.push({ file, form, manifestDir: fileDir });
|
|
275
276
|
}
|
|
276
277
|
for (let dir = dirnamePath(file.fileName); relativePath(root, dir) !== undefined && !dirs.has(dir); dir = dirnamePath(dir)) {
|
|
277
278
|
dirs.add(dir);
|
|
@@ -301,7 +302,7 @@ export function configurationFiles(host, targetRoot, files, parse) {
|
|
|
301
302
|
pending.push({ path, manifestDir: dir });
|
|
302
303
|
}
|
|
303
304
|
else if (held !== undefined) {
|
|
304
|
-
modules.push({ file: held, form: "a script's configuration flag" });
|
|
305
|
+
modules.push({ file: held, form: "a script's configuration flag", manifestDir: dir });
|
|
305
306
|
}
|
|
306
307
|
else {
|
|
307
308
|
named.push({ path, manifestDir: dir });
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
9
9
|
import { resolvedTargets } from "./calls.js";
|
|
10
|
+
import { declaredComponent } from "./inventory.js";
|
|
10
11
|
import { must, Unanswerable } from "./query.js";
|
|
11
12
|
import { nameComponent } from "./ref.js";
|
|
12
13
|
/** The meanings a name of the global scope is looked up under: any of them. */
|
|
@@ -80,9 +81,30 @@ function candidatesOf(project, container, isStatic) {
|
|
|
80
81
|
...exported.filter((member) => !isClass || !isClassMember(member)),
|
|
81
82
|
];
|
|
82
83
|
}
|
|
83
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* The properties of the type one container declares, or of its constructor's type for a
|
|
86
|
+
* static member. A member a symbol keys is bound to that type later than the container's
|
|
87
|
+
* symbol table is built, so the table does not hold it.
|
|
88
|
+
*/
|
|
89
|
+
function typeMembersOf(project, container, isStatic) {
|
|
90
|
+
const queries = project.queries;
|
|
91
|
+
const type = must(isStatic ? queries.typeOfSymbol(container) : queries.declaredTypeOf(container));
|
|
92
|
+
return type === undefined ? [] : must(queries.propertiesOf(type));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The member of one container a path component spells, followed through any alias. A
|
|
96
|
+
* component in brackets is a computed key, compared with the key each declaration of a
|
|
97
|
+
* candidate writes: the checker names a member a symbol keys by no spelling of its own.
|
|
98
|
+
*/
|
|
84
99
|
function memberOf(project, container, component, isStatic) {
|
|
85
|
-
const
|
|
100
|
+
const computed = component.startsWith("[");
|
|
101
|
+
const candidates = candidatesOf(project, container, isStatic);
|
|
102
|
+
const member = (computed ? [...candidates, ...typeMembersOf(project, container, isStatic)] : candidates).find((candidate) => nameComponent(candidate.name) === component ||
|
|
103
|
+
(computed &&
|
|
104
|
+
candidate.declarations.some((handle) => {
|
|
105
|
+
const node = project.declarationAt(handle)?.node;
|
|
106
|
+
return node !== undefined && declaredComponent(node) === component;
|
|
107
|
+
})));
|
|
86
108
|
return member === undefined ? undefined : unaliased(project, member);
|
|
87
109
|
}
|
|
88
110
|
/** The declaration a path names from one starting symbol, component by component. */
|
|
@@ -4,7 +4,7 @@ export const RECORD = {
|
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "5.2.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:012872542679539ec105f49b5ee502b0a9fa35c6bbd9ef3983b5f6e53af83d8e",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
|
@@ -196,12 +196,13 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
196
196
|
};
|
|
197
197
|
const calls = new Map();
|
|
198
198
|
/**
|
|
199
|
-
* The parameter and return types of a type's
|
|
200
|
-
*
|
|
199
|
+
* The parameter and return types of each of a type's call signatures, or of each of its
|
|
200
|
+
* construct signatures where it has no call signature, read once per type.
|
|
201
201
|
*/
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const callsOf = (type) => {
|
|
203
|
+
const known = calls.get(type.id);
|
|
204
|
+
if (known !== undefined) {
|
|
205
|
+
return known;
|
|
205
206
|
}
|
|
206
207
|
lookups += 1;
|
|
207
208
|
let kind = SignatureKind.Call;
|
|
@@ -211,28 +212,26 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
211
212
|
kind = SignatureKind.Construct;
|
|
212
213
|
signatures = queries.signaturesOf(type, kind);
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
-
const [signature, ...more] = isAnswered(signatures) ? signatures : [];
|
|
216
|
-
if (signature !== undefined && more.length === 0) {
|
|
215
|
+
const called = (isAnswered(signatures) ? signatures : []).map((signature) => {
|
|
217
216
|
const parameters = signature.parameters.map((_parameter, index) => queries.parameterType(signature, index));
|
|
218
217
|
const result = queries.returnTypeOf(signature);
|
|
219
218
|
lookups += parameters.length + 1;
|
|
220
|
-
|
|
219
|
+
return {
|
|
221
220
|
kind,
|
|
222
221
|
parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
|
|
223
222
|
result: isAnswered(result) ? result : undefined,
|
|
224
223
|
};
|
|
225
|
-
}
|
|
224
|
+
});
|
|
226
225
|
calls.set(type.id, called);
|
|
227
226
|
return called;
|
|
228
227
|
};
|
|
229
228
|
/**
|
|
230
229
|
* Each object type a value of `source` is checked as against the object types of
|
|
231
|
-
* `target`: the value's own, and through array elements,
|
|
232
|
-
* checked the other way,
|
|
233
|
-
*
|
|
234
|
-
* A source that is one of the target's own object types matches
|
|
235
|
-
* pair is compared once, which ends the walk over a recursive type.
|
|
230
|
+
* `target`: the value's own, and through array elements, every pair of signatures
|
|
231
|
+
* (parameters checked the other way, then results), which reads more than the overload
|
|
232
|
+
* the checker picks, and the type arguments of two instantiations by place, as an array
|
|
233
|
+
* reaches `Iterable<T>`. A source that is one of the target's own object types matches
|
|
234
|
+
* it as it stands. Each pair is compared once, which ends the walk over a recursive type.
|
|
236
235
|
*/
|
|
237
236
|
const compared = (source, target, into, seen) => {
|
|
238
237
|
const key = `${String(source?.id)}>${String(target?.id)}`;
|
|
@@ -253,7 +252,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
253
252
|
into.push({ source: one, targets, outside: true });
|
|
254
253
|
}
|
|
255
254
|
const element = elementOf(one);
|
|
256
|
-
const called = element === undefined ?
|
|
255
|
+
const called = element === undefined ? callsOf(one) : [];
|
|
257
256
|
const sourceArguments = typeArgumentsOf(one);
|
|
258
257
|
for (const part of targets) {
|
|
259
258
|
const partElement = elementOf(part);
|
|
@@ -267,17 +266,17 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
267
266
|
sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
|
|
268
267
|
compared(argument, targetArguments[index], into, seen);
|
|
269
268
|
});
|
|
270
|
-
if (element !== undefined) {
|
|
269
|
+
if (element !== undefined || called.length === 0) {
|
|
271
270
|
continue;
|
|
272
271
|
}
|
|
273
|
-
const other
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
for (const other of callsOf(part)) {
|
|
273
|
+
for (const signature of called.filter((one) => one.kind === other.kind)) {
|
|
274
|
+
other.parameters.forEach((parameter, index) => {
|
|
275
|
+
compared(parameter, signature.parameters[index], into, seen);
|
|
276
|
+
});
|
|
277
|
+
compared(signature.result, other.result, into, seen);
|
|
278
|
+
}
|
|
276
279
|
}
|
|
277
|
-
other.parameters.forEach((parameter, index) => {
|
|
278
|
-
compared(parameter, called.parameters[index], into, seen);
|
|
279
|
-
});
|
|
280
|
-
compared(called.result, other.result, into, seen);
|
|
281
280
|
}
|
|
282
281
|
}
|
|
283
282
|
};
|
|
@@ -298,7 +297,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
298
297
|
if (element !== undefined) {
|
|
299
298
|
return mayCarry(element, seen);
|
|
300
299
|
}
|
|
301
|
-
return
|
|
300
|
+
return callsOf(part).length > 0 || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
|
|
302
301
|
});
|
|
303
302
|
};
|
|
304
303
|
const sourced = carried.flatMap((flow, index) => {
|
|
@@ -330,10 +329,11 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
330
329
|
continue;
|
|
331
330
|
}
|
|
332
331
|
for (const [name, member] of sourceMembers) {
|
|
332
|
+
// A member the position shares with the source is no read of it. A member with no
|
|
333
|
+
// declaration, as a mapped type over literal keys has, is never one it shares.
|
|
333
334
|
const read = tables.some((table) => {
|
|
334
335
|
const declared = table.get(name);
|
|
335
336
|
return (declared !== undefined &&
|
|
336
|
-
declared.handles.size > 0 &&
|
|
337
337
|
![...declared.handles].some((handle) => member.handles.has(handle)));
|
|
338
338
|
});
|
|
339
339
|
if (read) {
|
package/dist/src/dependencies.js
CHANGED
|
@@ -244,8 +244,8 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
|
|
|
244
244
|
return names;
|
|
245
245
|
};
|
|
246
246
|
const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot), parse);
|
|
247
|
-
for (const { file } of configuration.modules) {
|
|
248
|
-
const names = declaredAt(
|
|
247
|
+
for (const { file, manifestDir } of configuration.modules) {
|
|
248
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
249
249
|
for (const { node, text } of moduleStrings(file)) {
|
|
250
250
|
use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
|
|
251
251
|
}
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* it. A rest element names no property: what it copies is read where the copy is.
|
|
7
7
|
*/
|
|
8
8
|
import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
9
|
-
import { TypeFlags } from "@typescript/native/sync";
|
|
9
|
+
import { SignatureKind, TypeFlags, } from "@typescript/native/sync";
|
|
10
10
|
import { isAnswered, propertyTables, UNANSWERED, } from "./query.js";
|
|
11
|
+
/** The types of an iteration result's `value` that carry no element of the iterable. */
|
|
12
|
+
const NO_ELEMENT = TypeFlags.AnyOrUnknown | TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Null | TypeFlags.Never;
|
|
11
13
|
/** The text a property name spells, or `undefined` for a computed name. */
|
|
12
14
|
function nameText(node) {
|
|
13
15
|
if (node === undefined) {
|
|
@@ -207,11 +209,84 @@ export function destructuring(project, cap, properties = propertyTables(project.
|
|
|
207
209
|
const elements = queries.typeArguments(type);
|
|
208
210
|
return isAnswered(elements) ? elements[0] : UNANSWERED;
|
|
209
211
|
};
|
|
212
|
+
/** The type of the one property of a type that `matches` accepts, unanswered where two do. */
|
|
213
|
+
const propertyTypeOf = (owner, matches) => {
|
|
214
|
+
const table = tableOf(owner);
|
|
215
|
+
if (!isAnswered(table)) {
|
|
216
|
+
return UNANSWERED;
|
|
217
|
+
}
|
|
218
|
+
const [property, ...more] = [...table.values()].filter(matches);
|
|
219
|
+
if (property === undefined) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
if (more.length > 0) {
|
|
223
|
+
return UNANSWERED;
|
|
224
|
+
}
|
|
225
|
+
patternLookups += 1;
|
|
226
|
+
return queries.typeOfSymbol(property);
|
|
227
|
+
};
|
|
228
|
+
const resultOf = (callee) => {
|
|
229
|
+
if (callee === undefined || callee === UNANSWERED) {
|
|
230
|
+
return callee;
|
|
231
|
+
}
|
|
232
|
+
patternLookups += 1;
|
|
233
|
+
const signatures = queries.signaturesOf(callee, SignatureKind.Call);
|
|
234
|
+
if (!isAnswered(signatures)) {
|
|
235
|
+
return UNANSWERED;
|
|
236
|
+
}
|
|
237
|
+
const [signature] = signatures;
|
|
238
|
+
if (signature === undefined) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
patternLookups += 1;
|
|
242
|
+
return queries.returnTypeOf(signature);
|
|
243
|
+
};
|
|
244
|
+
const iterations = new Map();
|
|
245
|
+
/**
|
|
246
|
+
* The type a `for…of` takes from one iterable that is no array, read through the
|
|
247
|
+
* iteration protocol: the `value` of what the `next()` of its `[Symbol.iterator]()`
|
|
248
|
+
* answers. A constituent of that answer whose value is {@link NO_ELEMENT} carries no
|
|
249
|
+
* element; where more than one carries one, the element is left unanswered, so a read
|
|
250
|
+
* through it is resolved by name.
|
|
251
|
+
*/
|
|
252
|
+
const iteratedOf = (type) => {
|
|
253
|
+
if (iterations.has(type.id)) {
|
|
254
|
+
return iterations.get(type.id);
|
|
255
|
+
}
|
|
256
|
+
// The checker keys a member a unique symbol names `__@<name>@<symbol id>`, and escapes
|
|
257
|
+
// a written key that starts with `__` with one more `_`, so no string key matches; a
|
|
258
|
+
// unique symbol of the program's own named `iterator` does, which leaves two matches:
|
|
259
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/checker/checker.go#L23096-L23108
|
|
260
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/ast/symbol.go#L91-L103
|
|
261
|
+
const iterator = resultOf(propertyTypeOf(type, (one) => one.escapedName.startsWith("__@iterator@")));
|
|
262
|
+
const result = iterator === undefined || iterator === UNANSWERED
|
|
263
|
+
? iterator
|
|
264
|
+
: resultOf(propertyTypeOf(iterator, (one) => one.name === "next"));
|
|
265
|
+
let found = result;
|
|
266
|
+
if (result !== undefined && result !== UNANSWERED) {
|
|
267
|
+
let parts = [result];
|
|
268
|
+
if (result.isUnionType()) {
|
|
269
|
+
patternLookups += 1;
|
|
270
|
+
parts = queries.constituents(result);
|
|
271
|
+
}
|
|
272
|
+
const values = isAnswered(parts)
|
|
273
|
+
? parts.map((part) => propertyTypeOf(part, (one) => one.name === "value"))
|
|
274
|
+
: [UNANSWERED];
|
|
275
|
+
const carried = values.filter((value) => value === UNANSWERED || (value !== undefined && (value.flags & NO_ELEMENT) === 0));
|
|
276
|
+
const [only, ...more] = carried;
|
|
277
|
+
found = more.length > 0 ? UNANSWERED : only;
|
|
278
|
+
}
|
|
279
|
+
iterations.set(type.id, found);
|
|
280
|
+
return found;
|
|
281
|
+
};
|
|
210
282
|
const step = (type, next) => {
|
|
211
283
|
const defined = definedOf(type);
|
|
212
284
|
if (defined === undefined || defined === UNANSWERED) {
|
|
213
285
|
return defined;
|
|
214
286
|
}
|
|
287
|
+
if (next.kind === "iterated") {
|
|
288
|
+
return elementOf(defined, undefined) ?? iteratedOf(defined);
|
|
289
|
+
}
|
|
215
290
|
if (next.kind === "property") {
|
|
216
291
|
const table = tableOf(defined);
|
|
217
292
|
if (!isAnswered(table)) {
|
|
@@ -224,7 +299,7 @@ export function destructuring(project, cap, properties = propertyTables(project.
|
|
|
224
299
|
patternLookups += 1;
|
|
225
300
|
return queries.typeOfSymbol(property);
|
|
226
301
|
}
|
|
227
|
-
return elementOf(defined, next.
|
|
302
|
+
return elementOf(defined, next.index);
|
|
228
303
|
};
|
|
229
304
|
const resolve = (reads) => {
|
|
230
305
|
const patterns = [...new Set(reads.map((read) => read.pattern))];
|