@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
package/dist/src/git-ignore.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which files
|
|
2
|
+
* Which files git ignores below a tree's root, read as git reads its ignore files:
|
|
3
3
|
* https://git-scm.com/docs/gitignore#_pattern_format
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
6
|
+
import { gitignoreExpression } from "./text/glob.js";
|
|
7
|
+
import { repositoryOf } from "./fs/tree.js";
|
|
8
|
+
/** The rules one ignore file's text writes, in order. */
|
|
8
9
|
function rulesOf(text) {
|
|
9
10
|
const rules = [];
|
|
10
11
|
for (const raw of text.split(/\r?\n/u)) {
|
|
@@ -13,7 +14,7 @@ function rulesOf(text) {
|
|
|
13
14
|
continue;
|
|
14
15
|
}
|
|
15
16
|
const negated = line.startsWith("!");
|
|
16
|
-
line = negated
|
|
17
|
+
line = negated ? line.slice(1) : line;
|
|
17
18
|
const directoryOnly = line.endsWith("/");
|
|
18
19
|
line = line.replace(/\/+$/u, "");
|
|
19
20
|
if (line === "") {
|
|
@@ -26,43 +27,51 @@ function rulesOf(text) {
|
|
|
26
27
|
rules.push({
|
|
27
28
|
negated,
|
|
28
29
|
directoryOnly,
|
|
29
|
-
expression:
|
|
30
|
+
expression: gitignoreExpression(anchored ? pattern : `**/${pattern}`),
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
return rules;
|
|
33
34
|
}
|
|
35
|
+
function rulesIn(host, path) {
|
|
36
|
+
try {
|
|
37
|
+
return rulesOf(host.readFile(path));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
34
43
|
/**
|
|
35
|
-
* Whether
|
|
36
|
-
*
|
|
37
|
-
*
|
|
44
|
+
* Whether git ignores one absolute path below `root`: by the repository's `info/exclude`,
|
|
45
|
+
* then the `.gitignore` files from the repository's root down to the path's directory, a
|
|
46
|
+
* later pattern overriding an earlier one. A path outside `root` is not ignored, and a file
|
|
47
|
+
* below an ignored directory stays ignored whatever a later pattern says, as git cannot
|
|
48
|
+
* re-include it. With no repository at or above `root`, the files at and below it are read;
|
|
49
|
+
* with one, the directories above `root` give patterns but are not themselves tested.
|
|
38
50
|
*/
|
|
39
51
|
export function gitIgnored(host, root) {
|
|
52
|
+
const found = repositoryOf(host, root);
|
|
53
|
+
const repository = found?.root;
|
|
54
|
+
const above = repository === undefined ? [] : [...ancestors(dirnamePath(root), repository)].reverse();
|
|
55
|
+
const excluded = found?.exclude === undefined ? [] : rulesIn(host, found.exclude);
|
|
40
56
|
const read = new Map();
|
|
41
57
|
const rulesAt = (dir) => {
|
|
42
58
|
let rules = read.get(dir);
|
|
43
59
|
if (rules === undefined) {
|
|
44
|
-
|
|
45
|
-
rules = rulesOf(host.readFile(joinPath(dir, ".gitignore")));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
rules = [];
|
|
49
|
-
}
|
|
60
|
+
rules = rulesIn(host, joinPath(dir, ".gitignore"));
|
|
50
61
|
read.set(dir, rules);
|
|
51
62
|
}
|
|
52
63
|
return rules;
|
|
53
64
|
};
|
|
54
|
-
/** Whether the rules of `root` and of each directory down to one path's own decide it ignored. */
|
|
55
65
|
const decided = (path, directory) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
dirs.unshift(root);
|
|
66
|
+
const sources = [
|
|
67
|
+
...(repository === undefined ? [] : [[repository, excluded]]),
|
|
68
|
+
...[...above, ...[...ancestors(dirnamePath(path), root)].reverse()].map((dir) => [dir, rulesAt(dir)]),
|
|
69
|
+
];
|
|
61
70
|
let ignored = false;
|
|
62
|
-
for (const dir of
|
|
63
|
-
const below = relativePath(dir, path);
|
|
64
|
-
for (const rule of
|
|
65
|
-
if ((directory || !rule.directoryOnly) && rule.expression.test(below
|
|
71
|
+
for (const [dir, rules] of sources) {
|
|
72
|
+
const below = relativePath(dir, path) ?? "";
|
|
73
|
+
for (const rule of rules) {
|
|
74
|
+
if ((directory || !rule.directoryOnly) && rule.expression.test(below)) {
|
|
66
75
|
ignored = !rule.negated;
|
|
67
76
|
}
|
|
68
77
|
}
|
package/dist/src/html-entries.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isExportDeclaration, isImportDeclaration, isStringLiteral, } from "@typescript/native/ast";
|
|
7
7
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
8
|
-
import { dirnamePath, joinPath,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { ancestors, dirnamePath, joinPath, resolvePath } from "./paths.js";
|
|
9
|
+
import { filesBelow } from "./fs/tree.js";
|
|
10
|
+
import { holdsManifest } from "./fs/manifest.js";
|
|
11
11
|
/** A `<script` start tag, its attribute text captured. */
|
|
12
12
|
const SCRIPT_TAG = /<script(?=[\s>/])([^>]*)>/giu;
|
|
13
13
|
/** The end tag an inline module script's content runs to. */
|
|
@@ -20,29 +20,6 @@ const ELSEWHERE = /^(?:\/\/|[A-Za-z][A-Za-z\d+.-]*:)/u;
|
|
|
20
20
|
const CLASSIC_TYPES = new Set(["text/javascript", "application/javascript"]);
|
|
21
21
|
/** The prefixes an inline script's specifier names a file of the target with. */
|
|
22
22
|
const FILE_PREFIXES = ["/", "./", "../"];
|
|
23
|
-
/** Every HTML file below `dir`, outside every `node_modules` directory, by absolute path. */
|
|
24
|
-
function htmlFiles(host, dir) {
|
|
25
|
-
let entries;
|
|
26
|
-
try {
|
|
27
|
-
entries = host.readDirectory(dir);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
const found = [];
|
|
33
|
-
for (const entry of entries) {
|
|
34
|
-
const path = joinPath(dir, entry.name);
|
|
35
|
-
if (entry.directory) {
|
|
36
|
-
if (entry.name !== SKIPPED) {
|
|
37
|
-
found.push(...htmlFiles(host, path));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else if (entry.name.endsWith(".html")) {
|
|
41
|
-
found.push(path);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return found.sort();
|
|
45
|
-
}
|
|
46
23
|
/** Each attribute of one start tag's attribute text, by its lowercase name. */
|
|
47
24
|
function attributes(text) {
|
|
48
25
|
const found = new Map();
|
|
@@ -98,12 +75,7 @@ function scriptValues(parse, path, text) {
|
|
|
98
75
|
}
|
|
99
76
|
/** The directory of the nearest `package.json` at or above `dir`, inside `root`, else `root`. */
|
|
100
77
|
function manifestDirectory(host, root, dir) {
|
|
101
|
-
|
|
102
|
-
if (host.kindOf(joinPath(at, "package.json")) === "file") {
|
|
103
|
-
return at;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return root;
|
|
78
|
+
return ancestors(dir, root).find((at) => holdsManifest(host, at)) ?? root;
|
|
107
79
|
}
|
|
108
80
|
/**
|
|
109
81
|
* The own files the scripts of every HTML file below the target root load. A value
|
|
@@ -115,7 +87,8 @@ function manifestDirectory(host, root, dir) {
|
|
|
115
87
|
export function htmlEntries(host, targetRoot, files, parse) {
|
|
116
88
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
117
89
|
const found = [];
|
|
118
|
-
for (const
|
|
90
|
+
for (const below of filesBelow(host, root, (name) => name.endsWith(".html"))) {
|
|
91
|
+
const path = joinPath(root, below);
|
|
119
92
|
let text;
|
|
120
93
|
try {
|
|
121
94
|
text = host.readFile(path);
|
package/dist/src/in-operands.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isBinaryExpression, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
7
7
|
import { sourceFilesOf } from "./source-files.js";
|
|
8
|
+
import { bytewise } from "./text/compare.js";
|
|
8
9
|
/**
|
|
9
10
|
* The text of each string literal, or template literal with no substitution, written as
|
|
10
11
|
* the left operand of an `in` expression in a file of the project that is no test file.
|
|
@@ -26,5 +27,5 @@ export function inOperandNames(project, targetRoot, testFiles) {
|
|
|
26
27
|
file.forEachChild(visit);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
return [...found].sort();
|
|
30
|
+
return [...found].sort(bytewise);
|
|
30
31
|
}
|
|
@@ -14,6 +14,7 @@ import { importRef } from "./ref.js";
|
|
|
14
14
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
15
15
|
import { signatureOf } from "./signatures.js";
|
|
16
16
|
import { spellsNoBinding } from "./spelled-names.js";
|
|
17
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
17
18
|
/** The result types that carry no value, as the checker spells them. */
|
|
18
19
|
const NO_VALUE = new Set([
|
|
19
20
|
"void",
|
|
@@ -29,17 +30,13 @@ function spanOf(file, root, node) {
|
|
|
29
30
|
const endLine = file.getLineAndCharacterOfPosition(node.end).line + 1;
|
|
30
31
|
return { ...start, endLine };
|
|
31
32
|
}
|
|
32
|
-
/** The parenthesized expression's content, through every pair of parentheses. */
|
|
33
|
-
function unwrapped(node) {
|
|
34
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
35
|
-
}
|
|
36
33
|
/** The node the parentheses around one expression sit in. */
|
|
37
34
|
function outerParent(node) {
|
|
38
35
|
return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
|
|
39
36
|
}
|
|
40
37
|
/** The name a call is made through: the identifier, or the member a property access names. */
|
|
41
38
|
function calleeName(call) {
|
|
42
|
-
const callee =
|
|
39
|
+
const callee = parenthesesRemoved(call.expression);
|
|
43
40
|
if (isIdentifier(callee)) {
|
|
44
41
|
return callee;
|
|
45
42
|
}
|
|
@@ -94,7 +91,7 @@ function unreachableExempt(statement) {
|
|
|
94
91
|
}
|
|
95
92
|
/** The value a case expression is a literal of, as a key two equal literals share, or none. */
|
|
96
93
|
function caseKey(expression) {
|
|
97
|
-
const node =
|
|
94
|
+
const node = parenthesesRemoved(expression);
|
|
98
95
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
99
96
|
return `s:${node.text}`;
|
|
100
97
|
}
|
|
@@ -205,7 +202,7 @@ function extendedNames(files) {
|
|
|
205
202
|
named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
|
|
206
203
|
}
|
|
207
204
|
else {
|
|
208
|
-
const expression =
|
|
205
|
+
const expression = parenthesesRemoved(type.expression);
|
|
209
206
|
named = isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
210
207
|
}
|
|
211
208
|
if (isIdentifier(named)) {
|
|
@@ -249,7 +246,7 @@ function decoratorFixes(constructor) {
|
|
|
249
246
|
}
|
|
250
247
|
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
251
248
|
function spelledName(expression) {
|
|
252
|
-
const node =
|
|
249
|
+
const node = parenthesesRemoved(expression);
|
|
253
250
|
if (isIdentifier(node)) {
|
|
254
251
|
return node;
|
|
255
252
|
}
|
|
@@ -442,8 +439,8 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
442
439
|
if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
|
|
443
440
|
specifiers.add(position(node.name));
|
|
444
441
|
}
|
|
445
|
-
if (isExportAssignment(node) && isIdentifier(
|
|
446
|
-
specifiers.add(position(
|
|
442
|
+
if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
|
|
443
|
+
specifiers.add(position(parenthesesRemoved(node.expression)));
|
|
447
444
|
}
|
|
448
445
|
if (isSwitchStatement(node)) {
|
|
449
446
|
const holder = holderOf(node);
|
|
@@ -946,7 +943,7 @@ function isPlainWrite(use) {
|
|
|
946
943
|
const parent = outerParent(use);
|
|
947
944
|
return (isBinaryExpression(parent) &&
|
|
948
945
|
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
949
|
-
|
|
946
|
+
parenthesesRemoved(parent.left) === use);
|
|
950
947
|
}
|
|
951
948
|
/**
|
|
952
949
|
* The writes to the locals of one block that no read reaches. A write is a declaration's
|
|
@@ -992,12 +989,12 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
992
989
|
if (!isExpressionStatement(statement)) {
|
|
993
990
|
return undefined;
|
|
994
991
|
}
|
|
995
|
-
const expression =
|
|
992
|
+
const expression = parenthesesRemoved(statement.expression);
|
|
996
993
|
if (!isBinaryExpression(expression) ||
|
|
997
994
|
expression.operatorToken.kind !== SyntaxKind.EqualsToken) {
|
|
998
995
|
return undefined;
|
|
999
996
|
}
|
|
1000
|
-
const target =
|
|
997
|
+
const target = parenthesesRemoved(expression.left);
|
|
1001
998
|
return isIdentifier(target) && uses.includes(target)
|
|
1002
999
|
? { target, value: expression.right }
|
|
1003
1000
|
: undefined;
|
package/dist/src/inventory.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
|
3
3
|
import { byPosition, positionKey, renderPosition, writtenLength, } from "./position.js";
|
|
4
4
|
import { computedComponent, nameComponent, renderRef } from "./ref.js";
|
|
5
5
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
6
|
-
|
|
6
|
+
import { manifestAt } from "./fs/manifest.js";
|
|
7
7
|
/**
|
|
8
8
|
* Resolves each file's package scope: the name of the nearest manifest at or above
|
|
9
9
|
* its directory up to the target root, and the file's path below that manifest.
|
|
@@ -17,26 +17,7 @@ const MANIFEST = "package.json";
|
|
|
17
17
|
export function packageScope(host, root) {
|
|
18
18
|
const named = new Map();
|
|
19
19
|
const manifestName = (dir) => {
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
text = host.readFile(joinPath(root, dir, MANIFEST));
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
let value;
|
|
28
|
-
try {
|
|
29
|
-
value = JSON.parse(text);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// A manifest this program cannot read names no package, which is the same
|
|
33
|
-
// answer as an absent one: the file is then scoped by the target root.
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
if (typeof value !== "object" || value === null) {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
const name = value["name"];
|
|
20
|
+
const name = manifestAt(host, joinPath(root, dir))?.["name"];
|
|
40
21
|
return typeof name === "string" && name !== "" ? name : undefined;
|
|
41
22
|
};
|
|
42
23
|
const nearest = (dir) => {
|
|
@@ -111,3 +111,7 @@ export function positionAt(text, file, offset) {
|
|
|
111
111
|
const lineStart = before.lastIndexOf("\n") + 1;
|
|
112
112
|
return { path: file, line: before.split("\n").length, column: offset - lineStart + 1 };
|
|
113
113
|
}
|
|
114
|
+
/** Whether a decoded JSON value is an object: not `null` and not an array. */
|
|
115
|
+
export function isRecord(value) {
|
|
116
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
117
|
+
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* that holds `keyof X`, the literal the other side has names the member of `X` under that
|
|
5
5
|
* key, because deleting the member makes the comparison a type error.
|
|
6
6
|
*/
|
|
7
|
-
import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier,
|
|
7
|
+
import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier, isPropertyAccessExpression, isSwitchStatement, isTypeOperatorNode, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
9
9
|
import { nodeKey } from "./inventory.js";
|
|
10
10
|
import { renderPosition } from "./position.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
12
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
12
13
|
/** The operators that compare their operands for equality. */
|
|
13
14
|
const EQUALITIES = new Set([
|
|
14
15
|
SyntaxKind.EqualsEqualsEqualsToken,
|
|
@@ -16,12 +17,9 @@ const EQUALITIES = new Set([
|
|
|
16
17
|
SyntaxKind.EqualsEqualsToken,
|
|
17
18
|
SyntaxKind.ExclamationEqualsToken,
|
|
18
19
|
]);
|
|
19
|
-
function unwrapped(node) {
|
|
20
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
21
|
-
}
|
|
22
20
|
/** The name node an operand's symbol is read at: an identifier, or a property access's name. */
|
|
23
21
|
function nameOf(node) {
|
|
24
|
-
const at =
|
|
22
|
+
const at = parenthesesRemoved(node);
|
|
25
23
|
if (isIdentifier(at)) {
|
|
26
24
|
return at;
|
|
27
25
|
}
|
|
@@ -96,7 +94,7 @@ export function keyofComparisonReferences(project, held, chains, root, testFiles
|
|
|
96
94
|
}
|
|
97
95
|
const named = comparisons.flatMap((one) => {
|
|
98
96
|
const name = nameOf(one.keyed);
|
|
99
|
-
return name === undefined || isElementAccessExpression(
|
|
97
|
+
return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
|
|
100
98
|
? []
|
|
101
99
|
: [{ ...one, name }];
|
|
102
100
|
});
|
package/dist/src/manifest.js
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
* shell running its command or one of its scripts. {@link readManifest} reads one
|
|
5
5
|
* directory's manifest, {@link packageEntries} those of the packages below the target root.
|
|
6
6
|
*/
|
|
7
|
-
import { isAbsolutePath,
|
|
7
|
+
import { isAbsolutePath, relativePath, resolvePath } from "./paths.js";
|
|
8
|
+
import { isRecord } from "./json-document.js";
|
|
9
|
+
import { MANIFEST, manifestAt } from "./fs/manifest.js";
|
|
10
|
+
import { scriptTokens } from "./text/shell.js";
|
|
8
11
|
/** The manifest's own name. */
|
|
9
|
-
const MANIFEST = "package.json";
|
|
10
12
|
/** The three members that name an entry point beside `exports` and `bin`. */
|
|
11
13
|
const LEGACY_MEMBERS = ["main", "module", "types"];
|
|
12
|
-
function isRecord(value) {
|
|
13
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
-
}
|
|
15
14
|
/**
|
|
16
15
|
* Whether one member's value names a file of this package rather than another
|
|
17
16
|
* package or a URL. A target written with no leading dot is a path too, which is
|
|
@@ -23,21 +22,6 @@ function isTarget(value, requireDot) {
|
|
|
23
22
|
}
|
|
24
23
|
return !requireDot || value.startsWith("./");
|
|
25
24
|
}
|
|
26
|
-
/** Where a script's command line splits into tokens: white space and the shell's operators. */
|
|
27
|
-
const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
|
|
28
|
-
/** A token written inside one pair of quotes. */
|
|
29
|
-
const QUOTED = /^(["'])(.*)\1$/su;
|
|
30
|
-
/**
|
|
31
|
-
* Every token one script's command line holds, each with one pair of enclosing quotes
|
|
32
|
-
* removed. A token holding a wildcard is a pattern rather than a path, and names no
|
|
33
|
-
* entry point.
|
|
34
|
-
*/
|
|
35
|
-
export function scriptTokens(command) {
|
|
36
|
-
return command
|
|
37
|
-
.split(SCRIPT_SEPARATORS)
|
|
38
|
-
.map((token) => token.replace(QUOTED, "$2"))
|
|
39
|
-
.filter((token) => token !== "" && !token.includes("*"));
|
|
40
|
-
}
|
|
41
25
|
/** One member's spelling below another: `exports` then `exports["."]`. */
|
|
42
26
|
function below(member, key) {
|
|
43
27
|
return `${member}[${JSON.stringify(key)}]`;
|
|
@@ -69,28 +53,6 @@ function exportTargets(value, member, found) {
|
|
|
69
53
|
exportTargets(held, below(member, key), found);
|
|
70
54
|
}
|
|
71
55
|
}
|
|
72
|
-
/** The manifest at the target root, decoded, or undefined where there is none to read. */
|
|
73
|
-
function manifestOf(host, targetRoot) {
|
|
74
|
-
let text;
|
|
75
|
-
try {
|
|
76
|
-
text = host.readFile(joinPath(targetRoot, MANIFEST));
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
let value;
|
|
82
|
-
try {
|
|
83
|
-
value = JSON.parse(text);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// A manifest this program cannot read names no entry point, which is the same
|
|
87
|
-
// answer as an absent one. What a manifest's contents say is not refused here:
|
|
88
|
-
// the package manager owns that refusal, and an analysis of a tree it rejects
|
|
89
|
-
// is not the run's subject.
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
return isRecord(value) ? value : undefined;
|
|
93
|
-
}
|
|
94
56
|
/**
|
|
95
57
|
* What the manifest in `targetRoot` declares about where its package is entered.
|
|
96
58
|
*
|
|
@@ -99,7 +61,7 @@ function manifestOf(host, targetRoot) {
|
|
|
99
61
|
* it and there is one such configuration per project, while there is one manifest.
|
|
100
62
|
*/
|
|
101
63
|
export function readManifest(host, targetRoot) {
|
|
102
|
-
const manifest =
|
|
64
|
+
const manifest = manifestAt(host, targetRoot);
|
|
103
65
|
if (manifest === undefined) {
|
|
104
66
|
return { entries: [], declaresExports: false };
|
|
105
67
|
}
|
package/dist/src/mdx-entries.js
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
7
7
|
import { dirnamePath, joinPath, resolvePath } from "./paths.js";
|
|
8
|
-
|
|
9
|
-
const SKIPPED = new Set(["node_modules", ".git"]);
|
|
8
|
+
import { filesBelow } from "./fs/tree.js";
|
|
10
9
|
/** The extensions of a document whose imports an MDX compiler reads. */
|
|
11
10
|
const DOCUMENT = /\.mdx?$/u;
|
|
12
11
|
/** A fence line, its marker run and what follows it captured. */
|
|
@@ -100,29 +99,6 @@ function clauseNames(clause) {
|
|
|
100
99
|
}
|
|
101
100
|
return names;
|
|
102
101
|
}
|
|
103
|
-
/** Every document below `dir`, outside every skipped directory, by absolute path. */
|
|
104
|
-
function documents(host, dir) {
|
|
105
|
-
let entries;
|
|
106
|
-
try {
|
|
107
|
-
entries = host.readDirectory(dir);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return [];
|
|
111
|
-
}
|
|
112
|
-
const found = [];
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
const path = joinPath(dir, entry.name);
|
|
115
|
-
if (entry.directory) {
|
|
116
|
-
if (!SKIPPED.has(entry.name)) {
|
|
117
|
-
found.push(...documents(host, path));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else if (DOCUMENT.test(entry.name)) {
|
|
121
|
-
found.push(path);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return found.sort();
|
|
125
|
-
}
|
|
126
102
|
/** The relative specifiers one document's import lines name, its fenced code blocks skipped. */
|
|
127
103
|
function importedBy(text) {
|
|
128
104
|
const found = [];
|
|
@@ -165,7 +141,8 @@ function documentImports(host, root) {
|
|
|
165
141
|
if (known !== undefined) {
|
|
166
142
|
return known;
|
|
167
143
|
}
|
|
168
|
-
const found =
|
|
144
|
+
const found = filesBelow(host, root, (name) => DOCUMENT.test(name)).flatMap((below) => {
|
|
145
|
+
const path = joinPath(root, below);
|
|
169
146
|
try {
|
|
170
147
|
return [{ path, imports: importedBy(host.readFile(path)) }];
|
|
171
148
|
}
|
package/dist/src/partial.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { relativePath } from "./paths.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
/** A count with its noun, so a line reads for one as well as for several. */
|
|
3
4
|
function counted(n, one, many) {
|
|
4
5
|
return `${String(n)} ${n === 1 ? one : many}`;
|
|
@@ -10,7 +11,7 @@ function counted(n, one, many) {
|
|
|
10
11
|
*/
|
|
11
12
|
export function partialNotes(partial) {
|
|
12
13
|
const notes = [...partial.notBuilt]
|
|
13
|
-
.sort((a, b) => (a.id
|
|
14
|
+
.sort((a, b) => bytewise(a.id, b.id))
|
|
14
15
|
.map((one) => `the derived configuration ${one.id} was not built and is not analyzed: ${one.error}`);
|
|
15
16
|
if (partial.unanswered.length > 0) {
|
|
16
17
|
const byProject = new Map();
|
package/dist/src/paths.js
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
|
const SEPARATOR = "/";
|
|
11
7
|
/** Whether one path names a place without a directory to resolve it against. */
|
|
@@ -61,6 +57,24 @@ export function dirnamePath(path) {
|
|
|
61
57
|
}
|
|
62
58
|
return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
|
|
63
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* `dir` and every directory above it, nearest first, normalized. With `top`, the list ends
|
|
62
|
+
* at `top` and is empty where `dir` is neither `top` nor below it; without, it ends at the
|
|
63
|
+
* root of the path.
|
|
64
|
+
*/
|
|
65
|
+
export function ancestors(dir, top) {
|
|
66
|
+
const end = top === undefined ? undefined : normalizePath(top);
|
|
67
|
+
const found = [];
|
|
68
|
+
for (let at = normalizePath(dir);; at = dirnamePath(at)) {
|
|
69
|
+
if (end !== undefined && at !== end && relativePath(end, at) === undefined) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
found.push(at);
|
|
73
|
+
if (at === end || dirnamePath(at) === at) {
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
64
78
|
/**
|
|
65
79
|
* The extension of one path's last segment, from its last full stop, or the empty string
|
|
66
80
|
* where it has none. A segment that only starts with a full stop has none.
|
|
@@ -92,14 +106,11 @@ export function queriedModulePath(specifier) {
|
|
|
92
106
|
return raw ? undefined : specifier.slice(0, query);
|
|
93
107
|
}
|
|
94
108
|
/**
|
|
95
|
-
* One path as it reads below a root, or undefined when it is not below the root
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* The caller resolves both against the same directory before calling: a relative
|
|
102
|
-
* root and an absolute path are two spellings this cannot reconcile.
|
|
109
|
+
* One path as it reads below a root, or undefined when it is not strictly below the root:
|
|
110
|
+
* the root itself is not below itself. Both are normalized, then compared lexically, so a
|
|
111
|
+
* root and a path spelled through different symbolic links name nothing in common. The
|
|
112
|
+
* caller resolves both against the same directory first: a relative root and an absolute
|
|
113
|
+
* path cannot be reconciled.
|
|
103
114
|
*/
|
|
104
115
|
export function relativePath(root, path) {
|
|
105
116
|
const at = normalizePath(root);
|
package/dist/src/position.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { relativePath } from "./paths.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
/**
|
|
3
4
|
* A file the program holds whose path is not below the target root, which leaves
|
|
4
5
|
* the analysis unable to name a declaration it found. It ends the run rather than
|
|
@@ -58,7 +59,7 @@ export function positionKey(position) {
|
|
|
58
59
|
*/
|
|
59
60
|
export function byPosition(a, b) {
|
|
60
61
|
if (a.path !== b.path) {
|
|
61
|
-
return a.path
|
|
62
|
+
return bytewise(a.path, b.path);
|
|
62
63
|
}
|
|
63
64
|
if (a.line !== b.line) {
|
|
64
65
|
return a.line - b.line;
|
package/dist/src/print.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { renderOrigin, } from "./config.js";
|
|
2
|
+
import { bytewise } from "./text/compare.js";
|
|
2
3
|
/** One declaration entry as a document writes it: the members of its shape alone. */
|
|
3
4
|
function declarationOf(entry) {
|
|
4
5
|
switch (entry.shape) {
|
|
@@ -50,7 +51,7 @@ export function printConfig(config, provenance) {
|
|
|
50
51
|
},
|
|
51
52
|
consumers: { complete: config.consumersComplete },
|
|
52
53
|
roots: { patterns: config.rootPatterns },
|
|
53
|
-
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => (a
|
|
54
|
+
severity: Object.fromEntries([...config.severity].sort(([a], [b]) => bytewise(a, b))),
|
|
54
55
|
exemptions: { disabled: config.exemptionsDisabled },
|
|
55
56
|
reporters: {
|
|
56
57
|
formats: config.reporters.formats,
|
|
@@ -76,7 +77,7 @@ export function printConfig(config, provenance) {
|
|
|
76
77
|
roots_complete: config.ts.rootsComplete,
|
|
77
78
|
},
|
|
78
79
|
provenance: Object.fromEntries([...provenance]
|
|
79
|
-
.sort(([a], [b]) => (a
|
|
80
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
80
81
|
.map(([path, origin]) => [path, renderOrigin(origin)])),
|
|
81
82
|
};
|
|
82
83
|
return `${JSON.stringify(document, undefined, 2)}\n`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LANGUAGE_ROWS } from "./contract/contract.generated.js";
|
|
2
|
+
/** The language this analyzer reads. */
|
|
3
|
+
export const LANGUAGE = "ts";
|
|
4
|
+
/** The analyzer's name: the one its language's row names, which every output spells it with. */
|
|
5
|
+
export const PRODUCT = LANGUAGE_ROWS[LANGUAGE].analyzer;
|
|
6
|
+
/** Writes one diagnostic line to the error stream, named for the analyzer. */
|
|
7
|
+
export function diagnose(err, text) {
|
|
8
|
+
err.write(`${PRODUCT}: ${text}\n`);
|
|
9
|
+
}
|
|
10
|
+
/** The text one thrown value carries. */
|
|
11
|
+
export function message(error) {
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|