@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.15
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/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +2 -1
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/contract/contract.generated.js +1 -0
- package/dist/src/dependencies.js +2 -1
- package/dist/src/entry-points.js +26 -95
- package/dist/src/file-facts.js +1 -1
- package/dist/src/graph.js +0 -1
- package/dist/src/inventory/commonjs.js +462 -0
- package/dist/src/inventory/enumerate.js +166 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/load/discover.js +1 -1
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/own-body.js +28 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +91 -30
- package/dist/src/session.js +3 -0
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/unseen-loads.js +106 -17
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +2 -1
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/contract/contract.generated.ts +1 -0
- package/src/dependencies.ts +2 -1
- package/src/entry-points.ts +30 -107
- package/src/file-facts.ts +1 -1
- package/src/graph.ts +0 -1
- package/src/inventory/commonjs.ts +611 -0
- package/src/inventory/enumerate.ts +202 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/load/discover.ts +1 -1
- package/src/load/package-scope.ts +74 -0
- package/src/own-body.ts +42 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +116 -53
- package/src/session.ts +17 -1
- package/src/suppress-inline.ts +53 -89
- package/src/unseen-loads.ts +166 -18
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LANGUAGE_ROWS } from "../contract/contract.generated.js";
|
|
2
2
|
import { filesBelow, filesIn } from "../fs/tree.js";
|
|
3
|
-
import { packageScope } from "
|
|
3
|
+
import { packageScope } from "./package-scope.js";
|
|
4
4
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "../paths.js";
|
|
5
5
|
import { LANGUAGE, message } from "../product.js";
|
|
6
6
|
import { globExpression } from "../text/glob.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { manifestAt } from "../fs/manifest.js";
|
|
2
|
+
import { dirnamePath, joinPath } from "../paths.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves each file's package scope: the name of the nearest manifest at or above its
|
|
5
|
+
* directory up to the target root, and the file's path below that manifest. Where no
|
|
6
|
+
* manifest up to the root carries a name, the package is a single dot and the path is read
|
|
7
|
+
* from the root, which cannot collide, because a package name never starts with a dot. One
|
|
8
|
+
* directory is read at most once.
|
|
9
|
+
*/
|
|
10
|
+
export function packageScope(host, root) {
|
|
11
|
+
const named = new Map();
|
|
12
|
+
const manifestName = (dir) => {
|
|
13
|
+
const name = manifestAt(host, joinPath(root, dir))?.["name"];
|
|
14
|
+
return typeof name === "string" && name !== "" ? name : undefined;
|
|
15
|
+
};
|
|
16
|
+
const nearest = (dir) => {
|
|
17
|
+
if (named.has(dir)) {
|
|
18
|
+
return named.get(dir);
|
|
19
|
+
}
|
|
20
|
+
const name = manifestName(dir);
|
|
21
|
+
const found = name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
|
|
22
|
+
named.set(dir, found);
|
|
23
|
+
return found;
|
|
24
|
+
};
|
|
25
|
+
return (path) => {
|
|
26
|
+
const found = nearest(parentOf(path));
|
|
27
|
+
if (found === undefined) {
|
|
28
|
+
return { package: ".", path };
|
|
29
|
+
}
|
|
30
|
+
return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether Node reads each file's `.js` siblings as ECMAScript modules: the `type` of the
|
|
35
|
+
* nearest manifest at or above its directory up to the target root is `module`
|
|
36
|
+
* (https://nodejs.org/api/packages.html#type). One directory is read at most once.
|
|
37
|
+
*/
|
|
38
|
+
export function packageTypeIsModule(host, root) {
|
|
39
|
+
const typed = new Map();
|
|
40
|
+
const nearest = (dir) => {
|
|
41
|
+
const known = typed.get(dir);
|
|
42
|
+
if (known !== undefined) {
|
|
43
|
+
return known;
|
|
44
|
+
}
|
|
45
|
+
const manifest = manifestAt(host, joinPath(root, dir));
|
|
46
|
+
const found = manifest !== undefined ? manifest["type"] === "module" : dir !== "" && nearest(parentOf(dir));
|
|
47
|
+
typed.set(dir, found);
|
|
48
|
+
return found;
|
|
49
|
+
};
|
|
50
|
+
return (path) => nearest(parentOf(path));
|
|
51
|
+
}
|
|
52
|
+
/** The directory one path below the root sits in, and the root itself as the empty path. */
|
|
53
|
+
function parentOf(path) {
|
|
54
|
+
const dir = dirnamePath(path);
|
|
55
|
+
return dir === "." ? "" : dir;
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isArrowFunction, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isVariableDeclaration, } from "@typescript/native/ast";
|
|
2
|
+
import { SymbolFlags } from "@typescript/native/sync";
|
|
3
|
+
import { isAmbient } from "./inventory.js";
|
|
4
|
+
import { unwrapped } from "./unwrap.js";
|
|
5
|
+
/**
|
|
6
|
+
* Whether `symbol`, through its aliases, names a function or class the target writes with a
|
|
7
|
+
* body, which a call or construction runs in place of the platform's `require` or `URL`. Any
|
|
8
|
+
* other answer, an unresolved one included, is the platform's, whose call loads a file.
|
|
9
|
+
*/
|
|
10
|
+
export function hasOwnBody(project, symbol) {
|
|
11
|
+
const named = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
|
|
12
|
+
const own = project.ownPaths();
|
|
13
|
+
return (typeof named === "object" &&
|
|
14
|
+
named.declarations.some((handle) => {
|
|
15
|
+
const node = own.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
16
|
+
const value = node !== undefined && isVariableDeclaration(node) && node.initializer !== undefined
|
|
17
|
+
? unwrapped(node.initializer)
|
|
18
|
+
: node;
|
|
19
|
+
return (value !== undefined &&
|
|
20
|
+
(isFunctionDeclaration(value)
|
|
21
|
+
? value.body !== undefined
|
|
22
|
+
: isFunctionExpression(value) ||
|
|
23
|
+
isArrowFunction(value) ||
|
|
24
|
+
isClassDeclaration(value) ||
|
|
25
|
+
isClassExpression(value)) &&
|
|
26
|
+
!isAmbient(value.getSourceFile(), value));
|
|
27
|
+
}));
|
|
28
|
+
}
|
package/dist/src/position.js
CHANGED
|
@@ -25,6 +25,47 @@ export function isComponentFile(file) {
|
|
|
25
25
|
export function writtenLength(file) {
|
|
26
26
|
return isComponentFile(file) ? file.originalText.length : file.text.length;
|
|
27
27
|
}
|
|
28
|
+
/** Whether one character ends a line, under the language's line terminators. */
|
|
29
|
+
function endsLine(char) {
|
|
30
|
+
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
|
|
31
|
+
}
|
|
32
|
+
/** The offset where the line holding `from` ends. */
|
|
33
|
+
export function lineEnd(text, from) {
|
|
34
|
+
let at = from;
|
|
35
|
+
while (at < text.length && !endsLine(text[at])) {
|
|
36
|
+
at += 1;
|
|
37
|
+
}
|
|
38
|
+
return at;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The offset of the first token at or after `from`: whitespace, comments and a leading
|
|
42
|
+
* hashbang are trivia.
|
|
43
|
+
*/
|
|
44
|
+
export function tokenStart(text, from) {
|
|
45
|
+
let at = from;
|
|
46
|
+
if (at === 0 && text.startsWith("#!")) {
|
|
47
|
+
at = lineEnd(text, 0);
|
|
48
|
+
}
|
|
49
|
+
for (;;) {
|
|
50
|
+
if (/\s/u.test(text[at] ?? "")) {
|
|
51
|
+
at += 1;
|
|
52
|
+
}
|
|
53
|
+
else if (text.startsWith("//", at)) {
|
|
54
|
+
at = lineEnd(text, at);
|
|
55
|
+
}
|
|
56
|
+
else if (text.startsWith("/*", at)) {
|
|
57
|
+
const close = text.indexOf("*/", at + 2);
|
|
58
|
+
at = close < 0 ? text.length : close + 2;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return at;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** The line, counted from one, of the first token at or after one offset of one file. */
|
|
66
|
+
export function startLine(file, offset) {
|
|
67
|
+
return file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
68
|
+
}
|
|
28
69
|
/**
|
|
29
70
|
* The position of one offset of one file, rendered against the target root.
|
|
30
71
|
*
|
package/dist/src/ref.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The stable symbol reference: the identifier that names one declaration across
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* written.
|
|
8
|
-
*
|
|
9
|
-
* The position key of {@link ./position.ts} is the other identifier and has the
|
|
10
|
-
* other job: it locates a finding inside one run, and the two never mix.
|
|
2
|
+
* The stable symbol reference: the identifier that names one declaration across runs,
|
|
3
|
+
* reports, ignore entries, configured roots and cross-language edges. It is built from
|
|
4
|
+
* names and containers only, so an edit above a declaration leaves it unchanged, and it
|
|
5
|
+
* is compared as bytes, so this module writes the one canonical spelling. The position
|
|
6
|
+
* key of {@link ./position.ts} locates a finding inside one run; the two never mix.
|
|
11
7
|
*/
|
|
12
8
|
/** The language prefix, which namespaces every reference this analyzer emits. */
|
|
13
9
|
const PREFIX = "ts://";
|
|
@@ -30,16 +26,10 @@ const TS_FILE = String.raw `${TS_DIRS}[^/#\r\n]+(?:(?:\.d)?\.[mc]?ts|\.tsx|\.[mc
|
|
|
30
26
|
const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
|
|
31
27
|
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
|
|
32
28
|
/**
|
|
33
|
-
* One expression per
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* They are written here rather than read from the Contract at run time: the Contract
|
|
39
|
-
* is data a reader outside this program consults, and a product that read it while
|
|
40
|
-
* running would need it installed beside itself. A test compares this list against
|
|
41
|
-
* the release this analyzer is written against, so a grammar that grows a form
|
|
42
|
-
* arrives here as a failing test.
|
|
29
|
+
* One anchored expression per spelling the grammar defines; a reference is valid when it
|
|
30
|
+
* matches one. They are written here rather than read from the Contract at run time, so
|
|
31
|
+
* the product needs no Contract installed beside it; a test compares this list against
|
|
32
|
+
* the release this analyzer implements, so a grammar that grows a form fails it.
|
|
43
33
|
*/
|
|
44
34
|
export const REF_EXPRESSIONS = [
|
|
45
35
|
`^${SCOPE}#$`,
|
|
@@ -102,7 +92,8 @@ export function aliasFragment(name, block) {
|
|
|
102
92
|
const head = block === undefined ? "" : `${quoted(block)}.`;
|
|
103
93
|
return `${head}${nameComponent(name)}:alias`;
|
|
104
94
|
}
|
|
105
|
-
|
|
95
|
+
/** The canonical spelling of one component: a name, or a computed key. */
|
|
96
|
+
export function spell(component) {
|
|
106
97
|
return component.computed ? computedComponent(component.text) : nameComponent(component.text);
|
|
107
98
|
}
|
|
108
99
|
/**
|
package/dist/src/references.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
1
|
+
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isStringLiteralLikeNode, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
2
2
|
import { parenthesesRemoved } from "./unwrap.js";
|
|
3
3
|
import { aliasChains } from "./alias-chain.js";
|
|
4
4
|
import { contextualUses } from "./contextual-uses.js";
|
|
5
5
|
import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
6
6
|
import { specifierCandidates } from "./configuration-files.js";
|
|
7
7
|
import { namespaceUses } from "./namespace-objects.js";
|
|
8
|
-
import {
|
|
8
|
+
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
9
|
import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
|
|
10
10
|
import { elementAccessReferences } from "./element-access.js";
|
|
11
11
|
import { indexedAccessReferences } from "./indexed-access.js";
|
|
@@ -18,6 +18,8 @@ import { byPosition, isComponentFile, renderPosition } from "./position.js";
|
|
|
18
18
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
|
|
19
19
|
import { typeParameterReads } from "./type-parameter-reads.js";
|
|
20
20
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
21
|
+
import { hasOwnBody } from "./own-body.js";
|
|
22
|
+
import { computedLoadsOf, isLoadCall } from "./unseen-loads.js";
|
|
21
23
|
import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
|
|
22
24
|
import { handedOn, valuesOf } from "./value-flow.js";
|
|
23
25
|
import { bytewise } from "./text/compare.js";
|
|
@@ -211,6 +213,12 @@ function moduleURLOf(node) {
|
|
|
211
213
|
}
|
|
212
214
|
return undefined;
|
|
213
215
|
}
|
|
216
|
+
/** The file of the target a relative specifier written in `file` names, by its source or output name. */
|
|
217
|
+
function fileNamed(file, specifier, fileIdByName) {
|
|
218
|
+
return specifierCandidates(dirnamePath(file.fileName), specifier)
|
|
219
|
+
.map((path) => fileIdByName.get(path))
|
|
220
|
+
.find((id) => id !== undefined);
|
|
221
|
+
}
|
|
214
222
|
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
215
223
|
function isLiteralText(node) {
|
|
216
224
|
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
@@ -330,6 +338,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
330
338
|
const evaluations = [];
|
|
331
339
|
const wholes = [];
|
|
332
340
|
const urls = [];
|
|
341
|
+
const loadCalls = [];
|
|
333
342
|
const decorated = [];
|
|
334
343
|
const parameterWrites = [];
|
|
335
344
|
const namespaces = namespaceUses(file);
|
|
@@ -411,7 +420,10 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
411
420
|
}
|
|
412
421
|
const url = moduleURLOf(node);
|
|
413
422
|
if (url !== undefined) {
|
|
414
|
-
urls.push(url);
|
|
423
|
+
urls.push({ ...url, from: enclosing });
|
|
424
|
+
}
|
|
425
|
+
if (isLoadCall(node)) {
|
|
426
|
+
loadCalls.push({ call: node, from: enclosing });
|
|
415
427
|
}
|
|
416
428
|
const held = declarations.get(nodeKey(file, node));
|
|
417
429
|
const outer = enclosing;
|
|
@@ -445,6 +457,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
445
457
|
evaluations,
|
|
446
458
|
wholes,
|
|
447
459
|
urls,
|
|
460
|
+
loadCalls,
|
|
448
461
|
decorated,
|
|
449
462
|
parameterWrites,
|
|
450
463
|
keyed,
|
|
@@ -611,20 +624,12 @@ function aloneIn(file, uses, writes, found) {
|
|
|
611
624
|
return result;
|
|
612
625
|
}
|
|
613
626
|
/**
|
|
614
|
-
* Every reference one project's own files make to
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* walks the consumer's files instead and renders against its root, as
|
|
621
|
-
* {@link ReferenceOptions.consumer} states.
|
|
622
|
-
*
|
|
623
|
-
* The order is by the referencing position, then by the declaration named, the
|
|
624
|
-
* declaration referencing it and the use. That is a total order because one name
|
|
625
|
-
* references one declaration once from one declaration, while a chain of aliases reaches
|
|
626
|
-
* several declarations at one position and a bare star re-export's specifier both reads
|
|
627
|
-
* and evaluates the module it names.
|
|
627
|
+
* Every reference one project's own files make to its own declarations, and the files the
|
|
628
|
+
* test-file rules classified. `held` is the same project's inventory, where a resolved symbol
|
|
629
|
+
* is looked up; positions render against `targetRoot`, or a consumer's root as
|
|
630
|
+
* {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
|
|
631
|
+
* named, the referencing declaration and the use: an alias chain or a bare star re-export
|
|
632
|
+
* names several at one position, and one name references one declaration once.
|
|
628
633
|
*/
|
|
629
634
|
export function references(project, held, targetRoot, options) {
|
|
630
635
|
const cap = options.batchCap ?? DEFAULT_BATCH_CAP;
|
|
@@ -716,7 +721,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
716
721
|
* live. A name a namespace import binds may be a use of any file.
|
|
717
722
|
*/
|
|
718
723
|
const named = (node) => {
|
|
719
|
-
const byName =
|
|
724
|
+
const byName = held.byName;
|
|
720
725
|
const text = node.getText();
|
|
721
726
|
const imported = importedNames(node.getSourceFile()).get(text) ?? [];
|
|
722
727
|
return [
|
|
@@ -740,7 +745,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
740
745
|
return;
|
|
741
746
|
}
|
|
742
747
|
aliased.set(id, symbol === UNANSWERED
|
|
743
|
-
? (
|
|
748
|
+
? (held.byName.get(alias.name) ?? []).map((one) => ({
|
|
744
749
|
id: one,
|
|
745
750
|
stepped: true,
|
|
746
751
|
guessed: true,
|
|
@@ -783,6 +788,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
783
788
|
return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
|
|
784
789
|
});
|
|
785
790
|
const fileIdByName = new Map(walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]));
|
|
791
|
+
const loadable = walked
|
|
792
|
+
.filter((one) => fileIds.has(one.fileId))
|
|
793
|
+
.map(({ file, fileId }) => ({ file, fileId }));
|
|
786
794
|
const contextual = contextualUses(project, held, chains, walked.flatMap((one) => one.literals), walked.flatMap((one) => one.flows), cap, tables);
|
|
787
795
|
const writtenBy = new Map();
|
|
788
796
|
for (const write of contextual.writes) {
|
|
@@ -862,8 +870,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
862
870
|
}
|
|
863
871
|
}
|
|
864
872
|
const keyNames = computedKeyNames(held.symbols);
|
|
865
|
-
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, parameterWrites, keyed, } of walked) {
|
|
873
|
+
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, loadCalls, decorated, parameterWrites, keyed, } of walked) {
|
|
866
874
|
const test = testFile.get(file) === true;
|
|
875
|
+
const loads = computedLoadsOf(file, loadCalls, loadable);
|
|
867
876
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
868
877
|
const markup = isComponentFile(file);
|
|
869
878
|
const bindings = markup ? importBindings(file) : [];
|
|
@@ -904,6 +913,10 @@ export function references(project, held, targetRoot, options) {
|
|
|
904
913
|
...wholes.map((whole) => whole.specifier),
|
|
905
914
|
...urls.flatMap((url) => url.constructor ?? []),
|
|
906
915
|
...bindings.map((binding) => binding.node),
|
|
916
|
+
...loads.flatMap((load) => [
|
|
917
|
+
...(load.require === undefined ? [] : [load.require]),
|
|
918
|
+
...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
|
|
919
|
+
]),
|
|
907
920
|
]),
|
|
908
921
|
];
|
|
909
922
|
const answered = new Map();
|
|
@@ -939,6 +952,18 @@ export function references(project, held, targetRoot, options) {
|
|
|
939
952
|
});
|
|
940
953
|
}
|
|
941
954
|
}
|
|
955
|
+
// A script declares no module, so the checker names nothing for a specifier of one;
|
|
956
|
+
// the compiler's module resolution still names its file.
|
|
957
|
+
const unnamed = evaluations.flatMap(({ node }) => answered.get(node) === undefined &&
|
|
958
|
+
isStringLiteralLikeNode(node) &&
|
|
959
|
+
queriedFile(file, node, fileIdByName) === undefined
|
|
960
|
+
? [node]
|
|
961
|
+
: []);
|
|
962
|
+
const resolved = project.resolvedFileNames(unnamed);
|
|
963
|
+
const scripts = new Map(unnamed.map((node, at) => {
|
|
964
|
+
const name = resolved[at];
|
|
965
|
+
return [node, name === undefined ? undefined : fileIdByName.get(name)];
|
|
966
|
+
}));
|
|
942
967
|
for (const evaluation of evaluations) {
|
|
943
968
|
const queried = queriedFile(file, evaluation.node, fileIdByName);
|
|
944
969
|
if (queried !== undefined) {
|
|
@@ -952,6 +977,17 @@ export function references(project, held, targetRoot, options) {
|
|
|
952
977
|
});
|
|
953
978
|
}
|
|
954
979
|
const module = answered.get(evaluation.node);
|
|
980
|
+
const script = scripts.get(evaluation.node);
|
|
981
|
+
if (script !== undefined) {
|
|
982
|
+
found.push({
|
|
983
|
+
from: evaluation.from,
|
|
984
|
+
to: script,
|
|
985
|
+
position: renderPosition(file, root, evaluation.node.getStart()),
|
|
986
|
+
use: "evaluation",
|
|
987
|
+
resolution: "syntax",
|
|
988
|
+
test,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
955
991
|
if (module === undefined) {
|
|
956
992
|
continue;
|
|
957
993
|
}
|
|
@@ -973,19 +1009,13 @@ export function references(project, held, targetRoot, options) {
|
|
|
973
1009
|
}
|
|
974
1010
|
for (const url of urls) {
|
|
975
1011
|
const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
|
|
976
|
-
|
|
977
|
-
// library's declaration; a `URL` the target declares has no other.
|
|
978
|
-
if (typeof constructor === "object" &&
|
|
979
|
-
constructor.declarations.length > 0 &&
|
|
980
|
-
constructor.declarations.every((handle) => ownPaths.has(handle.path))) {
|
|
1012
|
+
if (typeof constructor === "object" && hasOwnBody(project, constructor)) {
|
|
981
1013
|
continue;
|
|
982
1014
|
}
|
|
983
|
-
const loaded =
|
|
984
|
-
.map((path) => fileIdByName.get(path))
|
|
985
|
-
.find((id) => id !== undefined);
|
|
1015
|
+
const loaded = fileNamed(file, url.specifier, fileIdByName);
|
|
986
1016
|
if (loaded !== undefined) {
|
|
987
1017
|
found.push({
|
|
988
|
-
from:
|
|
1018
|
+
from: url.from,
|
|
989
1019
|
to: loaded,
|
|
990
1020
|
position: renderPosition(file, root, url.at.getStart()),
|
|
991
1021
|
use: "evaluation",
|
|
@@ -994,6 +1024,37 @@ export function references(project, held, targetRoot, options) {
|
|
|
994
1024
|
});
|
|
995
1025
|
}
|
|
996
1026
|
}
|
|
1027
|
+
// A computed load imports each file it loads where the call is written, reading what
|
|
1028
|
+
// the file exports unless it reads the file as text or a URL; hasOwnBody owns which
|
|
1029
|
+
// `require` loads nothing.
|
|
1030
|
+
for (const load of loads) {
|
|
1031
|
+
const callee = load.require === undefined ? undefined : answered.get(load.require);
|
|
1032
|
+
if (typeof callee === "object" && hasOwnBody(project, callee)) {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
const position = renderPosition(file, root, load.call.getStart());
|
|
1036
|
+
for (const loaded of load.files) {
|
|
1037
|
+
found.push({
|
|
1038
|
+
from: load.eager ? fileId : load.from,
|
|
1039
|
+
to: loaded.fileId,
|
|
1040
|
+
position,
|
|
1041
|
+
use: "evaluation",
|
|
1042
|
+
resolution: "syntax",
|
|
1043
|
+
test,
|
|
1044
|
+
});
|
|
1045
|
+
const module = load.asset ? undefined : answered.get(loaded.file);
|
|
1046
|
+
for (const target of module === undefined ? [] : exportsRead(module)) {
|
|
1047
|
+
found.push({
|
|
1048
|
+
from: load.from,
|
|
1049
|
+
to: target.id,
|
|
1050
|
+
position,
|
|
1051
|
+
use: "read",
|
|
1052
|
+
resolution: target.guessed ? "by-name" : "batch",
|
|
1053
|
+
test,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
997
1058
|
for (const whole of wholes) {
|
|
998
1059
|
const module = answered.get(whole.specifier);
|
|
999
1060
|
if (module === undefined) {
|
|
@@ -1060,7 +1121,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
1060
1121
|
}
|
|
1061
1122
|
const position = renderPosition(file, root, read.key.getStart());
|
|
1062
1123
|
const targets = symbol === UNANSWERED
|
|
1063
|
-
? (
|
|
1124
|
+
? (held.byName.get(read.name) ?? []).map((id) => ({
|
|
1064
1125
|
id,
|
|
1065
1126
|
stepped: false,
|
|
1066
1127
|
guessed: true,
|
package/dist/src/session.js
CHANGED
|
@@ -138,6 +138,9 @@ function viewOf(project, configFile, engine, log) {
|
|
|
138
138
|
ownKeys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
139
139
|
return ownKeys;
|
|
140
140
|
},
|
|
141
|
+
resolvedFileNames: (specifiers) => batched(engine, specifiers, (node) => project.program.getResolvedModuleFromModuleSpecifier.gen(node, node.getSourceFile().fileName)).map((resolved) => resolved === undefined || resolved.resolvedFileName === ""
|
|
142
|
+
? undefined
|
|
143
|
+
: resolved.resolvedFileName),
|
|
141
144
|
handle: (node) => ({ node }),
|
|
142
145
|
symbolsAt: (handles, cap) => queries.symbolsAt(handles.map(({ node }) => node), cap),
|
|
143
146
|
resolvedSymbolAt: ({ node }) => queries.resolvedSymbol(node),
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
6
|
import { CODE } from "./contract/contract.generated.js";
|
|
7
|
-
import {
|
|
7
|
+
import { SyntaxKind } from "@typescript/native/ast";
|
|
8
8
|
import { importBindings } from "./import-bindings.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { nodeKey } from "./inventory.js";
|
|
10
|
+
import { relativePath } from "./paths.js";
|
|
11
|
+
import { byPosition, lineEnd, positionKey, renderPosition, startLine, tokenStart, } from "./position.js";
|
|
11
12
|
import { importRef } from "./ref.js";
|
|
12
|
-
import { signatureOf } from "./signatures.js";
|
|
13
13
|
import { isPartKind, SuppressionError, siteText, } from "./suppress.js";
|
|
14
14
|
/**
|
|
15
15
|
* The three expressions the grammar applies to the text of each line comment, in this
|
|
@@ -55,43 +55,6 @@ const LITERALS = new Set([
|
|
|
55
55
|
SyntaxKind.RegularExpressionLiteral,
|
|
56
56
|
SyntaxKind.JsxText,
|
|
57
57
|
]);
|
|
58
|
-
/** Whether one character ends a line, under the language's line terminators. */
|
|
59
|
-
function endsLine(char) {
|
|
60
|
-
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
|
|
61
|
-
}
|
|
62
|
-
/** The offset where the line holding `from` ends. */
|
|
63
|
-
function lineEnd(text, from) {
|
|
64
|
-
let at = from;
|
|
65
|
-
while (at < text.length && !endsLine(text[at])) {
|
|
66
|
-
at += 1;
|
|
67
|
-
}
|
|
68
|
-
return at;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The offset of the first token at or after `from`: whitespace, comments and a leading
|
|
72
|
-
* hashbang are trivia.
|
|
73
|
-
*/
|
|
74
|
-
export function tokenStart(text, from) {
|
|
75
|
-
let at = from;
|
|
76
|
-
if (at === 0 && text.startsWith("#!")) {
|
|
77
|
-
at = lineEnd(text, 0);
|
|
78
|
-
}
|
|
79
|
-
for (;;) {
|
|
80
|
-
if (/\s/u.test(text[at] ?? "")) {
|
|
81
|
-
at += 1;
|
|
82
|
-
}
|
|
83
|
-
else if (text.startsWith("//", at)) {
|
|
84
|
-
at = lineEnd(text, at);
|
|
85
|
-
}
|
|
86
|
-
else if (text.startsWith("/*", at)) {
|
|
87
|
-
const close = text.indexOf("*/", at + 2);
|
|
88
|
-
at = close < 0 ? text.length : close + 2;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return at;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
58
|
/**
|
|
96
59
|
* Every `//` line comment of one file, in file order. The text between tokens is read as
|
|
97
60
|
* the language reads it: a block comment holds no line comment, and the literal tokens the
|
|
@@ -140,55 +103,42 @@ function lineComments(file) {
|
|
|
140
103
|
function bindingKey(one) {
|
|
141
104
|
return `${one.declared?.id ?? ""}\u0000${one.part.id}`;
|
|
142
105
|
}
|
|
106
|
+
/** The placed declarations of a project by the path of the file each is in. */
|
|
107
|
+
function placedByPath(held, symbols) {
|
|
108
|
+
const byPath = new Map();
|
|
109
|
+
for (const [id, placed] of held.placement) {
|
|
110
|
+
const one = symbols.get(id);
|
|
111
|
+
const holder = symbols.get(placed.holder);
|
|
112
|
+
if (one === undefined || holder === undefined) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const at = byPath.get(one.position.path) ?? [];
|
|
116
|
+
at.push({ one, holder, startLines: placed.startLines, partLines: placed.partLines });
|
|
117
|
+
byPath.set(one.position.path, at);
|
|
118
|
+
}
|
|
119
|
+
return byPath;
|
|
120
|
+
}
|
|
143
121
|
/**
|
|
144
|
-
* The declarations of one file by the line each
|
|
145
|
-
*
|
|
146
|
-
* keyword, and a variable written on a later line of the statement also begins there. A
|
|
147
|
-
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
148
|
-
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
122
|
+
* The declarations of one file by the line a directive binds each on, as the inventory
|
|
123
|
+
* places them, and each import binding of the file on its statement's lines.
|
|
149
124
|
*/
|
|
150
|
-
function declarationLines(file,
|
|
125
|
+
function declarationLines(file, placed, held, targetRoot) {
|
|
151
126
|
const below = new Map();
|
|
152
|
-
const add = (
|
|
153
|
-
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
127
|
+
const add = (line, one) => {
|
|
154
128
|
const at = below.get(line) ?? [];
|
|
155
129
|
if (!at.some((known) => bindingKey(known) === bindingKey(one))) {
|
|
156
130
|
at.push(one);
|
|
157
131
|
}
|
|
158
132
|
below.set(line, at);
|
|
159
133
|
};
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
};
|
|
164
|
-
const visit = (node) => {
|
|
165
|
-
const one = declaredAt(node);
|
|
166
|
-
const owner = isConstructorDeclaration(node) ? declaredAt(node.parent) : undefined;
|
|
167
|
-
if (owner !== undefined && isConstructorDeclaration(node)) {
|
|
168
|
-
for (const at of [node, ...node.parameters.filter((one) => !isParameterProperty(one))]) {
|
|
169
|
-
add(at.pos, { declared: undefined, part: owner });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const holder = one !== undefined && isParameterProperty(node) ? declaredAt(node.parent.parent) : one;
|
|
173
|
-
if (one !== undefined && holder !== undefined) {
|
|
174
|
-
add(node.pos, { declared: one, part: holder });
|
|
134
|
+
for (const { one, holder, startLines, partLines } of placed) {
|
|
135
|
+
for (const line of startLines) {
|
|
136
|
+
add(line, { declared: one, part: holder });
|
|
175
137
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// line above it, inside the parameter list, and the directive binds the function.
|
|
179
|
-
for (const parameter of signatureOf(node)?.parameters ?? []) {
|
|
180
|
-
add(parameter.pos, { declared: one, part: one });
|
|
181
|
-
}
|
|
182
|
-
const list = node.parent;
|
|
183
|
-
if (node.kind === SyntaxKind.VariableDeclaration &&
|
|
184
|
-
list.kind === SyntaxKind.VariableDeclarationList &&
|
|
185
|
-
list.parent.kind === SyntaxKind.VariableStatement) {
|
|
186
|
-
add(list.parent.pos, { declared: one, part: one });
|
|
187
|
-
}
|
|
138
|
+
for (const line of partLines) {
|
|
139
|
+
add(line, { declared: undefined, part: one });
|
|
188
140
|
}
|
|
189
|
-
|
|
190
|
-
};
|
|
191
|
-
file.forEachChild(visit);
|
|
141
|
+
}
|
|
192
142
|
// An import binding is a subject of its own, bound on its line as a declaration is.
|
|
193
143
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
194
144
|
const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
|
|
@@ -200,7 +150,7 @@ function declarationLines(file, held, symbols, targetRoot) {
|
|
|
200
150
|
statement = statement.parent;
|
|
201
151
|
}
|
|
202
152
|
for (const at of [statement.pos, name.getStart()]) {
|
|
203
|
-
add(at, { declared: one, part: one });
|
|
153
|
+
add(startLine(file, at), { declared: one, part: one });
|
|
204
154
|
}
|
|
205
155
|
}
|
|
206
156
|
return below;
|
|
@@ -221,13 +171,15 @@ export function inlineDirectives(project, held, targetRoot) {
|
|
|
221
171
|
const symbols = new Map(held.symbols
|
|
222
172
|
.filter((symbol) => symbol.kind !== "file")
|
|
223
173
|
.map((symbol) => [symbol.id, { id: symbol.id, ref: symbol.ref, position: symbol.position }]));
|
|
174
|
+
const placed = placedByPath(held, symbols);
|
|
224
175
|
const found = [];
|
|
225
176
|
for (const file of project.ownSourceFiles()) {
|
|
226
177
|
const comments = lineComments(file).filter((comment) => CANDIDATE.test(comment.text));
|
|
227
178
|
if (comments.length === 0) {
|
|
228
179
|
continue;
|
|
229
180
|
}
|
|
230
|
-
const
|
|
181
|
+
const path = relativePath(targetRoot, file.fileName) ?? "";
|
|
182
|
+
const below = declarationLines(file, placed.get(path) ?? [], held, targetRoot);
|
|
231
183
|
for (const comment of comments) {
|
|
232
184
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
233
185
|
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|