@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.
Files changed (158) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +3 -2
  3. package/dist/src/analysis.js +25 -43
  4. package/dist/src/automatic-types.js +10 -29
  5. package/dist/src/build-scripts.js +17 -29
  6. package/dist/src/component-layer.js +4 -2
  7. package/dist/src/components.js +2 -3
  8. package/dist/src/config.js +2 -1
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/contract/contract.generated.js +13 -0
  11. package/dist/src/conventions.js +20 -60
  12. package/dist/src/dependencies.js +25 -43
  13. package/dist/src/discover.js +13 -39
  14. package/dist/src/edges.js +7 -15
  15. package/dist/src/element-access.js +4 -6
  16. package/dist/src/entry-point-gaps.js +2 -8
  17. package/dist/src/entry-points.js +3 -10
  18. package/dist/src/enum-group.js +3 -10
  19. package/dist/src/erased-stores.js +4 -6
  20. package/dist/src/exempt-classes.js +2 -4
  21. package/dist/src/exempt.js +6 -22
  22. package/dist/src/file-facts.js +2 -25
  23. package/dist/src/findings/reads-and-writes.js +2 -1
  24. package/dist/src/findings/self-check.js +3 -2
  25. package/dist/src/findings-pass.js +6 -11
  26. package/dist/src/fs/manifest.js +43 -0
  27. package/dist/src/fs/tree.js +88 -0
  28. package/dist/src/git-ignore.js +34 -25
  29. package/dist/src/html-entries.js +6 -33
  30. package/dist/src/in-operands.js +2 -1
  31. package/dist/src/intra-function-parts.js +10 -13
  32. package/dist/src/inventory.js +2 -21
  33. package/dist/src/json-document.js +4 -0
  34. package/dist/src/keyof-comparisons.js +4 -6
  35. package/dist/src/manifest.js +5 -43
  36. package/dist/src/mdx-entries.js +3 -26
  37. package/dist/src/partial.js +2 -1
  38. package/dist/src/paths.js +26 -15
  39. package/dist/src/position.js +2 -1
  40. package/dist/src/print.js +3 -2
  41. package/dist/src/product.js +13 -0
  42. package/dist/src/references.js +14 -27
  43. package/dist/src/report-schema.js +15 -16
  44. package/dist/src/report.js +22 -31
  45. package/dist/src/reporters/sarif.js +4 -2
  46. package/dist/src/reporters/template.js +4 -19
  47. package/dist/src/resolve.js +11 -20
  48. package/dist/src/roots.js +7 -17
  49. package/dist/src/run.js +8 -7
  50. package/dist/src/schema.js +4 -3
  51. package/dist/src/scope.js +4 -5
  52. package/dist/src/serialization-contract.js +4 -8
  53. package/dist/src/session.js +2 -1
  54. package/dist/src/suppress-file.js +8 -10
  55. package/dist/src/symbol-keys.js +3 -5
  56. package/dist/src/template-field.js +5 -15
  57. package/dist/src/test-globals.js +6 -28
  58. package/dist/src/test-joins.js +6 -6
  59. package/dist/src/text/compare.js +26 -0
  60. package/dist/src/text/glob.js +194 -0
  61. package/dist/src/text/shell.js +159 -0
  62. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  63. package/dist/src/type-errors.js +14 -38
  64. package/dist/src/unseen-loads.js +2 -9
  65. package/dist/src/unwrap.js +23 -0
  66. package/dist/src/verbs/analyze.js +15 -13
  67. package/dist/src/verbs/describe.js +4 -4
  68. package/dist/src/verbs/explain.js +5 -3
  69. package/dist/src/verbs/print-projects.js +2 -1
  70. package/dist/src/verbs/print-retained.js +2 -1
  71. package/dist/src/verbs/print-roots.js +2 -1
  72. package/dist/src/verbs/version.js +2 -1
  73. package/dist/src/workflow-steps.js +9 -46
  74. package/dist/src/workspace-manifests.js +2 -2
  75. package/dist/src/workspace-programs.js +5 -3
  76. package/dist/src/workspace-resolution.js +13 -17
  77. package/dist/src/workspace.js +28 -116
  78. package/jsr.json +1 -1
  79. package/package.json +1 -1
  80. package/src/analysis.ts +26 -51
  81. package/src/automatic-types.ts +11 -29
  82. package/src/build-scripts.ts +17 -38
  83. package/src/component-layer.ts +4 -2
  84. package/src/components.ts +2 -4
  85. package/src/config.ts +4 -2
  86. package/src/configuration-files.ts +27 -170
  87. package/src/contract/contract.generated.ts +25 -0
  88. package/src/conventions.ts +26 -74
  89. package/src/dependencies.ts +32 -50
  90. package/src/discover.ts +14 -39
  91. package/src/edges.ts +7 -21
  92. package/src/element-access.ts +4 -7
  93. package/src/entry-point-gaps.ts +3 -9
  94. package/src/entry-points.ts +2 -13
  95. package/src/enum-group.ts +2 -11
  96. package/src/erased-stores.ts +3 -7
  97. package/src/exempt-classes.ts +4 -4
  98. package/src/exempt.ts +5 -27
  99. package/src/file-facts.ts +6 -24
  100. package/src/findings/reads-and-writes.ts +2 -1
  101. package/src/findings/self-check.ts +3 -2
  102. package/src/findings-pass.ts +6 -12
  103. package/src/fs/manifest.ts +52 -0
  104. package/src/fs/tree.ts +110 -0
  105. package/src/git-ignore.ts +38 -25
  106. package/src/html-entries.ts +6 -33
  107. package/src/in-operands.ts +2 -1
  108. package/src/intra-function-parts.ts +10 -14
  109. package/src/inventory.ts +2 -20
  110. package/src/json-document.ts +5 -0
  111. package/src/keyof-comparisons.ts +3 -7
  112. package/src/manifest.ts +5 -46
  113. package/src/mdx-entries.ts +3 -26
  114. package/src/partial.ts +2 -1
  115. package/src/paths.ts +27 -15
  116. package/src/position.ts +2 -1
  117. package/src/print.ts +3 -2
  118. package/src/product.ts +18 -0
  119. package/src/references.ts +14 -29
  120. package/src/report-schema.ts +15 -17
  121. package/src/report.ts +24 -34
  122. package/src/reporters/sarif.ts +4 -4
  123. package/src/reporters/template.ts +4 -20
  124. package/src/resolve.ts +20 -27
  125. package/src/roots.ts +7 -20
  126. package/src/run.ts +8 -13
  127. package/src/schema.ts +4 -3
  128. package/src/scope.ts +4 -10
  129. package/src/serialization-contract.ts +4 -9
  130. package/src/session.ts +2 -1
  131. package/src/suppress-file.ts +8 -11
  132. package/src/symbol-keys.ts +2 -5
  133. package/src/template-field.ts +5 -18
  134. package/src/test-globals.ts +9 -27
  135. package/src/test-joins.ts +6 -8
  136. package/src/text/compare.ts +27 -0
  137. package/src/text/glob.ts +210 -0
  138. package/src/text/shell.ts +161 -0
  139. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  140. package/src/type-errors.ts +25 -36
  141. package/src/unseen-loads.ts +1 -12
  142. package/src/unwrap.ts +34 -0
  143. package/src/verbs/analyze.ts +22 -21
  144. package/src/verbs/describe.ts +4 -4
  145. package/src/verbs/explain.ts +5 -3
  146. package/src/verbs/print-projects.ts +2 -1
  147. package/src/verbs/print-retained.ts +2 -1
  148. package/src/verbs/print-roots.ts +2 -1
  149. package/src/verbs/version.ts +2 -1
  150. package/src/workflow-steps.ts +9 -44
  151. package/src/workspace-manifests.ts +2 -2
  152. package/src/workspace-programs.ts +5 -3
  153. package/src/workspace-resolution.ts +21 -17
  154. package/src/workspace.ts +35 -121
  155. package/dist/src/glob.js +0 -65
  156. package/dist/src/pnpm-workspace.js +0 -120
  157. package/src/glob.ts +0 -67
  158. package/src/pnpm-workspace.ts +0 -135
@@ -3,10 +3,15 @@ import { baseUrlPaths } from "./base-url.js";
3
3
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
4
4
  import { SetupError } from "./setup-failure.js";
5
5
  import { writtenPath } from "./workspace-programs.js";
6
- /** Directory names discovery does not descend into. */
7
- export const IGNORED_DIRECTORIES = new Set(["node_modules", ".git"]);
8
- /** A compiler configuration file discovery recognises under a root. */
9
- const CONFIG_FILE = /^tsconfig.*\.json$/u;
6
+ import { filesBelow } from "./fs/tree.js";
7
+ import { LANGUAGE, message } from "./product.js";
8
+ import { LANGUAGE_ROWS } from "./contract/contract.generated.js";
9
+ import { globExpression } from "./text/glob.js";
10
+ const MARKERS = LANGUAGE_ROWS[LANGUAGE].markers.map(globExpression);
11
+ /** Whether one file name is a compiler configuration's: one of the language row's markers. */
12
+ export function isConfigFile(name) {
13
+ return MARKERS.some((marker) => marker.test(name));
14
+ }
10
15
  /**
11
16
  * A run that cannot read what it was asked to analyze. It ends the run with the
12
17
  * failure code and no finding list: a configuration file the compiler cannot read,
@@ -111,40 +116,9 @@ export function targetRelative(text, targetRoot) {
111
116
  export function isGuess(discovery, configFile) {
112
117
  return discovery.derived && !discovery.fromScope.includes(configFile);
113
118
  }
114
- /**
115
- * The configuration files below `root`, in ascending path order, skipping an
116
- * ignored directory. A directory that cannot be read is skipped: a tree the
117
- * analysis may not enter names no project.
118
- */
119
+ /** The configuration files below `root`, by absolute path in ascending order. */
119
120
  function configFilesUnder(host, root) {
120
- const found = [];
121
- const pending = [root];
122
- while (pending.length > 0) {
123
- const dir = pending.pop();
124
- if (dir === undefined) {
125
- break;
126
- }
127
- let entries;
128
- try {
129
- entries = host.readDirectory(dir);
130
- }
131
- catch {
132
- continue;
133
- }
134
- for (const entry of [...entries].sort((a, b) => (a.name < b.name ? -1 : 1))) {
135
- const path = joinPath(dir, entry.name);
136
- if (entry.directory) {
137
- if (!IGNORED_DIRECTORIES.has(entry.name)) {
138
- pending.push(path);
139
- }
140
- continue;
141
- }
142
- if (CONFIG_FILE.test(entry.name)) {
143
- found.push(path);
144
- }
145
- }
146
- }
147
- return found.sort();
121
+ return filesBelow(host, root, isConfigFile).map((path) => joinPath(root, path));
148
122
  }
149
123
  /**
150
124
  * The configuration file the scope names for the target directly, where its path
@@ -211,7 +185,7 @@ export function consumerProjects(engine, host, root) {
211
185
  if (kind !== "directory") {
212
186
  throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
213
187
  }
214
- const found = derivedFrom(engine, host, configFilesUnder(host, root), () => true);
188
+ const found = derivedFrom(engine, host, [...configFilesUnder(host, root)], () => true);
215
189
  if (found.length === 0) {
216
190
  throw new DiscoveryError(`the consumer ${root} holds no compiler configuration, so none of its references can be read`, []);
217
191
  }
@@ -253,7 +227,7 @@ function readConfiguration(engine, configFile) {
253
227
  : { reason: `TS${String(circular.code)}: ${circular.text.replace("{0}", () => configFile)}` };
254
228
  }
255
229
  catch (error) {
256
- return { reason: error instanceof Error ? error.message : String(error) };
230
+ return { reason: message(error) };
257
231
  }
258
232
  }
259
233
  /**
package/dist/src/edges.js CHANGED
@@ -5,10 +5,12 @@
5
5
  * pairing is a use, and the merge reads the two evaluations together.
6
6
  */
7
7
  import { ConfigError } from "./config.js";
8
- import { positionAt, walkDocument } from "./json-document.js";
8
+ import { isRecord, positionAt, walkDocument } from "./json-document.js";
9
9
  import { joinPath } from "./paths.js";
10
10
  import { positionKey } from "./position.js";
11
11
  import { isPartSubject } from "./suppress.js";
12
+ import { bytewise } from "./text/compare.js";
13
+ import { message } from "./product.js";
12
14
  /** The name and the location the Contract fixes for the edges document. */
13
15
  export const EDGES_FILE = "deadset-edges.json";
14
16
  /** The language prefix of every reference this analyzer evaluates. */
@@ -64,15 +66,12 @@ function carriesWildcard(ref) {
64
66
  }
65
67
  return false;
66
68
  }
67
- function isObject(value) {
68
- return typeof value === "object" && value !== null && !Array.isArray(value);
69
- }
70
69
  /** One edge, checked: the closed key list, the value types and the value forms. */
71
70
  function readEdge(value, at) {
72
71
  const refuse = (text, want) => {
73
72
  throw new EdgesError(at, text, want);
74
73
  };
75
- if (!isObject(value)) {
74
+ if (!isRecord(value)) {
76
75
  return refuse(JSON.stringify(value), EDGE_WANT);
77
76
  }
78
77
  for (const [key, held] of Object.entries(value)) {
@@ -111,9 +110,9 @@ export function readEdgeSides(host, targetRoot) {
111
110
  parsed = JSON.parse(text);
112
111
  }
113
112
  catch (error) {
114
- throw new EdgesError(EDGES_FILE, error instanceof Error ? error.message : String(error), DOCUMENT_WANT);
113
+ throw new EdgesError(EDGES_FILE, message(error), DOCUMENT_WANT);
115
114
  }
116
- if (!isObject(parsed)) {
115
+ if (!isRecord(parsed)) {
117
116
  throw new EdgesError(EDGES_FILE, JSON.stringify(parsed), DOCUMENT_WANT);
118
117
  }
119
118
  const walk = walkDocument(text, "edges");
@@ -145,13 +144,6 @@ export function readEdgeSides(host, targetRoot) {
145
144
  });
146
145
  return sides;
147
146
  }
148
- /** Two strings ordered bytewise. */
149
- function compare(a, b) {
150
- if (a === b) {
151
- return 0;
152
- }
153
- return a < b ? -1 : 1;
154
- }
155
147
  /**
156
148
  * Evaluates every declared side of this language over the run's findings. A side naming a
157
149
  * symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
@@ -191,6 +183,6 @@ export function evaluateEdges(findings, sides, symbols) {
191
183
  return { ...side, state: "dead", finding };
192
184
  });
193
185
  });
194
- evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
186
+ evaluations.sort((a, b) => bytewise(a.edge, b.edge) || bytewise(a.side, b.side));
195
187
  return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
196
188
  }
@@ -8,6 +8,7 @@ import { isArrayLiteralExpression, isArrowFunction, isBinaryExpression, isCallEx
8
8
  import { nodeKey } from "./inventory.js";
9
9
  import { renderPosition } from "./position.js";
10
10
  import { UNANSWERED } from "./query.js";
11
+ import { parenthesesRemoved } from "./unwrap.js";
11
12
  /** The assignment operators that read their target before they write it. */
12
13
  const COMPOUND = new Set([
13
14
  SyntaxKind.PlusEqualsToken,
@@ -47,12 +48,9 @@ function useOf(node) {
47
48
  }
48
49
  return "read";
49
50
  }
50
- function unwrapped(node) {
51
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
52
- }
53
51
  /** Whether an index is a literal the checker resolves to a member by its own text. */
54
52
  function isLiteralIndex(node) {
55
- const at = unwrapped(node);
53
+ const at = parenthesesRemoved(node);
56
54
  return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at);
57
55
  }
58
56
  /**
@@ -195,7 +193,7 @@ export function elementAccessReferences(project, held, chains, root, testFiles)
195
193
  const parameterOf = new Map();
196
194
  const byCallable = new Map();
197
195
  for (const candidate of candidates) {
198
- const index = unwrapped(candidate.access.argumentExpression);
196
+ const index = parenthesesRemoved(candidate.access.argumentExpression);
199
197
  const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
200
198
  const callable = fn === undefined ? undefined : callableOf(fn);
201
199
  if (callable === undefined || !isIdentifier(index)) {
@@ -252,7 +250,7 @@ export function elementAccessReferences(project, held, chains, root, testFiles)
252
250
  continue;
253
251
  }
254
252
  // An object that is a parameter too is read as the argument each call passes there.
255
- const object = unwrapped(candidate.access.expression);
253
+ const object = parenthesesRemoved(candidate.access.expression);
256
254
  const objectAt = isIdentifier(object)
257
255
  ? deferred.callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text)
258
256
  : -1;
@@ -7,6 +7,7 @@
7
7
  * Every row carries a reason, because the list is read where the rows are shown
8
8
  * one at a time: beside a finding, or in a report's declared gaps.
9
9
  */
10
+ import { bytewise } from "./text/compare.js";
10
11
  /** What is declined for a tool whose own conventions are entered only through its configuration files. */
11
12
  const EVERY_CONVENTION = "every file it loads by its own convention beyond its configuration files";
12
13
  /** Why a tool whose own conventions are not read declines them. */
@@ -291,15 +292,8 @@ const PARTLY_ENTERED = [
291
292
  reason: NOT_LITERAL,
292
293
  },
293
294
  ];
294
- /** Two strings ordered bytewise. */
295
- function compare(a, b) {
296
- if (a === b) {
297
- return 0;
298
- }
299
- return a < b ? -1 : 1;
300
- }
301
295
  /** Every declined entry-point convention, ordered by tool and then by convention. */
302
296
  export const DECLINED_CONVENTIONS = [
303
297
  ...UNREAD_TOOLS.map((tool) => ({ tool, convention: EVERY_CONVENTION, reason: UNREAD })),
304
298
  ...PARTLY_ENTERED,
305
- ].sort((a, b) => compare(a.tool, b.tool) || compare(a.convention, b.convention));
299
+ ].sort((a, b) => bytewise(a.tool, b.tool) || bytewise(a.convention, b.convention));
@@ -10,17 +10,18 @@
10
10
  * value that is not a literal is not evaluated and not guessed, so a file it would
11
11
  * have named is entered by nothing here.
12
12
  */
13
- import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
13
+ import { isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
14
14
  import { SymbolFlags } from "@typescript/native/sync";
15
15
  import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
16
16
  import { buildScriptEntries } from "./build-scripts.js";
17
- import { globExpression } from "./glob.js";
18
17
  import { htmlEntries } from "./html-entries.js";
19
18
  import { documentEntries } from "./mdx-entries.js";
20
19
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
21
20
  import { UNANSWERED } from "./query.js";
22
21
  import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.js";
23
22
  import { workflowEntries } from "./workflow-steps.js";
23
+ import { unwrapped } from "./unwrap.js";
24
+ import { globExpression } from "./text/glob.js";
24
25
  /** The extensions the test runner and the browser-test runner load a configuration from. */
25
26
  const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
26
27
  /** The tool file names the configuration form does not cover. */
@@ -69,14 +70,6 @@ function literalOf(node) {
69
70
  }
70
71
  return undefined;
71
72
  }
72
- /** The expression a wrapper that changes no value holds: parentheses and type assertions. */
73
- function unwrapped(node) {
74
- let at = node;
75
- while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
76
- at = at.expression;
77
- }
78
- return at;
79
- }
80
73
  /**
81
74
  * The object literals one configuration file's default export is written as: the
82
75
  * literal itself, or each literal argument of the call it is, at any depth of calls.
@@ -6,7 +6,8 @@
6
6
  * or `unknown` expression asserted to it, or bound to a variable or returned from a
7
7
  * function it annotates, where a number counts too. An enum's own value converts nothing.
8
8
  */
9
- import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedExpression, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
9
+ import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
+ import { parenthesesRemoved } from "./unwrap.js";
10
11
  import { TypeFlags } from "@typescript/native/sync";
11
12
  import { aliasChains } from "./alias-chain.js";
12
13
  import { nodeKey } from "./inventory.js";
@@ -16,14 +17,6 @@ import { isAnswered, UNANSWERED } from "./query.js";
16
17
  const NUMBER = TypeFlags.NumberLike;
17
18
  const STRING = TypeFlags.StringLike;
18
19
  const DECODED = TypeFlags.Any | TypeFlags.Unknown;
19
- /** The expression one node is, through however many parentheses are written around it. */
20
- function unparenthesized(node) {
21
- let held = node;
22
- while (isParenthesizedExpression(held)) {
23
- held = held.expression;
24
- }
25
- return held;
26
- }
27
20
  /**
28
21
  * The name node a type node names its declaration by: the identifier of a type
29
22
  * reference, the right side of a qualified one, through parentheses. A type that is no
@@ -45,7 +38,7 @@ function typeName(type) {
45
38
  }
46
39
  /** The name node an expression names an object by: an identifier, or a property access's name. */
47
40
  function objectName(expression) {
48
- const held = unparenthesized(expression);
41
+ const held = parenthesesRemoved(expression);
49
42
  if (isIdentifier(held)) {
50
43
  return held;
51
44
  }
@@ -6,10 +6,11 @@
6
6
  * either stores. A store into an erased property of a type the analyzed program does not
7
7
  * declare hands the value to the code that declares it.
8
8
  */
9
- import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
9
+ import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { SymbolFlags, TypeFlags } from "@typescript/native/sync";
11
11
  import { must } from "./query.js";
12
12
  import { checkedTypes } from "./value-reach.js";
13
+ import { parenthesesRemoved } from "./unwrap.js";
13
14
  /** The types that keep nothing of the value they are given. */
14
15
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
15
16
  /** The array methods whose arguments become elements of the array they are called on. */
@@ -20,9 +21,6 @@ function literalKey(node) {
20
21
  ? node.text
21
22
  : undefined;
22
23
  }
23
- function unwrapped(node) {
24
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
25
- }
26
24
  /**
27
25
  * Whether a variable declaration's type can differ from what is stored in it: it carries an
28
26
  * annotation, or it has neither an annotation nor an initializer, so its type evolves.
@@ -76,8 +74,8 @@ function propertyAt(source, name) {
76
74
  * here, and a default value is stored in its target too.
77
75
  */
78
76
  function patternTargets(pattern, from, out) {
79
- const target = unwrapped(pattern);
80
- const source = from.path.length === 0 ? unwrapped(from.value) : from.value;
77
+ const target = parenthesesRemoved(pattern);
78
+ const source = from.path.length === 0 ? parenthesesRemoved(from.value) : from.value;
81
79
  const into = (element, step, literal) => {
82
80
  patternTargets(element, literal === undefined
83
81
  ? { value: from.value, path: [...from.path, step] }
@@ -11,7 +11,5 @@ const VISIBILITY = new Map(TS_ROWS.map((row) => [row.class, row.typescriptVisibi
11
11
  export function typescriptVisibilityOf(exemptionClass) {
12
12
  return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
13
13
  }
14
- /** Whether one name is a class of the vocabulary, whichever language it runs on. */
15
- export function isExemptionClass(name) {
16
- return EXEMPTION_CLASSES.some((row) => row.class === name);
17
- }
14
+ /** Every class of the vocabulary, whichever language it runs on, in the vocabulary's order. */
15
+ export const EXEMPTION_CLASS_NAMES = EXEMPTION_CLASSES.map((row) => row.class);
@@ -7,26 +7,17 @@
7
7
  * hands it the table of the classes it has.
8
8
  */
9
9
  import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
10
- import { ConfigError } from "./config.js";
11
- import { isExemptionClass, TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
10
+ import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
12
11
  import { byPosition, positionKey } from "./position.js";
13
12
  import { Unanswerable } from "./query.js";
14
13
  import { sweep } from "./sweep.js";
14
+ import { bytewise } from "./text/compare.js";
15
15
  /**
16
- * The classes `exemptions.disabled` switches off. A name is refused unless the
17
- * vocabulary holds it; a class that runs on another language only is admitted and
18
- * switches nothing off here, so one configuration serves every analyzer.
16
+ * The classes `exemptions.disabled` switches off. A class that runs on another language
17
+ * only switches nothing off here, so one configuration serves every analyzer.
19
18
  */
20
19
  export function disabledClasses(config) {
21
- const disabled = new Set();
22
- for (const name of config.exemptionsDisabled) {
23
- if (!isExemptionClass(name)) {
24
- const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
25
- throw new ConfigError("malformed", "exemptions.disabled", `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`);
26
- }
27
- disabled.add(name);
28
- }
29
- return disabled;
20
+ return new Set(config.exemptionsDisabled);
30
21
  }
31
22
  /**
32
23
  * Whether a class whose row states `allowed` may retain a member of one visibility. A
@@ -122,20 +113,13 @@ export function heldRecords(records, holding) {
122
113
  }
123
114
  return { records: exemptionsOf(found), inTestFiles };
124
115
  }
125
- /** Two strings ordered bytewise. */
126
- function compare(a, b) {
127
- if (a === b) {
128
- return 0;
129
- }
130
- return a < b ? -1 : 1;
131
- }
132
116
  /**
133
117
  * Records as a run holds them: ordered by site, then class, then declaration, and one
134
118
  * per declaration, class, detail and file it holds while live, the one at the first
135
119
  * site. A fact found at many sites, or by several projects, is one record.
136
120
  */
137
121
  export function exemptionsOf(found) {
138
- const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || compare(a.class, b.class) || compare(a.id, b.id));
122
+ const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
139
123
  const seen = new Set();
140
124
  return ordered.filter((record) => {
141
125
  const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
@@ -3,10 +3,10 @@
3
3
  * file below the target root, whether or not a project includes it, and the files a
4
4
  * program holds by inclusion rather than by an import.
5
5
  */
6
- import { IGNORED_DIRECTORIES } from "./discover.js";
7
6
  import { packageScope } from "./inventory.js";
8
7
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
9
8
  import { renderRef } from "./ref.js";
9
+ import { filesBelow } from "./fs/tree.js";
10
10
  /** A file name the compiler reads as a source file. */
11
11
  const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
12
12
  /** A file name the compiler reads as a declaration file, which no build emits code from. */
@@ -30,30 +30,7 @@ function lastLine(text) {
30
30
  * that cannot be read is skipped, as discovery skips it.
31
31
  */
32
32
  export function sourcePaths(host, targetRoot) {
33
- const found = [];
34
- const pending = [""];
35
- for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
36
- let entries;
37
- try {
38
- entries = host.readDirectory(joinPath(targetRoot, dir));
39
- }
40
- catch {
41
- continue;
42
- }
43
- for (const entry of entries) {
44
- const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
45
- if (entry.directory) {
46
- if (!IGNORED_DIRECTORIES.has(entry.name)) {
47
- pending.push(path);
48
- }
49
- continue;
50
- }
51
- if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
52
- found.push(path);
53
- }
54
- }
55
- }
56
- return found.sort((a, b) => (a < b ? -1 : 1));
33
+ return filesBelow(host, targetRoot, (name) => SOURCE_FILE.test(name) && !DECLARATION_FILE.test(name));
57
34
  }
58
35
  /**
59
36
  * Every source file below the target root, in path order. A file that cannot be read is
@@ -1,6 +1,7 @@
1
1
  import { CODE } from "../contract/contract.generated.js";
2
2
  import { positionKey } from "../position.js";
3
3
  import { declarationKey } from "../ref.js";
4
+ import { bytewise } from "../text/compare.js";
4
5
  /** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
5
6
  const SIGNATURES = new Set(["function", "method", "type"]);
6
7
  function indexed(input) {
@@ -76,7 +77,7 @@ function writeOnlyMessage(run, namedBy, symbol, held) {
76
77
  : "never read";
77
78
  const cascade = [
78
79
  ...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
79
- ...[...held.alone].sort().map((name) => `import ${name}`),
80
+ ...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
80
81
  ];
81
82
  const word = SUBJECT_WORDS[symbol.kind] ?? "member";
82
83
  const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
@@ -2,6 +2,7 @@ import { CODE } from "../contract/contract.generated.js";
2
2
  import { REPOSITORY_DOCUMENT } from "../config.js";
3
3
  import { FIXED_SEVERITY_CODES } from "../kinds.js";
4
4
  import { isDependencyCode, isUnsuppressibleCode, } from "../suppress.js";
5
+ import { bytewise } from "../text/compare.js";
5
6
  /** The subject kind of a finding about a configured declaration. */
6
7
  const DECLARATION_SUBJECT = "configured-declaration";
7
8
  /** The subject kind of a finding about a suppression record. */
@@ -80,10 +81,10 @@ export function staleSuppressions(stale, files) {
80
81
  bySite.set(key, held);
81
82
  }
82
83
  return [...bySite.values()].map(({ first, records }) => {
83
- const codes = [...new Set(records.map((one) => one.record.code))].sort();
84
+ const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
84
85
  const reports = [...new Set(records.flatMap((one) => one.reports))]
85
86
  .filter((code) => !codes.includes(code))
86
- .sort();
87
+ .sort(bytewise);
87
88
  const rows = codes.filter(isUnsuppressibleCode);
88
89
  const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
89
90
  const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
@@ -8,20 +8,15 @@ import { fails } from "./config.js";
8
8
  import { decidedFindings } from "./findings/emitters.js";
9
9
  import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
10
10
  import { isDependencySubject, isRowSubject, pairs, suppressionCounts, } from "./suppress.js";
11
- /** Two strings ordered bytewise. */
12
- function compare(a, b) {
13
- if (a === b) {
14
- return 0;
15
- }
16
- return a < b ? -1 : 1;
17
- }
11
+ import { bytewise } from "./text/compare.js";
12
+ import { PRODUCT } from "./product.js";
18
13
  /** The canonical order: path, line, column, code, then the symbol reference. */
19
14
  function canonical(a, b) {
20
- return (compare(a.position.path, b.position.path) ||
15
+ return (bytewise(a.position.path, b.position.path) ||
21
16
  a.position.line - b.position.line ||
22
17
  a.position.column - b.position.column ||
23
- compare(a.code, b.code) ||
24
- compare(a.symbol.ref, b.symbol.ref));
18
+ bytewise(a.code, b.code) ||
19
+ bytewise(a.symbol.ref, b.symbol.ref));
25
20
  }
26
21
  /**
27
22
  * The pass over one run. The emitter table decides every record against every finding the
@@ -98,5 +93,5 @@ export function rowKey(row) {
98
93
  }
99
94
  /** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
100
95
  export function analyzerProvenance(version) {
101
- return { analyzer: "deadset-ts", version };
96
+ return { analyzer: PRODUCT, version };
102
97
  }
@@ -0,0 +1,43 @@
1
+ import { isRecord } from "../json-document.js";
2
+ import { joinPath } from "../paths.js";
3
+ /** The file a package's manifest is read from. */
4
+ export const MANIFEST = "package.json";
5
+ /** The directory a package manager installs packages into, whose files are no project's own. */
6
+ export const INSTALLED_PACKAGES = "node_modules";
7
+ /** Whether one path passes through an installed-packages directory. */
8
+ export function inInstalledPackages(path) {
9
+ return path.split("/").includes(INSTALLED_PACKAGES);
10
+ }
11
+ /** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
12
+ export function readDocument(host, path) {
13
+ let text;
14
+ try {
15
+ text = host.readFile(path);
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ try {
21
+ return { text, value: JSON.parse(text) };
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
28
+ export function readJSON(host, path) {
29
+ return readDocument(host, path)?.value;
30
+ }
31
+ /**
32
+ * The manifest in one directory, decoded, or undefined where none reads as an object. A
33
+ * manifest this program cannot read declares nothing, the same answer as an absent one:
34
+ * the package manager owns the refusal of a manifest it rejects.
35
+ */
36
+ export function manifestAt(host, dir) {
37
+ const value = readJSON(host, joinPath(dir, MANIFEST));
38
+ return isRecord(value) ? value : undefined;
39
+ }
40
+ /** Whether one directory holds a manifest file. */
41
+ export function holdsManifest(host, dir) {
42
+ return host.kindOf(joinPath(dir, MANIFEST)) === "file";
43
+ }
@@ -0,0 +1,88 @@
1
+ import { ancestors, joinPath, resolvePath } from "../paths.js";
2
+ import { LANGUAGE_ROWS } from "../contract/contract.generated.js";
3
+ import { LANGUAGE } from "../product.js";
4
+ import { bytewise } from "../text/compare.js";
5
+ import { globExpression } from "../text/glob.js";
6
+ /** The directories no walk of the tree enters, by the patterns the language's row names. */
7
+ const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
8
+ function skipped(name) {
9
+ return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
10
+ }
11
+ /** One file's text without its line ending, or undefined where it cannot be read. */
12
+ function lineOf(host, path) {
13
+ try {
14
+ return host.readFile(path).replace(/\r?\n$/u, "");
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ /**
21
+ * The repository whose working tree holds `dir`: the nearest directory at or above it with a
22
+ * `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
23
+ * directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
24
+ * the directory shared with the main worktree, which holds `info/exclude`:
25
+ * https://git-scm.com/docs/gitrepository-layout
26
+ */
27
+ export function repositoryOf(host, dir) {
28
+ for (const root of ancestors(dir)) {
29
+ const dotGit = joinPath(root, ".git");
30
+ const kind = host.kindOf(dotGit);
31
+ if (kind === "directory") {
32
+ return { root, exclude: joinPath(dotGit, "info/exclude") };
33
+ }
34
+ if (kind === "file") {
35
+ const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
36
+ const gitDir = named === undefined ? undefined : resolvePath(root, named);
37
+ const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
38
+ const shared = gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
39
+ return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ /** The entries of one directory, none where it cannot be read. */
45
+ export function entriesOf(host, dir) {
46
+ try {
47
+ return host.readDirectory(dir);
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
53
+ /** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
54
+ export function filesIn(host, dir, accepts) {
55
+ return entriesOf(host, dir)
56
+ .filter((entry) => !entry.directory && accepts(entry.name))
57
+ .map((entry) => entry.name)
58
+ .sort(bytewise);
59
+ }
60
+ /** The names of the directories directly in `dir` a walk enters, in bytewise order. */
61
+ export function directoriesIn(host, dir) {
62
+ return entriesOf(host, dir)
63
+ .filter((entry) => entry.directory && !skipped(entry.name))
64
+ .map((entry) => entry.name)
65
+ .sort(bytewise);
66
+ }
67
+ /**
68
+ * Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
69
+ * order, read in no skipped directory.
70
+ */
71
+ export function filesBelow(host, root, accepts) {
72
+ const found = [];
73
+ const pending = [""];
74
+ for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
75
+ for (const entry of entriesOf(host, joinPath(root, dir))) {
76
+ const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
77
+ if (!entry.directory) {
78
+ if (accepts(entry.name)) {
79
+ found.push(path);
80
+ }
81
+ }
82
+ else if (!skipped(entry.name)) {
83
+ pending.push(path);
84
+ }
85
+ }
86
+ }
87
+ return found.sort(bytewise);
88
+ }