@cplieger/deadset-ts 8.0.0-dev.5 → 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 +28 -46
- 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 +3 -2
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1084 -0
- package/dist/src/contract/report.generated.js +796 -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 +3 -63
- package/dist/src/exempt.js +13 -40
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +11 -20
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +4 -4
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +8 -7
- package/dist/src/findings/non-code-artifacts.js +5 -6
- package/dist/src/findings/reads-and-writes.js +11 -9
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +28 -27
- package/dist/src/findings/visibility-narrowing.js +9 -11
- package/dist/src/findings-pass.js +7 -14
- 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/index.js +1 -1
- package/dist/src/intra-function-parts.js +18 -26
- 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/kinds.js +4 -360
- 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 +272 -0
- package/dist/src/report.js +39 -42
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +12 -26
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +10 -8
- 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/setup-failure.js +2 -6
- package/dist/src/suppress-file.js +12 -13
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +29 -48
- 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 +22 -15
- package/dist/src/verbs/describe.js +7 -7
- package/dist/src/verbs/explain.js +7 -4
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +4 -2
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +4 -3
- 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 +2 -1
- package/src/analysis.ts +29 -54
- 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 +11 -15
- package/src/configuration-files.ts +27 -170
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1148 -0
- package/src/contract/report.generated.ts +1157 -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 +5 -74
- package/src/exempt.ts +17 -47
- package/src/file-facts.ts +6 -24
- package/src/finding.ts +9 -9
- package/src/findings/completion.ts +22 -23
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +6 -7
- package/src/findings/emitters.ts +6 -6
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +8 -8
- package/src/findings/non-code-artifacts.ts +5 -7
- package/src/findings/reads-and-writes.ts +15 -12
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +28 -27
- package/src/findings/visibility-narrowing.ts +9 -12
- package/src/findings-pass.ts +7 -16
- 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/index.ts +1 -1
- package/src/intra-function-parts.ts +18 -28
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +6 -406
- 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 +364 -0
- package/src/report.ts +66 -229
- package/src/reporters/annotations.ts +2 -2
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +17 -12
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +31 -38
- package/src/roots.ts +7 -20
- package/src/run.ts +10 -14
- 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/setup-failure.ts +5 -7
- package/src/suppress-file.ts +11 -15
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +39 -55
- 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 +38 -25
- package/src/verbs/describe.ts +13 -7
- package/src/verbs/explain.ts +7 -4
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +4 -2
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +0 -6
- package/src/verbs/version.ts +4 -3
- 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/dist/src/version.js +0 -7
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/version.ts +0 -7
package/src/fs/tree.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { DirectoryEntry, Host } from "../host.ts";
|
|
2
|
+
import { ancestors, joinPath, resolvePath } from "../paths.ts";
|
|
3
|
+
import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
|
|
4
|
+
import { LANGUAGE } from "../product.ts";
|
|
5
|
+
import { bytewise } from "../text/compare.ts";
|
|
6
|
+
import { globExpression } from "../text/glob.ts";
|
|
7
|
+
|
|
8
|
+
/** The directories no walk of the tree enters, by the patterns the language's row names. */
|
|
9
|
+
const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
|
|
10
|
+
|
|
11
|
+
function skipped(name: string): boolean {
|
|
12
|
+
return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A git repository's working tree root, and the `info/exclude` file git reads for it. */
|
|
16
|
+
interface Repository {
|
|
17
|
+
readonly root: string;
|
|
18
|
+
/** Undefined where a `.git` file names no repository directory. */
|
|
19
|
+
readonly exclude: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One file's text without its line ending, or undefined where it cannot be read. */
|
|
23
|
+
function lineOf(host: Host, path: string): string | undefined {
|
|
24
|
+
try {
|
|
25
|
+
return host.readFile(path).replace(/\r?\n$/u, "");
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The repository whose working tree holds `dir`: the nearest directory at or above it with a
|
|
33
|
+
* `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
|
|
34
|
+
* directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
|
|
35
|
+
* the directory shared with the main worktree, which holds `info/exclude`:
|
|
36
|
+
* https://git-scm.com/docs/gitrepository-layout
|
|
37
|
+
*/
|
|
38
|
+
export function repositoryOf(host: Host, dir: string): Repository | undefined {
|
|
39
|
+
for (const root of ancestors(dir)) {
|
|
40
|
+
const dotGit = joinPath(root, ".git");
|
|
41
|
+
const kind = host.kindOf(dotGit);
|
|
42
|
+
if (kind === "directory") {
|
|
43
|
+
return { root, exclude: joinPath(dotGit, "info/exclude") };
|
|
44
|
+
}
|
|
45
|
+
if (kind === "file") {
|
|
46
|
+
const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
|
|
47
|
+
const gitDir = named === undefined ? undefined : resolvePath(root, named);
|
|
48
|
+
const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
|
|
49
|
+
const shared =
|
|
50
|
+
gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
|
|
51
|
+
return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The entries of one directory, none where it cannot be read. */
|
|
58
|
+
export function entriesOf(host: Host, dir: string): readonly DirectoryEntry[] {
|
|
59
|
+
try {
|
|
60
|
+
return host.readDirectory(dir);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
|
|
67
|
+
export function filesIn(
|
|
68
|
+
host: Host,
|
|
69
|
+
dir: string,
|
|
70
|
+
accepts: (name: string) => boolean,
|
|
71
|
+
): readonly string[] {
|
|
72
|
+
return entriesOf(host, dir)
|
|
73
|
+
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
74
|
+
.map((entry) => entry.name)
|
|
75
|
+
.sort(bytewise);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The names of the directories directly in `dir` a walk enters, in bytewise order. */
|
|
79
|
+
export function directoriesIn(host: Host, dir: string): readonly string[] {
|
|
80
|
+
return entriesOf(host, dir)
|
|
81
|
+
.filter((entry) => entry.directory && !skipped(entry.name))
|
|
82
|
+
.map((entry) => entry.name)
|
|
83
|
+
.sort(bytewise);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
|
|
88
|
+
* order, read in no skipped directory.
|
|
89
|
+
*/
|
|
90
|
+
export function filesBelow(
|
|
91
|
+
host: Host,
|
|
92
|
+
root: string,
|
|
93
|
+
accepts: (name: string) => boolean,
|
|
94
|
+
): readonly string[] {
|
|
95
|
+
const found: string[] = [];
|
|
96
|
+
const pending = [""];
|
|
97
|
+
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
98
|
+
for (const entry of entriesOf(host, joinPath(root, dir))) {
|
|
99
|
+
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
100
|
+
if (!entry.directory) {
|
|
101
|
+
if (accepts(entry.name)) {
|
|
102
|
+
found.push(path);
|
|
103
|
+
}
|
|
104
|
+
} else if (!skipped(entry.name)) {
|
|
105
|
+
pending.push(path);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return found.sort(bytewise);
|
|
110
|
+
}
|
package/src/git-ignore.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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
5
|
|
|
6
|
-
import { globExpression } from "./glob.ts";
|
|
7
6
|
import type { Host } from "./host.ts";
|
|
8
|
-
import { dirnamePath, joinPath, relativePath } from "./paths.ts";
|
|
7
|
+
import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.ts";
|
|
8
|
+
import { gitignoreExpression } from "./text/glob.ts";
|
|
9
|
+
import { repositoryOf } from "./fs/tree.ts";
|
|
9
10
|
|
|
10
|
-
/** One pattern line of
|
|
11
|
+
/** One pattern line of an ignore file. */
|
|
11
12
|
interface Rule {
|
|
12
13
|
readonly negated: boolean;
|
|
13
14
|
/** Whether the pattern ends with `/`, so it matches a directory alone. */
|
|
@@ -16,7 +17,7 @@ interface Rule {
|
|
|
16
17
|
readonly expression: RegExp;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/** The rules one
|
|
20
|
+
/** The rules one ignore file's text writes, in order. */
|
|
20
21
|
function rulesOf(text: string): readonly Rule[] {
|
|
21
22
|
const rules: Rule[] = [];
|
|
22
23
|
for (const raw of text.split(/\r?\n/u)) {
|
|
@@ -25,7 +26,7 @@ function rulesOf(text: string): readonly Rule[] {
|
|
|
25
26
|
continue;
|
|
26
27
|
}
|
|
27
28
|
const negated = line.startsWith("!");
|
|
28
|
-
line = negated
|
|
29
|
+
line = negated ? line.slice(1) : line;
|
|
29
30
|
const directoryOnly = line.endsWith("/");
|
|
30
31
|
line = line.replace(/\/+$/u, "");
|
|
31
32
|
if (line === "") {
|
|
@@ -38,43 +39,55 @@ function rulesOf(text: string): readonly Rule[] {
|
|
|
38
39
|
rules.push({
|
|
39
40
|
negated,
|
|
40
41
|
directoryOnly,
|
|
41
|
-
expression:
|
|
42
|
+
expression: gitignoreExpression(anchored ? pattern : `**/${pattern}`),
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
return rules;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function rulesIn(host: Host, path: string): readonly Rule[] {
|
|
49
|
+
try {
|
|
50
|
+
return rulesOf(host.readFile(path));
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
/**
|
|
48
|
-
* Whether
|
|
49
|
-
*
|
|
50
|
-
*
|
|
57
|
+
* Whether git ignores one absolute path below `root`: by the repository's `info/exclude`,
|
|
58
|
+
* then the `.gitignore` files from the repository's root down to the path's directory, a
|
|
59
|
+
* later pattern overriding an earlier one. A path outside `root` is not ignored, and a file
|
|
60
|
+
* below an ignored directory stays ignored whatever a later pattern says, as git cannot
|
|
61
|
+
* re-include it. With no repository at or above `root`, the files at and below it are read;
|
|
62
|
+
* with one, the directories above `root` give patterns but are not themselves tested.
|
|
51
63
|
*/
|
|
52
64
|
export function gitIgnored(host: Host, root: string): (path: string) => boolean {
|
|
65
|
+
const found = repositoryOf(host, root);
|
|
66
|
+
const repository = found?.root;
|
|
67
|
+
const above =
|
|
68
|
+
repository === undefined ? [] : [...ancestors(dirnamePath(root), repository)].reverse();
|
|
69
|
+
const excluded = found?.exclude === undefined ? [] : rulesIn(host, found.exclude);
|
|
53
70
|
const read = new Map<string, readonly Rule[]>();
|
|
54
71
|
const rulesAt = (dir: string): readonly Rule[] => {
|
|
55
72
|
let rules = read.get(dir);
|
|
56
73
|
if (rules === undefined) {
|
|
57
|
-
|
|
58
|
-
rules = rulesOf(host.readFile(joinPath(dir, ".gitignore")));
|
|
59
|
-
} catch {
|
|
60
|
-
rules = [];
|
|
61
|
-
}
|
|
74
|
+
rules = rulesIn(host, joinPath(dir, ".gitignore"));
|
|
62
75
|
read.set(dir, rules);
|
|
63
76
|
}
|
|
64
77
|
return rules;
|
|
65
78
|
};
|
|
66
|
-
/** Whether the rules of `root` and of each directory down to one path's own decide it ignored. */
|
|
67
79
|
const decided = (path: string, directory: boolean): boolean => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
const sources: [string, readonly Rule[]][] = [
|
|
81
|
+
...(repository === undefined ? [] : [[repository, excluded] as [string, readonly Rule[]]]),
|
|
82
|
+
...[...above, ...[...ancestors(dirnamePath(path), root)].reverse()].map(
|
|
83
|
+
(dir): [string, readonly Rule[]] => [dir, rulesAt(dir)],
|
|
84
|
+
),
|
|
85
|
+
];
|
|
73
86
|
let ignored = false;
|
|
74
|
-
for (const dir of
|
|
75
|
-
const below = relativePath(dir, path);
|
|
76
|
-
for (const rule of
|
|
77
|
-
if ((directory || !rule.directoryOnly) && rule.expression.test(below
|
|
87
|
+
for (const [dir, rules] of sources) {
|
|
88
|
+
const below = relativePath(dir, path) ?? "";
|
|
89
|
+
for (const rule of rules) {
|
|
90
|
+
if ((directory || !rule.directoryOnly) && rule.expression.test(below)) {
|
|
78
91
|
ignored = !rule.negated;
|
|
79
92
|
}
|
|
80
93
|
}
|
package/src/html-entries.ts
CHANGED
|
@@ -12,8 +12,10 @@ import {
|
|
|
12
12
|
} from "@typescript/native/ast";
|
|
13
13
|
import { resolveRelativeSpecifier, type ParseFile } from "./configuration-files.ts";
|
|
14
14
|
import type { Host } from "./host.ts";
|
|
15
|
-
import { dirnamePath, joinPath,
|
|
15
|
+
import { ancestors, dirnamePath, joinPath, resolvePath } from "./paths.ts";
|
|
16
16
|
import type { SourceFiles } from "./source-files.ts";
|
|
17
|
+
import { filesBelow } from "./fs/tree.ts";
|
|
18
|
+
import { holdsManifest } from "./fs/manifest.ts";
|
|
17
19
|
|
|
18
20
|
/** One file a script loads, and the value or specifier that named it. */
|
|
19
21
|
interface HTMLEntry {
|
|
@@ -21,9 +23,6 @@ interface HTMLEntry {
|
|
|
21
23
|
readonly source: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/** The directory no HTML file is read below. */
|
|
25
|
-
const SKIPPED = "node_modules";
|
|
26
|
-
|
|
27
26
|
/** A `<script` start tag, its attribute text captured. */
|
|
28
27
|
const SCRIPT_TAG = /<script(?=[\s>/])([^>]*)>/giu;
|
|
29
28
|
|
|
@@ -42,28 +41,6 @@ const CLASSIC_TYPES: ReadonlySet<string> = new Set(["text/javascript", "applicat
|
|
|
42
41
|
/** The prefixes an inline script's specifier names a file of the target with. */
|
|
43
42
|
const FILE_PREFIXES = ["/", "./", "../"] as const;
|
|
44
43
|
|
|
45
|
-
/** Every HTML file below `dir`, outside every `node_modules` directory, by absolute path. */
|
|
46
|
-
function htmlFiles(host: Host, dir: string): readonly string[] {
|
|
47
|
-
let entries;
|
|
48
|
-
try {
|
|
49
|
-
entries = host.readDirectory(dir);
|
|
50
|
-
} catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
const found: string[] = [];
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
const path = joinPath(dir, entry.name);
|
|
56
|
-
if (entry.directory) {
|
|
57
|
-
if (entry.name !== SKIPPED) {
|
|
58
|
-
found.push(...htmlFiles(host, path));
|
|
59
|
-
}
|
|
60
|
-
} else if (entry.name.endsWith(".html")) {
|
|
61
|
-
found.push(path);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return found.sort();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
44
|
/** Each attribute of one start tag's attribute text, by its lowercase name. */
|
|
68
45
|
function attributes(text: string): ReadonlyMap<string, string> {
|
|
69
46
|
const found = new Map<string, string>();
|
|
@@ -127,12 +104,7 @@ function scriptValues(parse: ParseFile | undefined, path: string, text: string):
|
|
|
127
104
|
|
|
128
105
|
/** The directory of the nearest `package.json` at or above `dir`, inside `root`, else `root`. */
|
|
129
106
|
function manifestDirectory(host: Host, root: string, dir: string): string {
|
|
130
|
-
|
|
131
|
-
if (host.kindOf(joinPath(at, "package.json")) === "file") {
|
|
132
|
-
return at;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return root;
|
|
107
|
+
return ancestors(dir, root).find((at) => holdsManifest(host, at)) ?? root;
|
|
136
108
|
}
|
|
137
109
|
|
|
138
110
|
/**
|
|
@@ -150,7 +122,8 @@ export function htmlEntries(
|
|
|
150
122
|
): readonly HTMLEntry[] {
|
|
151
123
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
152
124
|
const found: HTMLEntry[] = [];
|
|
153
|
-
for (const
|
|
125
|
+
for (const below of filesBelow(host, root, (name) => name.endsWith(".html"))) {
|
|
126
|
+
const path = joinPath(root, below);
|
|
154
127
|
let text: string;
|
|
155
128
|
try {
|
|
156
129
|
text = host.readFile(path);
|
package/src/in-operands.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@typescript/native/ast";
|
|
14
14
|
import type { ProjectView } from "./session.ts";
|
|
15
15
|
import { sourceFilesOf } from "./source-files.ts";
|
|
16
|
+
import { bytewise } from "./text/compare.ts";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* The text of each string literal, or template literal with no substitution, written as
|
|
@@ -41,5 +42,5 @@ export function inOperandNames<Brand>(
|
|
|
41
42
|
file.forEachChild(visit);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
return [...found].sort();
|
|
45
|
+
return [...found].sort(bytewise);
|
|
45
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* write to a local that no read reaches, and each case an earlier case covers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { CODE } from "./contract/contract.generated.ts";
|
|
8
9
|
import {
|
|
9
10
|
isArrowFunction,
|
|
10
11
|
isAwaitExpression,
|
|
@@ -95,6 +96,7 @@ import type { Reference } from "./references.ts";
|
|
|
95
96
|
import type { ProjectView } from "./session.ts";
|
|
96
97
|
import { signatureOf, type SignatureNode } from "./signatures.ts";
|
|
97
98
|
import { spellsNoBinding } from "./spelled-names.ts";
|
|
99
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
98
100
|
|
|
99
101
|
/** The subject kind of each part this module reads. */
|
|
100
102
|
type PartKind = "parameter" | "result" | "statement" | "store" | "case";
|
|
@@ -179,13 +181,6 @@ export interface ProjectParts {
|
|
|
179
181
|
readonly constructions: ReadonlySet<string>;
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
/** The codes of the parts this module reads. */
|
|
183
|
-
export const UNUSED_PARAMETER = "DS1801";
|
|
184
|
-
export const UNUSED_RESULT = "DS1803";
|
|
185
|
-
const UNREACHABLE_STATEMENT = "DS1805";
|
|
186
|
-
const DEAD_STORE = "DS1807";
|
|
187
|
-
const UNREACHABLE_CASE = "DS1809";
|
|
188
|
-
|
|
189
184
|
/** The result types that carry no value, as the checker spells them. */
|
|
190
185
|
const NO_VALUE: ReadonlySet<string> = new Set([
|
|
191
186
|
"void",
|
|
@@ -203,11 +198,6 @@ function spanOf(file: SourceFile, root: string, node: Node): FindingPosition {
|
|
|
203
198
|
return { ...start, endLine };
|
|
204
199
|
}
|
|
205
200
|
|
|
206
|
-
/** The parenthesized expression's content, through every pair of parentheses. */
|
|
207
|
-
function unwrapped(node: Node): Node {
|
|
208
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
201
|
/** The node the parentheses around one expression sit in. */
|
|
212
202
|
function outerParent(node: Node): Node {
|
|
213
203
|
return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
|
|
@@ -215,7 +205,7 @@ function outerParent(node: Node): Node {
|
|
|
215
205
|
|
|
216
206
|
/** The name a call is made through: the identifier, or the member a property access names. */
|
|
217
207
|
function calleeName(call: CallExpression): Identifier | undefined {
|
|
218
|
-
const callee =
|
|
208
|
+
const callee = parenthesesRemoved(call.expression);
|
|
219
209
|
if (isIdentifier(callee)) {
|
|
220
210
|
return callee;
|
|
221
211
|
}
|
|
@@ -282,7 +272,7 @@ function unreachableExempt(statement: Statement): boolean {
|
|
|
282
272
|
|
|
283
273
|
/** The value a case expression is a literal of, as a key two equal literals share, or none. */
|
|
284
274
|
function caseKey(expression: Node): string | undefined {
|
|
285
|
-
const node =
|
|
275
|
+
const node = parenthesesRemoved(expression);
|
|
286
276
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
287
277
|
return `s:${node.text}`;
|
|
288
278
|
}
|
|
@@ -408,7 +398,7 @@ function extendedNames(files: readonly SourceFile[]): ReadonlySet<string> {
|
|
|
408
398
|
if (isTypeReferenceNode(type)) {
|
|
409
399
|
named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
|
|
410
400
|
} else {
|
|
411
|
-
const expression =
|
|
401
|
+
const expression = parenthesesRemoved(type.expression);
|
|
412
402
|
named = isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
413
403
|
}
|
|
414
404
|
if (isIdentifier(named)) {
|
|
@@ -461,7 +451,7 @@ function decoratorFixes(constructor: ConstructorDeclaration): boolean {
|
|
|
461
451
|
|
|
462
452
|
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
463
453
|
function spelledName(expression: Node): Identifier | undefined {
|
|
464
|
-
const node =
|
|
454
|
+
const node = parenthesesRemoved(expression);
|
|
465
455
|
if (isIdentifier(node)) {
|
|
466
456
|
return node;
|
|
467
457
|
}
|
|
@@ -730,8 +720,8 @@ export function projectParts<Brand>(
|
|
|
730
720
|
if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
|
|
731
721
|
specifiers.add(position(node.name));
|
|
732
722
|
}
|
|
733
|
-
if (isExportAssignment(node) && isIdentifier(
|
|
734
|
-
specifiers.add(position(
|
|
723
|
+
if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
|
|
724
|
+
specifiers.add(position(parenthesesRemoved(node.expression)));
|
|
735
725
|
}
|
|
736
726
|
if (isSwitchStatement(node)) {
|
|
737
727
|
const holder = holderOf(node);
|
|
@@ -869,7 +859,7 @@ export function projectParts<Brand>(
|
|
|
869
859
|
}
|
|
870
860
|
walk.catches.forEach(({ name, holder }, at) => {
|
|
871
861
|
parts.push({
|
|
872
|
-
code:
|
|
862
|
+
code: CODE.deadStore,
|
|
873
863
|
declaration: holder,
|
|
874
864
|
kind: "store",
|
|
875
865
|
name: name.text,
|
|
@@ -881,7 +871,7 @@ export function projectParts<Brand>(
|
|
|
881
871
|
for (const { names, uses, holder } of loopVariables) {
|
|
882
872
|
for (const name of names) {
|
|
883
873
|
parts.push({
|
|
884
|
-
code:
|
|
874
|
+
code: CODE.deadStore,
|
|
885
875
|
declaration: holder,
|
|
886
876
|
kind: "store",
|
|
887
877
|
name: name.text,
|
|
@@ -899,7 +889,7 @@ export function projectParts<Brand>(
|
|
|
899
889
|
});
|
|
900
890
|
for (const { statement, holder } of walk.unreachable) {
|
|
901
891
|
parts.push({
|
|
902
|
-
code:
|
|
892
|
+
code: CODE.unreachableStatement,
|
|
903
893
|
declaration: holder,
|
|
904
894
|
kind: "statement",
|
|
905
895
|
name: "statement",
|
|
@@ -910,7 +900,7 @@ export function projectParts<Brand>(
|
|
|
910
900
|
}
|
|
911
901
|
for (const { clause, text, holder } of walk.cases) {
|
|
912
902
|
parts.push({
|
|
913
|
-
code:
|
|
903
|
+
code: CODE.unreachableCase,
|
|
914
904
|
declaration: holder,
|
|
915
905
|
kind: "case",
|
|
916
906
|
name: text,
|
|
@@ -1176,7 +1166,7 @@ function parameterParts(
|
|
|
1176
1166
|
return;
|
|
1177
1167
|
}
|
|
1178
1168
|
const part: PartFact = {
|
|
1179
|
-
code:
|
|
1169
|
+
code: CODE.unusedParameter,
|
|
1180
1170
|
declaration,
|
|
1181
1171
|
kind: "parameter",
|
|
1182
1172
|
name: name.text,
|
|
@@ -1331,7 +1321,7 @@ function isPlainWrite(use: Identifier): boolean {
|
|
|
1331
1321
|
return (
|
|
1332
1322
|
isBinaryExpression(parent) &&
|
|
1333
1323
|
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
1334
|
-
|
|
1324
|
+
parenthesesRemoved(parent.left) === use
|
|
1335
1325
|
);
|
|
1336
1326
|
}
|
|
1337
1327
|
|
|
@@ -1353,7 +1343,7 @@ function deadStores(
|
|
|
1353
1343
|
): PartFact[] {
|
|
1354
1344
|
const found: PartFact[] = [];
|
|
1355
1345
|
const deadAt = (local: Local, target: Identifier): PartFact => ({
|
|
1356
|
-
code:
|
|
1346
|
+
code: CODE.deadStore,
|
|
1357
1347
|
declaration: holder,
|
|
1358
1348
|
kind: "store",
|
|
1359
1349
|
name: local.name.text,
|
|
@@ -1388,14 +1378,14 @@ function deadStores(
|
|
|
1388
1378
|
if (!isExpressionStatement(statement)) {
|
|
1389
1379
|
return undefined;
|
|
1390
1380
|
}
|
|
1391
|
-
const expression =
|
|
1381
|
+
const expression = parenthesesRemoved(statement.expression);
|
|
1392
1382
|
if (
|
|
1393
1383
|
!isBinaryExpression(expression) ||
|
|
1394
1384
|
expression.operatorToken.kind !== SyntaxKind.EqualsToken
|
|
1395
1385
|
) {
|
|
1396
1386
|
return undefined;
|
|
1397
1387
|
}
|
|
1398
|
-
const target =
|
|
1388
|
+
const target = parenthesesRemoved(expression.left);
|
|
1399
1389
|
return isIdentifier(target) && uses.includes(target)
|
|
1400
1390
|
? { target, value: expression.right }
|
|
1401
1391
|
: undefined;
|
|
@@ -1482,7 +1472,7 @@ function resultParts<Brand>(
|
|
|
1482
1472
|
const valued = project.queries.nonNullable(result);
|
|
1483
1473
|
const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
|
|
1484
1474
|
found.push({
|
|
1485
|
-
code:
|
|
1475
|
+
code: CODE.unusedResult,
|
|
1486
1476
|
declaration: id,
|
|
1487
1477
|
kind: "result",
|
|
1488
1478
|
name: "result",
|
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
|
});
|