@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.7
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 +2 -1
- package/dist/bin/node-host.js +3 -2
- package/dist/src/analysis.js +25 -43
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +2 -1
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +2 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings-pass.js +6 -11
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +15 -16
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +11 -20
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +8 -7
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +8 -10
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/verbs/analyze.js +15 -13
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +5 -3
- package/dist/src/verbs/print-projects.js +2 -1
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +26 -51
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +4 -2
- package/src/configuration-files.ts +27 -170
- package/src/contract/contract.generated.ts +25 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +4 -4
- package/src/exempt.ts +5 -27
- package/src/file-facts.ts +6 -24
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings-pass.ts +6 -12
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +15 -17
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +20 -27
- package/src/roots.ts +7 -20
- package/src/run.ts +8 -13
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +8 -11
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/verbs/analyze.ts +22 -21
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +5 -3
- package/src/verbs/print-projects.ts +2 -1
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/version.ts +2 -1
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
|
@@ -96,6 +96,7 @@ import type { Reference } from "./references.ts";
|
|
|
96
96
|
import type { ProjectView } from "./session.ts";
|
|
97
97
|
import { signatureOf, type SignatureNode } from "./signatures.ts";
|
|
98
98
|
import { spellsNoBinding } from "./spelled-names.ts";
|
|
99
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
99
100
|
|
|
100
101
|
/** The subject kind of each part this module reads. */
|
|
101
102
|
type PartKind = "parameter" | "result" | "statement" | "store" | "case";
|
|
@@ -197,11 +198,6 @@ function spanOf(file: SourceFile, root: string, node: Node): FindingPosition {
|
|
|
197
198
|
return { ...start, endLine };
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
/** The parenthesized expression's content, through every pair of parentheses. */
|
|
201
|
-
function unwrapped(node: Node): Node {
|
|
202
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
201
|
/** The node the parentheses around one expression sit in. */
|
|
206
202
|
function outerParent(node: Node): Node {
|
|
207
203
|
return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
|
|
@@ -209,7 +205,7 @@ function outerParent(node: Node): Node {
|
|
|
209
205
|
|
|
210
206
|
/** The name a call is made through: the identifier, or the member a property access names. */
|
|
211
207
|
function calleeName(call: CallExpression): Identifier | undefined {
|
|
212
|
-
const callee =
|
|
208
|
+
const callee = parenthesesRemoved(call.expression);
|
|
213
209
|
if (isIdentifier(callee)) {
|
|
214
210
|
return callee;
|
|
215
211
|
}
|
|
@@ -276,7 +272,7 @@ function unreachableExempt(statement: Statement): boolean {
|
|
|
276
272
|
|
|
277
273
|
/** The value a case expression is a literal of, as a key two equal literals share, or none. */
|
|
278
274
|
function caseKey(expression: Node): string | undefined {
|
|
279
|
-
const node =
|
|
275
|
+
const node = parenthesesRemoved(expression);
|
|
280
276
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
281
277
|
return `s:${node.text}`;
|
|
282
278
|
}
|
|
@@ -402,7 +398,7 @@ function extendedNames(files: readonly SourceFile[]): ReadonlySet<string> {
|
|
|
402
398
|
if (isTypeReferenceNode(type)) {
|
|
403
399
|
named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
|
|
404
400
|
} else {
|
|
405
|
-
const expression =
|
|
401
|
+
const expression = parenthesesRemoved(type.expression);
|
|
406
402
|
named = isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
407
403
|
}
|
|
408
404
|
if (isIdentifier(named)) {
|
|
@@ -455,7 +451,7 @@ function decoratorFixes(constructor: ConstructorDeclaration): boolean {
|
|
|
455
451
|
|
|
456
452
|
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
457
453
|
function spelledName(expression: Node): Identifier | undefined {
|
|
458
|
-
const node =
|
|
454
|
+
const node = parenthesesRemoved(expression);
|
|
459
455
|
if (isIdentifier(node)) {
|
|
460
456
|
return node;
|
|
461
457
|
}
|
|
@@ -724,8 +720,8 @@ export function projectParts<Brand>(
|
|
|
724
720
|
if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
|
|
725
721
|
specifiers.add(position(node.name));
|
|
726
722
|
}
|
|
727
|
-
if (isExportAssignment(node) && isIdentifier(
|
|
728
|
-
specifiers.add(position(
|
|
723
|
+
if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
|
|
724
|
+
specifiers.add(position(parenthesesRemoved(node.expression)));
|
|
729
725
|
}
|
|
730
726
|
if (isSwitchStatement(node)) {
|
|
731
727
|
const holder = holderOf(node);
|
|
@@ -1325,7 +1321,7 @@ function isPlainWrite(use: Identifier): boolean {
|
|
|
1325
1321
|
return (
|
|
1326
1322
|
isBinaryExpression(parent) &&
|
|
1327
1323
|
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
1328
|
-
|
|
1324
|
+
parenthesesRemoved(parent.left) === use
|
|
1329
1325
|
);
|
|
1330
1326
|
}
|
|
1331
1327
|
|
|
@@ -1382,14 +1378,14 @@ function deadStores(
|
|
|
1382
1378
|
if (!isExpressionStatement(statement)) {
|
|
1383
1379
|
return undefined;
|
|
1384
1380
|
}
|
|
1385
|
-
const expression =
|
|
1381
|
+
const expression = parenthesesRemoved(statement.expression);
|
|
1386
1382
|
if (
|
|
1387
1383
|
!isBinaryExpression(expression) ||
|
|
1388
1384
|
expression.operatorToken.kind !== SyntaxKind.EqualsToken
|
|
1389
1385
|
) {
|
|
1390
1386
|
return undefined;
|
|
1391
1387
|
}
|
|
1392
|
-
const target =
|
|
1388
|
+
const target = parenthesesRemoved(expression.left);
|
|
1393
1389
|
return isIdentifier(target) && uses.includes(target)
|
|
1394
1390
|
? { target, value: expression.right }
|
|
1395
1391
|
: undefined;
|
package/src/inventory.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import { computedComponent, nameComponent, renderRef, type Component, type Module } from "./ref.ts";
|
|
51
51
|
import { isAnswered, UNANSWERED } from "./query.ts";
|
|
52
52
|
import type { Handle, ProjectView } from "./session.ts";
|
|
53
|
+
import { manifestAt } from "./fs/manifest.ts";
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* The symbol inventory of one project: every declaration the target's own files
|
|
@@ -233,8 +234,6 @@ interface ExportScope {
|
|
|
233
234
|
readonly nameNode: Node;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
const MANIFEST = "package.json";
|
|
237
|
-
|
|
238
237
|
/** One manifest that names a package, and the directory below the root that holds it. */
|
|
239
238
|
interface Named {
|
|
240
239
|
readonly name: string;
|
|
@@ -255,24 +254,7 @@ export function packageScope(host: Host, root: string): (path: string) => Module
|
|
|
255
254
|
const named = new Map<string, Named | undefined>();
|
|
256
255
|
|
|
257
256
|
const manifestName = (dir: string): string | undefined => {
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
text = host.readFile(joinPath(root, dir, MANIFEST));
|
|
261
|
-
} catch {
|
|
262
|
-
return undefined;
|
|
263
|
-
}
|
|
264
|
-
let value: unknown;
|
|
265
|
-
try {
|
|
266
|
-
value = JSON.parse(text);
|
|
267
|
-
} catch {
|
|
268
|
-
// A manifest this program cannot read names no package, which is the same
|
|
269
|
-
// answer as an absent one: the file is then scoped by the target root.
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
if (typeof value !== "object" || value === null) {
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
const name = (value as Record<string, unknown>)["name"];
|
|
257
|
+
const name = manifestAt(host, joinPath(root, dir))?.["name"];
|
|
276
258
|
return typeof name === "string" && name !== "" ? name : undefined;
|
|
277
259
|
};
|
|
278
260
|
|
package/src/json-document.ts
CHANGED
|
@@ -128,3 +128,8 @@ export function positionAt(text: string, file: string, offset: number): Position
|
|
|
128
128
|
const lineStart = before.lastIndexOf("\n") + 1;
|
|
129
129
|
return { path: file, line: before.split("\n").length, column: offset - lineStart + 1 };
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/** Whether a decoded JSON value is an object: not `null` and not an array. */
|
|
133
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
134
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
135
|
+
}
|
package/src/keyof-comparisons.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
isCaseClause,
|
|
11
11
|
isElementAccessExpression,
|
|
12
12
|
isIdentifier,
|
|
13
|
-
isParenthesizedExpression,
|
|
14
13
|
isPropertyAccessExpression,
|
|
15
14
|
isSwitchStatement,
|
|
16
15
|
isTypeOperatorNode,
|
|
@@ -27,6 +26,7 @@ import { renderPosition } from "./position.ts";
|
|
|
27
26
|
import { UNANSWERED } from "./query.ts";
|
|
28
27
|
import type { Reference } from "./references.ts";
|
|
29
28
|
import type { ProjectView } from "./session.ts";
|
|
29
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
30
30
|
|
|
31
31
|
/** The operators that compare their operands for equality. */
|
|
32
32
|
const EQUALITIES: ReadonlySet<SyntaxKind> = new Set([
|
|
@@ -44,13 +44,9 @@ interface Comparison {
|
|
|
44
44
|
readonly other: Expression;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function unwrapped(node: Expression): Expression {
|
|
48
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
/** The name node an operand's symbol is read at: an identifier, or a property access's name. */
|
|
52
48
|
function nameOf(node: Expression): Node | undefined {
|
|
53
|
-
const at =
|
|
49
|
+
const at = parenthesesRemoved(node);
|
|
54
50
|
if (isIdentifier(at)) {
|
|
55
51
|
return at;
|
|
56
52
|
}
|
|
@@ -132,7 +128,7 @@ export function keyofComparisonReferences<Brand>(
|
|
|
132
128
|
}
|
|
133
129
|
const named = comparisons.flatMap((one) => {
|
|
134
130
|
const name = nameOf(one.keyed);
|
|
135
|
-
return name === undefined || isElementAccessExpression(
|
|
131
|
+
return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
|
|
136
132
|
? []
|
|
137
133
|
: [{ ...one, name }];
|
|
138
134
|
});
|
package/src/manifest.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Host } from "./host.ts";
|
|
9
|
-
import { isAbsolutePath,
|
|
9
|
+
import { isAbsolutePath, relativePath, resolvePath } from "./paths.ts";
|
|
10
|
+
import { isRecord } from "./json-document.ts";
|
|
11
|
+
import { MANIFEST, manifestAt } from "./fs/manifest.ts";
|
|
12
|
+
import { scriptTokens } from "./text/shell.ts";
|
|
10
13
|
|
|
11
14
|
/** The manifest's own name. */
|
|
12
|
-
const MANIFEST = "package.json";
|
|
13
15
|
|
|
14
16
|
/** The three members that name an entry point beside `exports` and `bin`. */
|
|
15
17
|
const LEGACY_MEMBERS = ["main", "module", "types"] as const;
|
|
@@ -44,10 +46,6 @@ export interface ManifestEntry {
|
|
|
44
46
|
readonly dir: string;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
49
|
/**
|
|
52
50
|
* Whether one member's value names a file of this package rather than another
|
|
53
51
|
* package or a URL. A target written with no leading dot is a path too, which is
|
|
@@ -60,24 +58,6 @@ function isTarget(value: string, requireDot: boolean): boolean {
|
|
|
60
58
|
return !requireDot || value.startsWith("./");
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
/** Where a script's command line splits into tokens: white space and the shell's operators. */
|
|
64
|
-
const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
|
|
65
|
-
|
|
66
|
-
/** A token written inside one pair of quotes. */
|
|
67
|
-
const QUOTED = /^(["'])(.*)\1$/su;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Every token one script's command line holds, each with one pair of enclosing quotes
|
|
71
|
-
* removed. A token holding a wildcard is a pattern rather than a path, and names no
|
|
72
|
-
* entry point.
|
|
73
|
-
*/
|
|
74
|
-
export function scriptTokens(command: string): readonly string[] {
|
|
75
|
-
return command
|
|
76
|
-
.split(SCRIPT_SEPARATORS)
|
|
77
|
-
.map((token) => token.replace(QUOTED, "$2"))
|
|
78
|
-
.filter((token) => token !== "" && !token.includes("*"));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
61
|
/** One member's spelling below another: `exports` then `exports["."]`. */
|
|
82
62
|
function below(member: string, key: string): string {
|
|
83
63
|
return `${member}[${JSON.stringify(key)}]`;
|
|
@@ -111,27 +91,6 @@ function exportTargets(value: unknown, member: string, found: Omit<ManifestEntry
|
|
|
111
91
|
}
|
|
112
92
|
}
|
|
113
93
|
|
|
114
|
-
/** The manifest at the target root, decoded, or undefined where there is none to read. */
|
|
115
|
-
function manifestOf(host: Host, targetRoot: string): Record<string, unknown> | undefined {
|
|
116
|
-
let text: string;
|
|
117
|
-
try {
|
|
118
|
-
text = host.readFile(joinPath(targetRoot, MANIFEST));
|
|
119
|
-
} catch {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
let value: unknown;
|
|
123
|
-
try {
|
|
124
|
-
value = JSON.parse(text);
|
|
125
|
-
} catch {
|
|
126
|
-
// A manifest this program cannot read names no entry point, which is the same
|
|
127
|
-
// answer as an absent one. What a manifest's contents say is not refused here:
|
|
128
|
-
// the package manager owns that refusal, and an analysis of a tree it rejects
|
|
129
|
-
// is not the run's subject.
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
return isRecord(value) ? value : undefined;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
94
|
/** What one manifest declares about where its package is entered. */
|
|
136
95
|
export interface Manifest {
|
|
137
96
|
/**
|
|
@@ -158,7 +117,7 @@ export interface Manifest {
|
|
|
158
117
|
* it and there is one such configuration per project, while there is one manifest.
|
|
159
118
|
*/
|
|
160
119
|
export function readManifest(host: Host, targetRoot: string): Manifest {
|
|
161
|
-
const manifest =
|
|
120
|
+
const manifest = manifestAt(host, targetRoot);
|
|
162
121
|
if (manifest === undefined) {
|
|
163
122
|
return { entries: [], declaresExports: false };
|
|
164
123
|
}
|
package/src/mdx-entries.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { resolveRelativeSpecifier } from "./configuration-files.ts";
|
|
|
9
9
|
import type { Host } from "./host.ts";
|
|
10
10
|
import { dirnamePath, joinPath, resolvePath } from "./paths.ts";
|
|
11
11
|
import type { SourceFiles } from "./source-files.ts";
|
|
12
|
+
import { filesBelow } from "./fs/tree.ts";
|
|
12
13
|
|
|
13
14
|
/** One file a document imports, the specifier that named it, and the export names it reads. */
|
|
14
15
|
interface DocumentEntry {
|
|
@@ -18,9 +19,6 @@ interface DocumentEntry {
|
|
|
18
19
|
readonly names: readonly string[] | undefined;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/** The directories no document is read below. */
|
|
22
|
-
const SKIPPED: ReadonlySet<string> = new Set(["node_modules", ".git"]);
|
|
23
|
-
|
|
24
22
|
/** The extensions of a document whose imports an MDX compiler reads. */
|
|
25
23
|
const DOCUMENT = /\.mdx?$/u;
|
|
26
24
|
|
|
@@ -129,28 +127,6 @@ function clauseNames(clause: string): readonly string[] | undefined {
|
|
|
129
127
|
return names;
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
/** Every document below `dir`, outside every skipped directory, by absolute path. */
|
|
133
|
-
function documents(host: Host, dir: string): readonly string[] {
|
|
134
|
-
let entries;
|
|
135
|
-
try {
|
|
136
|
-
entries = host.readDirectory(dir);
|
|
137
|
-
} catch {
|
|
138
|
-
return [];
|
|
139
|
-
}
|
|
140
|
-
const found: string[] = [];
|
|
141
|
-
for (const entry of entries) {
|
|
142
|
-
const path = joinPath(dir, entry.name);
|
|
143
|
-
if (entry.directory) {
|
|
144
|
-
if (!SKIPPED.has(entry.name)) {
|
|
145
|
-
found.push(...documents(host, path));
|
|
146
|
-
}
|
|
147
|
-
} else if (DOCUMENT.test(entry.name)) {
|
|
148
|
-
found.push(path);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return found.sort();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
130
|
/** The relative specifiers one document's import lines name, its fenced code blocks skipped. */
|
|
155
131
|
function importedBy(
|
|
156
132
|
text: string,
|
|
@@ -205,7 +181,8 @@ function documentImports(host: Host, root: string): readonly DocumentImports[] {
|
|
|
205
181
|
if (known !== undefined) {
|
|
206
182
|
return known;
|
|
207
183
|
}
|
|
208
|
-
const found =
|
|
184
|
+
const found = filesBelow(host, root, (name) => DOCUMENT.test(name)).flatMap((below) => {
|
|
185
|
+
const path = joinPath(root, below);
|
|
209
186
|
try {
|
|
210
187
|
return [{ path, imports: importedBy(host.readFile(path)) }];
|
|
211
188
|
} catch {
|
package/src/partial.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NotBuilt } from "./discover.ts";
|
|
2
2
|
import { relativePath } from "./paths.ts";
|
|
3
3
|
import type { UnansweredQuestion } from "./query.ts";
|
|
4
|
+
import { bytewise } from "./text/compare.ts";
|
|
4
5
|
|
|
5
6
|
/** What a run read only in part. */
|
|
6
7
|
interface Partial {
|
|
@@ -24,7 +25,7 @@ function counted(n: number, one: string, many: string): string {
|
|
|
24
25
|
*/
|
|
25
26
|
export function partialNotes(partial: Partial): readonly string[] {
|
|
26
27
|
const notes = [...partial.notBuilt]
|
|
27
|
-
.sort((a, b) => (a.id
|
|
28
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
28
29
|
.map(
|
|
29
30
|
(one) =>
|
|
30
31
|
`the derived configuration ${one.id} was not built and is not analyzed: ${one.error}`,
|
package/src/paths.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filesystem paths
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* They are written here rather than taken from the platform's own library because
|
|
6
|
-
* every module below the command line stays free of the platform, and because the
|
|
7
|
-
* supported platform set is one, so a second spelling would be a rule nothing
|
|
8
|
-
* exercises.
|
|
2
|
+
* Filesystem paths as the supported platform spells them: a solidus between segments, a
|
|
3
|
+
* leading one for an absolute path. They are written here rather than taken from the
|
|
4
|
+
* platform's library so every module below the command line stays free of the platform.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
const SEPARATOR = "/";
|
|
@@ -67,6 +63,25 @@ export function dirnamePath(path: string): string {
|
|
|
67
63
|
return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
|
|
68
64
|
}
|
|
69
65
|
|
|
66
|
+
/**
|
|
67
|
+
* `dir` and every directory above it, nearest first, normalized. With `top`, the list ends
|
|
68
|
+
* at `top` and is empty where `dir` is neither `top` nor below it; without, it ends at the
|
|
69
|
+
* root of the path.
|
|
70
|
+
*/
|
|
71
|
+
export function ancestors(dir: string, top?: string): readonly string[] {
|
|
72
|
+
const end = top === undefined ? undefined : normalizePath(top);
|
|
73
|
+
const found: string[] = [];
|
|
74
|
+
for (let at = normalizePath(dir); ; at = dirnamePath(at)) {
|
|
75
|
+
if (end !== undefined && at !== end && relativePath(end, at) === undefined) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
found.push(at);
|
|
79
|
+
if (at === end || dirnamePath(at) === at) {
|
|
80
|
+
return found;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
/**
|
|
71
86
|
* The extension of one path's last segment, from its last full stop, or the empty string
|
|
72
87
|
* where it has none. A segment that only starts with a full stop has none.
|
|
@@ -101,14 +116,11 @@ export function queriedModulePath(specifier: string): string | undefined {
|
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
/**
|
|
104
|
-
* One path as it reads below a root, or undefined when it is not below the root
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* The caller resolves both against the same directory before calling: a relative
|
|
111
|
-
* root and an absolute path are two spellings this cannot reconcile.
|
|
119
|
+
* One path as it reads below a root, or undefined when it is not strictly below the root:
|
|
120
|
+
* the root itself is not below itself. Both are normalized, then compared lexically, so a
|
|
121
|
+
* root and a path spelled through different symbolic links name nothing in common. The
|
|
122
|
+
* caller resolves both against the same directory first: a relative root and an absolute
|
|
123
|
+
* path cannot be reconciled.
|
|
112
124
|
*/
|
|
113
125
|
export function relativePath(root: string, path: string): string | undefined {
|
|
114
126
|
const at = normalizePath(root);
|
package/src/position.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SourceFile } from "@typescript/native/ast";
|
|
2
2
|
import { relativePath } from "./paths.ts";
|
|
3
|
+
import { bytewise } from "./text/compare.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Where one declaration is written, as every report renders it.
|
|
@@ -87,7 +88,7 @@ export function positionKey(position: Position): string {
|
|
|
87
88
|
*/
|
|
88
89
|
export function byPosition(a: Position, b: Position): number {
|
|
89
90
|
if (a.path !== b.path) {
|
|
90
|
-
return a.path
|
|
91
|
+
return bytewise(a.path, b.path);
|
|
91
92
|
}
|
|
92
93
|
if (a.line !== b.line) {
|
|
93
94
|
return a.line - b.line;
|
package/src/print.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Provenance,
|
|
6
6
|
type Provider,
|
|
7
7
|
} from "./config.ts";
|
|
8
|
+
import { bytewise } from "./text/compare.ts";
|
|
8
9
|
|
|
9
10
|
/** One declaration entry as a document writes it: the members of its shape alone. */
|
|
10
11
|
function declarationOf(entry: DeclarationEntry): Record<string, string> {
|
|
@@ -61,7 +62,7 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
61
62
|
},
|
|
62
63
|
consumers: { complete: config.consumersComplete },
|
|
63
64
|
roots: { patterns: config.rootPatterns },
|
|
64
|
-
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => (a
|
|
65
|
+
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => bytewise(a, b))),
|
|
65
66
|
exemptions: { disabled: config.exemptionsDisabled },
|
|
66
67
|
reporters: {
|
|
67
68
|
formats: config.reporters.formats,
|
|
@@ -88,7 +89,7 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
88
89
|
},
|
|
89
90
|
provenance: Object.fromEntries(
|
|
90
91
|
[...provenance]
|
|
91
|
-
.sort(([a], [b]) => (a
|
|
92
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
92
93
|
.map(([path, origin]) => [path, renderOrigin(origin)]),
|
|
93
94
|
),
|
|
94
95
|
};
|
package/src/product.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LANGUAGE_ROWS } from "./contract/contract.generated.ts";
|
|
2
|
+
import type { Writer } from "./run.ts";
|
|
3
|
+
|
|
4
|
+
/** The language this analyzer reads. */
|
|
5
|
+
export const LANGUAGE = "ts";
|
|
6
|
+
|
|
7
|
+
/** The analyzer's name: the one its language's row names, which every output spells it with. */
|
|
8
|
+
export const PRODUCT = LANGUAGE_ROWS[LANGUAGE].analyzer;
|
|
9
|
+
|
|
10
|
+
/** Writes one diagnostic line to the error stream, named for the analyzer. */
|
|
11
|
+
export function diagnose(err: Writer, text: string): void {
|
|
12
|
+
err.write(`${PRODUCT}: ${text}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The text one thrown value carries. */
|
|
16
|
+
export function message(error: unknown): string {
|
|
17
|
+
return error instanceof Error ? error.message : String(error);
|
|
18
|
+
}
|
package/src/references.ts
CHANGED
|
@@ -38,13 +38,13 @@ import {
|
|
|
38
38
|
type ParameterDeclaration,
|
|
39
39
|
type SourceFile,
|
|
40
40
|
} from "@typescript/native/ast";
|
|
41
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
41
42
|
import type { Symbol as TSSymbol } from "@typescript/native/sync";
|
|
42
43
|
import { aliasChains, type ChainTarget } from "./alias-chain.ts";
|
|
43
44
|
import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
|
|
44
45
|
import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
|
|
45
46
|
import { specifierCandidates } from "./configuration-files.ts";
|
|
46
47
|
import { namespaceUses } from "./namespace-objects.ts";
|
|
47
|
-
import { globExpression } from "./glob.ts";
|
|
48
48
|
import {
|
|
49
49
|
declarationsByName,
|
|
50
50
|
isParameterProperty,
|
|
@@ -73,6 +73,8 @@ import {
|
|
|
73
73
|
type KeyedAccess,
|
|
74
74
|
} from "./symbol-keys.ts";
|
|
75
75
|
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
76
|
+
import { bytewise } from "./text/compare.ts";
|
|
77
|
+
import { globExpression } from "./text/glob.ts";
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
80
|
* Every use one project's own files make of the project's own declarations.
|
|
@@ -315,15 +317,6 @@ function isName(node: Node): boolean {
|
|
|
315
317
|
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
/** The expression one node is, through however many parentheses are written around it. */
|
|
319
|
-
function unparenthesized(node: Node): Node {
|
|
320
|
-
let held = node;
|
|
321
|
-
while (isParenthesizedExpression(held)) {
|
|
322
|
-
held = held.expression;
|
|
323
|
-
}
|
|
324
|
-
return held;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
320
|
/**
|
|
328
321
|
* Records the name one store writes into.
|
|
329
322
|
*
|
|
@@ -333,7 +326,7 @@ function unparenthesized(node: Node): Node {
|
|
|
333
326
|
* target writes each name it binds, through its spreads and nested patterns.
|
|
334
327
|
*/
|
|
335
328
|
function markWrite(target: Node, writes: Set<number>, accesses: Set<Node>): void {
|
|
336
|
-
const held =
|
|
329
|
+
const held = parenthesesRemoved(target);
|
|
337
330
|
if (isName(held)) {
|
|
338
331
|
writes.add(held.pos);
|
|
339
332
|
return;
|
|
@@ -348,7 +341,7 @@ function markWrite(target: Node, writes: Set<number>, accesses: Set<Node>): void
|
|
|
348
341
|
accesses.add(held);
|
|
349
342
|
// A store through a member's value lands in storage every holder of the value
|
|
350
343
|
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
351
|
-
if (!isPropertyAccessExpression(
|
|
344
|
+
if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
|
|
352
345
|
markWrite(held.expression, writes, accesses);
|
|
353
346
|
}
|
|
354
347
|
return;
|
|
@@ -524,7 +517,7 @@ interface FileSites {
|
|
|
524
517
|
|
|
525
518
|
/** Whether one node is `import.meta.url`. */
|
|
526
519
|
function isImportMetaURL(node: Node): boolean {
|
|
527
|
-
const at =
|
|
520
|
+
const at = parenthesesRemoved(node);
|
|
528
521
|
return (
|
|
529
522
|
isPropertyAccessExpression(at) &&
|
|
530
523
|
isMetaProperty(at.expression) &&
|
|
@@ -536,7 +529,7 @@ function isImportMetaURL(node: Node): boolean {
|
|
|
536
529
|
/** The module URL one node builds, where it builds one. */
|
|
537
530
|
function moduleURLOf(node: Node): ModuleURL | undefined {
|
|
538
531
|
const relative = (argument: Node | undefined): string | undefined => {
|
|
539
|
-
const at = argument === undefined ? undefined :
|
|
532
|
+
const at = argument === undefined ? undefined : parenthesesRemoved(argument);
|
|
540
533
|
return at !== undefined &&
|
|
541
534
|
(isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
|
|
542
535
|
/^\.\.?\//u.test(at.text)
|
|
@@ -551,7 +544,7 @@ function moduleURLOf(node: Node): ModuleURL | undefined {
|
|
|
551
544
|
: undefined;
|
|
552
545
|
}
|
|
553
546
|
if (isCallExpression(node)) {
|
|
554
|
-
const callee =
|
|
547
|
+
const callee = parenthesesRemoved(node.expression);
|
|
555
548
|
const specifier = relative(node.arguments[0]);
|
|
556
549
|
if (
|
|
557
550
|
specifier !== undefined &&
|
|
@@ -869,7 +862,7 @@ export function testFileRulesOf(
|
|
|
869
862
|
};
|
|
870
863
|
}),
|
|
871
864
|
...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
|
|
872
|
-
].sort((a, b) =>
|
|
865
|
+
].sort((a, b) => bytewise(a.rule, b.rule));
|
|
873
866
|
}
|
|
874
867
|
|
|
875
868
|
const importedCache = new WeakMap<
|
|
@@ -1003,7 +996,7 @@ function aloneIn(
|
|
|
1003
996
|
nodes.every((node) => sameTarget.some((one) => inside(node, one.value))),
|
|
1004
997
|
)
|
|
1005
998
|
.map(([name]) => name)
|
|
1006
|
-
.sort(
|
|
999
|
+
.sort(bytewise);
|
|
1007
1000
|
if (names.length > 0) {
|
|
1008
1001
|
result.push({ index: write.index, names });
|
|
1009
1002
|
}
|
|
@@ -1011,14 +1004,6 @@ function aloneIn(
|
|
|
1011
1004
|
return result;
|
|
1012
1005
|
}
|
|
1013
1006
|
|
|
1014
|
-
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
1015
|
-
function compare(a: string, b: string): number {
|
|
1016
|
-
if (a === b) {
|
|
1017
|
-
return 0;
|
|
1018
|
-
}
|
|
1019
|
-
return a < b ? -1 : 1;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
1007
|
/**
|
|
1023
1008
|
* Every reference one project's own files make to the project's own declarations, in
|
|
1024
1009
|
* position order, and the files the test-file rules classified.
|
|
@@ -1677,9 +1662,9 @@ export function references<Brand>(
|
|
|
1677
1662
|
found.sort(
|
|
1678
1663
|
(a, b) =>
|
|
1679
1664
|
byPosition(a.position, b.position) ||
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1665
|
+
bytewise(a.to, b.to) ||
|
|
1666
|
+
bytewise(a.from, b.from) ||
|
|
1667
|
+
bytewise(a.use, b.use),
|
|
1683
1668
|
);
|
|
1684
1669
|
|
|
1685
1670
|
return {
|
|
@@ -1687,7 +1672,7 @@ export function references<Brand>(
|
|
|
1687
1672
|
consumer === undefined
|
|
1688
1673
|
? found
|
|
1689
1674
|
: found.map((reference) => ({ ...reference, consumer: consumer.id })),
|
|
1690
|
-
testFilePaths: tests.sort(
|
|
1675
|
+
testFilePaths: tests.sort(bytewise),
|
|
1691
1676
|
cost: {
|
|
1692
1677
|
batched,
|
|
1693
1678
|
fileBatches,
|