@cplieger/deadset-ts 4.0.1 → 5.0.0

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.
Files changed (117) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +229 -36
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +186 -0
  10. package/dist/src/conformance-record.js +2 -2
  11. package/dist/src/convention-rows.js +383 -0
  12. package/dist/src/conventions.js +456 -0
  13. package/dist/src/dependencies.js +30 -3
  14. package/dist/src/discover.js +10 -3
  15. package/dist/src/emit-map.js +72 -0
  16. package/dist/src/entry-point-gaps.js +81 -35
  17. package/dist/src/entry-points.js +47 -32
  18. package/dist/src/exempt.js +11 -3
  19. package/dist/src/findings/completion.js +19 -29
  20. package/dist/src/findings/emitters.js +22 -1
  21. package/dist/src/findings/unused-declarations.js +7 -2
  22. package/dist/src/graph.js +13 -5
  23. package/dist/src/inventory.js +4 -2
  24. package/dist/src/kinds.js +2 -2
  25. package/dist/src/manifest.js +37 -11
  26. package/dist/src/matrix.js +21 -13
  27. package/dist/src/partial.js +3 -1
  28. package/dist/src/pnpm-workspace.js +120 -0
  29. package/dist/src/position.js +17 -2
  30. package/dist/src/print.js +2 -0
  31. package/dist/src/references.js +209 -30
  32. package/dist/src/report.js +26 -6
  33. package/dist/src/reporters/annotations.js +10 -3
  34. package/dist/src/reporters/template.js +3 -0
  35. package/dist/src/resolve.js +31 -6
  36. package/dist/src/roots.js +37 -8
  37. package/dist/src/run.js +13 -3
  38. package/dist/src/schema.js +2 -0
  39. package/dist/src/session.js +141 -34
  40. package/dist/src/setup-failure.js +23 -0
  41. package/dist/src/source-files.js +4 -61
  42. package/dist/src/sweep.js +4 -2
  43. package/dist/src/template-field.js +38 -6
  44. package/dist/src/test-support.js +59 -0
  45. package/dist/src/type-errors.js +252 -0
  46. package/dist/src/type-query-alias.js +92 -0
  47. package/dist/src/verbs/analyze.js +24 -4
  48. package/dist/src/verbs/explain.js +4 -1
  49. package/dist/src/verbs/print-projects.js +17 -56
  50. package/dist/src/verbs/print-retained.js +7 -2
  51. package/dist/src/verbs/print-roots.js +7 -2
  52. package/dist/src/version-range.js +255 -0
  53. package/dist/src/version.js +1 -1
  54. package/dist/src/workspace-manifests.js +68 -0
  55. package/dist/src/workspace-programs.js +342 -0
  56. package/dist/src/workspace-resolution.js +307 -0
  57. package/dist/src/workspace-run.js +75 -0
  58. package/dist/src/workspace.js +358 -0
  59. package/jsr.json +1 -1
  60. package/package.json +2 -1
  61. package/src/analysis.ts +394 -70
  62. package/src/component-files.ts +718 -0
  63. package/src/component-layer.ts +160 -0
  64. package/src/config.ts +11 -2
  65. package/src/configuration-files.ts +235 -0
  66. package/src/conformance-record.ts +2 -2
  67. package/src/convention-rows.ts +440 -0
  68. package/src/conventions.ts +640 -0
  69. package/src/dependencies.ts +36 -2
  70. package/src/discover.ts +10 -6
  71. package/src/emit-map.ts +93 -0
  72. package/src/entry-point-gaps.ts +92 -38
  73. package/src/entry-points.ts +66 -36
  74. package/src/exempt.ts +21 -3
  75. package/src/findings/completion.ts +24 -33
  76. package/src/findings/emitters.ts +32 -1
  77. package/src/findings/unused-declarations.ts +9 -2
  78. package/src/graph.ts +15 -4
  79. package/src/host.ts +23 -0
  80. package/src/index.ts +1 -1
  81. package/src/inventory.ts +13 -2
  82. package/src/kinds.ts +2 -2
  83. package/src/manifest.ts +47 -14
  84. package/src/matrix.ts +43 -15
  85. package/src/partial.ts +6 -1
  86. package/src/pnpm-workspace.ts +135 -0
  87. package/src/position.ts +23 -2
  88. package/src/print.ts +2 -0
  89. package/src/references.ts +227 -28
  90. package/src/report.ts +56 -6
  91. package/src/reporters/annotations.ts +12 -3
  92. package/src/reporters/template.ts +3 -0
  93. package/src/resolve.ts +59 -6
  94. package/src/roots.ts +60 -9
  95. package/src/run.ts +15 -5
  96. package/src/schema.ts +2 -0
  97. package/src/session.ts +218 -37
  98. package/src/setup-failure.ts +39 -0
  99. package/src/source-files.ts +4 -66
  100. package/src/sweep.ts +9 -2
  101. package/src/template-field.ts +42 -7
  102. package/src/test-support.ts +77 -0
  103. package/src/type-errors.ts +340 -0
  104. package/src/type-query-alias.ts +133 -0
  105. package/src/verbs/analyze.ts +38 -4
  106. package/src/verbs/explain.ts +4 -1
  107. package/src/verbs/print-projects.ts +17 -69
  108. package/src/verbs/print-retained.ts +7 -2
  109. package/src/verbs/print-roots.ts +7 -2
  110. package/src/verbs/verb.ts +6 -3
  111. package/src/version-range.ts +309 -0
  112. package/src/version.ts +1 -1
  113. package/src/workspace-manifests.ts +100 -0
  114. package/src/workspace-programs.ts +486 -0
  115. package/src/workspace-resolution.ts +393 -0
  116. package/src/workspace-run.ts +148 -0
  117. package/src/workspace.ts +463 -0
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * The entry points a package manifest names, which are roots because something
3
3
  * outside the analyzed program reaches them: a consumer importing the package, or a
4
- * shell running its command.
4
+ * shell running its command or one of its scripts.
5
5
  *
6
- * Only the target's own manifest is read. A manifest below the target root belongs
7
- * to another package, whose entry points are that package's roots and not this
8
- * target's.
6
+ * One call reads one manifest, the one in the directory it is given.
9
7
  */
10
8
  import { isAbsolutePath, joinPath, resolvePath } from "./paths.js";
11
9
  /** The manifest's own name. */
@@ -26,6 +24,21 @@ function isTarget(value, requireDot) {
26
24
  }
27
25
  return !requireDot || value.startsWith("./");
28
26
  }
27
+ /** Where a script's command line splits into tokens: white space and the shell's operators. */
28
+ const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
29
+ /** A token written inside one pair of quotes. */
30
+ const QUOTED = /^(["'])(.*)\1$/su;
31
+ /**
32
+ * Every token one script's command line holds, each with one pair of enclosing quotes
33
+ * removed. A token holding a wildcard is a pattern rather than a path, and names no
34
+ * entry point.
35
+ */
36
+ export function scriptTokens(command) {
37
+ return command
38
+ .split(SCRIPT_SEPARATORS)
39
+ .map((token) => token.replace(QUOTED, "$2"))
40
+ .filter((token) => token !== "" && !token.includes("*"));
41
+ }
29
42
  /** One member's spelling below another: `exports` then `exports["."]`. */
30
43
  function below(member, key) {
31
44
  return `${member}[${JSON.stringify(key)}]`;
@@ -33,12 +46,9 @@ function below(member, key) {
33
46
  /**
34
47
  * Every target one `exports` value carries, at the member each is written under.
35
48
  *
36
- * The value is a string, an object whose keys are subpaths or conditions, or an
37
- * array of alternatives, nested to any depth, and a `null` blocks a subpath rather
38
- * than naming a target. Every level is walked because a condition names a different
39
- * file of the same package and each is an entry point of its own; a key's meaning
40
- * is not read, because a subpath and a condition both lead to a target and the
41
- * walk needs no more than that.
49
+ * The value nests strings, subpath or condition objects and alternative arrays to
50
+ * any depth; a `null` blocks a subpath. Every level is walked, because each
51
+ * condition names its own entry file, and a key's meaning is never read.
42
52
  */
43
53
  function exportTargets(value, member, found) {
44
54
  if (typeof value === "string") {
@@ -83,7 +93,7 @@ function manifestOf(host, targetRoot) {
83
93
  return isRecord(value) ? value : undefined;
84
94
  }
85
95
  /**
86
- * What the target's own manifest declares about where the package is entered.
96
+ * What the manifest in `targetRoot` declares about where its package is entered.
87
97
  *
88
98
  * A target is taken as written. It is not resolved to a source file here, because
89
99
  * which file a target names is a fact about the compiler configuration that emits
@@ -113,6 +123,22 @@ export function readManifest(host, targetRoot) {
113
123
  }
114
124
  }
115
125
  exportTargets(manifest["exports"], "exports", found);
126
+ const scripts = manifest["scripts"];
127
+ if (isRecord(scripts)) {
128
+ for (const [name, command] of Object.entries(scripts)) {
129
+ if (typeof command !== "string") {
130
+ continue;
131
+ }
132
+ for (const token of new Set(scriptTokens(command))) {
133
+ found.push({
134
+ member: below("scripts", name),
135
+ path: token,
136
+ role: "script",
137
+ published: false,
138
+ });
139
+ }
140
+ }
141
+ }
116
142
  return {
117
143
  entries: found.map((entry) => ({ ...entry, path: resolvePath(targetRoot, entry.path) })),
118
144
  declaresExports: manifest["exports"] !== undefined,
@@ -32,32 +32,31 @@ export function referenceKey(reference) {
32
32
  }
33
33
  /**
34
34
  * Indexes what the passes answered for each configuration of one run, in the run's order.
35
- *
36
- * A declaration is keyed across configurations by its identifier, which is its rendered
37
- * position and so is the same in every configuration that compiles its file; the fields
38
- * of the first configuration that holds it are the ones kept. A declaration one
39
- * configuration alone holds is in the union with that one configuration, so it is
40
- * judged where it exists rather than skipped.
41
- *
42
- * One reference written at one position of a file several configurations compile is seen
43
- * once per configuration and is one reference, so the union holds it once; two references
44
- * one configuration makes at one position are two.
35
+ * A declaration is keyed by its rendered position, with the fields of the first
36
+ * configuration that judges it; one in a file every holder holds only for its references
37
+ * is judged by none and is not in the union. One reference at one position of a file
38
+ * several configurations compile is one reference; two one configuration makes are two.
45
39
  */
46
40
  export function matrixOf(per) {
47
41
  const symbols = [];
48
42
  const holders = new Map();
49
43
  const references = [];
50
44
  const firstSeen = new Map();
45
+ const judged = new Set();
51
46
  per.forEach((one, index) => {
47
+ const referenceOnly = new Set(one.referenceOnly);
52
48
  for (const symbol of one.symbols) {
53
49
  const held = holders.get(symbol.id);
54
50
  if (held === undefined) {
55
51
  holders.set(symbol.id, [one.configuration]);
56
- symbols.push(symbol);
57
52
  }
58
53
  else if (!held.includes(one.configuration)) {
59
54
  held.push(one.configuration);
60
55
  }
56
+ if (!referenceOnly.has(symbol.position.path) && !judged.has(symbol.id)) {
57
+ judged.add(symbol.id);
58
+ symbols.push(symbol);
59
+ }
61
60
  }
62
61
  for (const reference of one.references) {
63
62
  const key = referenceKey(reference);
@@ -74,8 +73,8 @@ export function matrixOf(per) {
74
73
  symbols.sort((a, b) => byPosition(a.position, b.position) || compare(a.ref, b.ref));
75
74
  return {
76
75
  configurations: per.map((one) => one.configuration),
77
- graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles)),
78
- union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))]),
76
+ graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles)),
77
+ union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))]),
79
78
  heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
80
79
  };
81
80
  }
@@ -131,6 +130,15 @@ export function sweepMatrix(matrix, input) {
131
130
  components: componentsOf(union, dead, testOfDeadCode),
132
131
  };
133
132
  }
133
+ /**
134
+ * The file declarations at `paths`, below the target root, that one sweep holds live
135
+ * under both relations, the rule a judged declaration is live by.
136
+ */
137
+ export function liveFilesAt(matrix, result, paths) {
138
+ return matrix.union.symbols.filter((symbol) => symbol.kind === "file" &&
139
+ paths.has(symbol.position.path) &&
140
+ (result.liveUnder.get(symbol.id)?.length ?? 0) === RELATIONS.length);
141
+ }
134
142
  /** The relations in the order a declaration's entry lists them. */
135
143
  const RELATIONS = ["reference-counting", "reachability"];
136
144
  /**
@@ -1,3 +1,4 @@
1
+ import { relativePath } from "./paths.js";
1
2
  /** A count with its noun, so a line reads for one as well as for several. */
2
3
  function counted(n, one, many) {
3
4
  return `${String(n)} ${n === 1 ? one : many}`;
@@ -14,7 +15,8 @@ export function partialNotes(partial) {
14
15
  if (partial.unanswered.length > 0) {
15
16
  const byProject = new Map();
16
17
  for (const question of partial.unanswered) {
17
- byProject.set(question.configFile, (byProject.get(question.configFile) ?? 0) + 1);
18
+ const configFile = relativePath(partial.targetRoot, question.configFile) ?? question.configFile;
19
+ byProject.set(configFile, (byProject.get(configFile) ?? 0) + 1);
18
20
  }
19
21
  const where = [...byProject].map(([configFile, n]) => `${configFile}: ${String(n)}`).join(", ");
20
22
  notes.push(`the checker answered ${counted(partial.unanswered.length, "question", "questions")} with a failure (${where}); every declaration an answer could have kept live is kept live and reported by nothing`);
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The `packages` list of a `pnpm-workspace.yaml`. The document is read as far as that
3
+ * one key needs and no further: a top-level `packages` key holding a block sequence or
4
+ * a one-line flow sequence of plain or quoted strings. Any other value of the key is
5
+ * refused rather than guessed at, because a list read wrong names the wrong packages.
6
+ */
7
+ /** The characters a plain scalar may not start with. */
8
+ const INDICATORS = new Set(["[", "]", "{", "}", ",", "&", "*", "!", "|", ">", "%", "@", "`", "#"]);
9
+ /** One line with its comment removed, a `#` inside quotes kept. */
10
+ function uncommented(line) {
11
+ let quote;
12
+ for (let at = 0; at < line.length; at += 1) {
13
+ const character = line[at];
14
+ if (quote !== undefined) {
15
+ if (character === quote) {
16
+ quote = undefined;
17
+ }
18
+ continue;
19
+ }
20
+ if (character === "'" || character === '"') {
21
+ quote = character;
22
+ }
23
+ else if (character === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
24
+ return line.slice(0, at).trimEnd();
25
+ }
26
+ }
27
+ return line.trimEnd();
28
+ }
29
+ /** One scalar as written, or `undefined` where it is not a string this reader reads. */
30
+ function scalarOf(text) {
31
+ const value = text.trim();
32
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
33
+ const inner = value.slice(1, -1);
34
+ return /'(?!')/u.test(inner.replaceAll("''", "")) ? undefined : inner.replaceAll("''", "'");
35
+ }
36
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
37
+ const inner = value.slice(1, -1);
38
+ return inner.includes("\\") || inner.includes('"') ? undefined : inner;
39
+ }
40
+ if (value === "" ||
41
+ INDICATORS.has(value[0] ?? "") ||
42
+ value.startsWith("'") ||
43
+ value.startsWith('"')) {
44
+ return undefined;
45
+ }
46
+ if (value.startsWith("- ") || value.startsWith("? ") || /:(?:\s|$)/u.test(value)) {
47
+ return undefined;
48
+ }
49
+ return value;
50
+ }
51
+ /** The entries of a one-line flow sequence, or `undefined` where one does not read. */
52
+ function flowOf(text) {
53
+ const inner = text.slice(1, -1).trim();
54
+ if (inner === "") {
55
+ return [];
56
+ }
57
+ const entries = inner.split(",");
58
+ if (entries.length > 1 && entries.at(-1)?.trim() === "") {
59
+ entries.pop();
60
+ }
61
+ const found = [];
62
+ for (const entry of entries) {
63
+ const value = scalarOf(entry);
64
+ if (value === undefined) {
65
+ return undefined;
66
+ }
67
+ found.push(value);
68
+ }
69
+ return found;
70
+ }
71
+ const KEY = /^(?:packages|'packages'|"packages")\s*:(?:\s(.*))?$/u;
72
+ /** The `packages` list one `pnpm-workspace.yaml` text declares. */
73
+ export function pnpmPackages(text) {
74
+ const lines = text.split(/\r?\n/u).map(uncommented);
75
+ const keys = lines.flatMap((line, at) => (KEY.test(line) ? [at] : []));
76
+ const [at] = keys;
77
+ if (at === undefined) {
78
+ return { kind: "absent" };
79
+ }
80
+ if (keys.length > 1) {
81
+ return { kind: "refused", reason: "the packages key is declared more than once" };
82
+ }
83
+ const rest = (KEY.exec(lines[at] ?? "")?.[1] ?? "").trim();
84
+ if (rest !== "") {
85
+ const listed = rest.startsWith("[") && rest.endsWith("]") ? flowOf(rest) : undefined;
86
+ return listed === undefined
87
+ ? {
88
+ kind: "refused",
89
+ reason: `packages holds ${JSON.stringify(rest)}, not a sequence of strings`,
90
+ }
91
+ : { kind: "listed", patterns: listed };
92
+ }
93
+ const patterns = [];
94
+ for (const line of lines.slice(at + 1)) {
95
+ if (line.trim() === "") {
96
+ continue;
97
+ }
98
+ const item = /^(\s*)-(?:\s+(.*))?$/u.exec(line);
99
+ if (item === null) {
100
+ if (/^\s/u.test(line)) {
101
+ return {
102
+ kind: "refused",
103
+ reason: `packages holds ${JSON.stringify(line.trim())}, not a sequence item`,
104
+ };
105
+ }
106
+ break;
107
+ }
108
+ const value = scalarOf(item[2] ?? "");
109
+ if (value === undefined) {
110
+ return {
111
+ kind: "refused",
112
+ reason: `the packages item ${JSON.stringify(line.trim())} is not a plain or quoted string`,
113
+ };
114
+ }
115
+ patterns.push(value);
116
+ }
117
+ return patterns.length === 0
118
+ ? { kind: "refused", reason: "packages holds no sequence" }
119
+ : { kind: "listed", patterns };
120
+ }
@@ -13,18 +13,33 @@ export class PositionError extends Error {
13
13
  this.file = file;
14
14
  }
15
15
  }
16
+ /** Whether the compiler read one file through a content mapper, which makes it a component file. */
17
+ export function isComponentFile(file) {
18
+ return file.contentMapper !== undefined;
19
+ }
20
+ /**
21
+ * The length of the file as it is written. A component file's module holds the file's
22
+ * text at its own offsets and appends what it needs past its end.
23
+ */
24
+ export function writtenLength(file) {
25
+ return isComponentFile(file) ? file.originalText.length : file.text.length;
26
+ }
16
27
  /**
17
28
  * The position of one offset of one file, rendered against the target root.
18
29
  *
19
30
  * `offset` is a UTF-16 code unit offset into the file's text, which is what every
20
- * node of the tree carries.
31
+ * node of the tree carries. An offset a component file's module appends renders where
32
+ * the module's span map places it in the file.
21
33
  */
22
34
  export function renderPosition(file, root, offset) {
23
35
  const path = relativePath(root, file.fileName);
24
36
  if (path === undefined) {
25
37
  throw new PositionError(file.fileName, root);
26
38
  }
27
- const { line, character } = file.getLineAndCharacterOfPosition(offset);
39
+ const written = isComponentFile(file) && offset >= file.originalText.length
40
+ ? Math.min(file.spanMap?.virtualToOriginalPosition(offset).position ?? offset, file.originalText.length)
41
+ : offset;
42
+ const { line, character } = file.getLineAndCharacterOfPosition(written);
28
43
  return { path, line: line + 1, column: character + 1 };
29
44
  }
30
45
  /**
package/dist/src/print.js CHANGED
@@ -64,6 +64,8 @@ export function printConfig(config, provenance) {
64
64
  ts: {
65
65
  test_files: config.ts.testFiles,
66
66
  entry_files: config.ts.entryFiles,
67
+ component_extensions: config.ts.componentExtensions,
68
+ disabled_conventions: config.ts.disabledConventions,
67
69
  injection_registrations: config.ts.injectionRegistrations.map(declarationOf),
68
70
  lifecycle_contracts: config.ts.lifecycleContracts.map((entry) => ({
69
71
  components: entry.components.map(declarationOf),