@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8

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 (193) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +24 -5
  3. package/dist/src/analysis.js +37 -56
  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 +6 -82
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/configured-declarations.js +15 -20
  11. package/dist/src/consumers.js +2 -2
  12. package/dist/src/contract/config.generated.js +544 -0
  13. package/dist/src/contract/contract.generated.js +13 -0
  14. package/dist/src/contract/report.generated.js +3 -2
  15. package/dist/src/conventions.js +20 -60
  16. package/dist/src/dependencies.js +25 -43
  17. package/dist/src/discover.js +14 -40
  18. package/dist/src/edges.js +38 -122
  19. package/dist/src/element-access.js +4 -6
  20. package/dist/src/entry-point-gaps.js +2 -8
  21. package/dist/src/entry-points.js +3 -10
  22. package/dist/src/enum-group.js +3 -10
  23. package/dist/src/erased-stores.js +4 -6
  24. package/dist/src/exempt-classes.js +0 -4
  25. package/dist/src/exempt.js +6 -22
  26. package/dist/src/file-facts.js +2 -25
  27. package/dist/src/findings/completion.js +5 -4
  28. package/dist/src/findings/emitters.js +1 -1
  29. package/dist/src/findings/intra-function.js +1 -1
  30. package/dist/src/findings/non-code-artifacts.js +2 -2
  31. package/dist/src/findings/reads-and-writes.js +2 -1
  32. package/dist/src/findings/self-check.js +3 -2
  33. package/dist/src/findings/unused-declarations.js +1 -1
  34. package/dist/src/findings/visibility-narrowing.js +2 -2
  35. package/dist/src/findings-pass.js +7 -12
  36. package/dist/src/framework-lifecycle.js +7 -7
  37. package/dist/src/fs/document.js +59 -0
  38. package/dist/src/fs/manifest.js +43 -0
  39. package/dist/src/fs/tree.js +88 -0
  40. package/dist/src/git-ignore.js +34 -25
  41. package/dist/src/html-entries.js +6 -33
  42. package/dist/src/in-operands.js +2 -1
  43. package/dist/src/injection-container.js +1 -1
  44. package/dist/src/intra-function-parts.js +10 -13
  45. package/dist/src/inventory.js +2 -21
  46. package/dist/src/json-document.js +198 -67
  47. package/dist/src/keyof-comparisons.js +4 -6
  48. package/dist/src/kinds.js +0 -7
  49. package/dist/src/manifest.js +5 -43
  50. package/dist/src/mdx-entries.js +3 -26
  51. package/dist/src/partial.js +2 -1
  52. package/dist/src/paths.js +26 -15
  53. package/dist/src/position.js +2 -1
  54. package/dist/src/print.js +11 -79
  55. package/dist/src/product.js +13 -0
  56. package/dist/src/references.js +14 -27
  57. package/dist/src/report-schema.js +3 -225
  58. package/dist/src/report.js +22 -31
  59. package/dist/src/reporters/sarif.js +4 -2
  60. package/dist/src/reporters/template.js +4 -19
  61. package/dist/src/resolve.js +245 -724
  62. package/dist/src/roots.js +7 -17
  63. package/dist/src/run.js +55 -61
  64. package/dist/src/schema.js +320 -343
  65. package/dist/src/scope.js +34 -103
  66. package/dist/src/serialization-contract.js +5 -9
  67. package/dist/src/session.js +2 -1
  68. package/dist/src/suppress-file.js +21 -25
  69. package/dist/src/suppress.js +3 -3
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +9 -19
  72. package/dist/src/test-globals.js +6 -28
  73. package/dist/src/test-joins.js +6 -6
  74. package/dist/src/text/compare.js +26 -0
  75. package/dist/src/text/glob.js +194 -0
  76. package/dist/src/text/shell.js +159 -0
  77. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  78. package/dist/src/type-errors.js +14 -38
  79. package/dist/src/unseen-loads.js +2 -9
  80. package/dist/src/unwrap.js +23 -0
  81. package/dist/src/usage.js +10 -0
  82. package/dist/src/verbs/analyze.js +25 -26
  83. package/dist/src/verbs/describe.js +4 -4
  84. package/dist/src/verbs/explain.js +8 -6
  85. package/dist/src/verbs/print-projects.js +4 -3
  86. package/dist/src/verbs/print-retained.js +2 -1
  87. package/dist/src/verbs/print-roots.js +2 -1
  88. package/dist/src/verbs/version.js +2 -1
  89. package/dist/src/workflow-steps.js +9 -46
  90. package/dist/src/workspace-manifests.js +2 -2
  91. package/dist/src/workspace-programs.js +5 -3
  92. package/dist/src/workspace-resolution.js +13 -17
  93. package/dist/src/workspace.js +28 -116
  94. package/jsr.json +1 -1
  95. package/package.json +1 -1
  96. package/src/analysis.ts +55 -76
  97. package/src/automatic-types.ts +11 -29
  98. package/src/build-scripts.ts +17 -38
  99. package/src/component-layer.ts +4 -2
  100. package/src/components.ts +2 -4
  101. package/src/config.ts +31 -296
  102. package/src/configuration-files.ts +27 -170
  103. package/src/configured-declarations.ts +18 -23
  104. package/src/consumers.ts +4 -4
  105. package/src/contract/config.generated.ts +798 -0
  106. package/src/contract/contract.generated.ts +25 -0
  107. package/src/contract/report.generated.ts +3 -2
  108. package/src/conventions.ts +26 -74
  109. package/src/dependencies.ts +32 -50
  110. package/src/discover.ts +15 -40
  111. package/src/edges.ts +41 -140
  112. package/src/element-access.ts +4 -7
  113. package/src/entry-point-gaps.ts +3 -9
  114. package/src/entry-points.ts +2 -13
  115. package/src/enum-group.ts +2 -11
  116. package/src/erased-stores.ts +3 -7
  117. package/src/exempt-classes.ts +0 -5
  118. package/src/exempt.ts +8 -30
  119. package/src/file-facts.ts +6 -24
  120. package/src/findings/completion.ts +5 -6
  121. package/src/findings/emitter.ts +2 -2
  122. package/src/findings/emitters.ts +1 -1
  123. package/src/findings/intra-function.ts +1 -1
  124. package/src/findings/non-code-artifacts.ts +4 -4
  125. package/src/findings/reads-and-writes.ts +2 -1
  126. package/src/findings/self-check.ts +3 -2
  127. package/src/findings/unused-declarations.ts +1 -1
  128. package/src/findings/visibility-narrowing.ts +2 -2
  129. package/src/findings-pass.ts +9 -15
  130. package/src/framework-lifecycle.ts +7 -7
  131. package/src/fs/document.ts +74 -0
  132. package/src/fs/manifest.ts +52 -0
  133. package/src/fs/tree.ts +110 -0
  134. package/src/git-ignore.ts +38 -25
  135. package/src/host.ts +12 -5
  136. package/src/html-entries.ts +6 -33
  137. package/src/in-operands.ts +2 -1
  138. package/src/injection-container.ts +1 -1
  139. package/src/intra-function-parts.ts +10 -14
  140. package/src/inventory.ts +2 -20
  141. package/src/json-document.ts +241 -76
  142. package/src/keyof-comparisons.ts +3 -7
  143. package/src/kinds.ts +0 -8
  144. package/src/manifest.ts +5 -46
  145. package/src/mdx-entries.ts +3 -26
  146. package/src/partial.ts +2 -1
  147. package/src/paths.ts +27 -15
  148. package/src/position.ts +2 -1
  149. package/src/print.ts +12 -92
  150. package/src/product.ts +18 -0
  151. package/src/references.ts +14 -29
  152. package/src/report-schema.ts +3 -301
  153. package/src/report.ts +24 -34
  154. package/src/reporters/sarif.ts +4 -4
  155. package/src/reporters/template.ts +4 -20
  156. package/src/resolve.ts +283 -1114
  157. package/src/roots.ts +7 -20
  158. package/src/run.ts +57 -75
  159. package/src/schema.ts +391 -388
  160. package/src/scope.ts +40 -126
  161. package/src/serialization-contract.ts +5 -10
  162. package/src/session.ts +2 -1
  163. package/src/suppress-file.ts +21 -29
  164. package/src/suppress.ts +3 -3
  165. package/src/symbol-keys.ts +2 -5
  166. package/src/template-field.ts +15 -25
  167. package/src/test-globals.ts +9 -27
  168. package/src/test-joins.ts +6 -8
  169. package/src/text/compare.ts +27 -0
  170. package/src/text/glob.ts +210 -0
  171. package/src/text/shell.ts +161 -0
  172. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  173. package/src/type-errors.ts +25 -36
  174. package/src/unseen-loads.ts +1 -12
  175. package/src/unwrap.ts +34 -0
  176. package/src/usage.ts +10 -0
  177. package/src/verbs/analyze.ts +32 -42
  178. package/src/verbs/describe.ts +4 -4
  179. package/src/verbs/explain.ts +8 -10
  180. package/src/verbs/print-projects.ts +4 -3
  181. package/src/verbs/print-retained.ts +2 -1
  182. package/src/verbs/print-roots.ts +2 -1
  183. package/src/verbs/verb.ts +2 -2
  184. package/src/verbs/version.ts +2 -1
  185. package/src/workflow-steps.ts +9 -44
  186. package/src/workspace-manifests.ts +2 -2
  187. package/src/workspace-programs.ts +5 -3
  188. package/src/workspace-resolution.ts +21 -17
  189. package/src/workspace.ts +35 -121
  190. package/dist/src/glob.js +0 -65
  191. package/dist/src/pnpm-workspace.js +0 -120
  192. package/src/glob.ts +0 -67
  193. package/src/pnpm-workspace.ts +0 -135
@@ -5,14 +5,19 @@
5
5
  * directory a configuration property moves read from the property's literal value.
6
6
  * No configuration file is run.
7
7
  */
8
- import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
8
+ import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
9
9
  import { lineOf } from "./component-files.js";
10
10
  import { CONVENTION_ROWS, } from "./convention-rows.js";
11
11
  import { documentStrings } from "./configuration-files.js";
12
- import { globExpression } from "./glob.js";
13
- import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
12
+ import { ancestors, dirnamePath, joinPath, normalizePath, relativePath, resolvePath, } from "./paths.js";
14
13
  import { satisfies } from "./version-range.js";
15
- import { yamlDocument } from "./yaml-document.js";
14
+ import { bytewise } from "./text/compare.js";
15
+ import { isRecord } from "./json-document.js";
16
+ import { unwrapped } from "./unwrap.js";
17
+ import { filesIn } from "./fs/tree.js";
18
+ import { INSTALLED_PACKAGES, MANIFEST, readJSON } from "./fs/manifest.js";
19
+ import { globExpression } from "./text/glob.js";
20
+ import { yamlDocument } from "./text/yaml.js";
16
21
  const JSON_EXTENSION = ".json";
17
22
  /**
18
23
  * A configuration file its tool reads as a YAML document, read as the JSON document it
@@ -29,17 +34,6 @@ const DEPENDENCY_SECTIONS = [
29
34
  "peerDependencies",
30
35
  "optionalDependencies",
31
36
  ];
32
- function isRecord(value) {
33
- return typeof value === "object" && value !== null && !Array.isArray(value);
34
- }
35
- function readJSON(host, path) {
36
- try {
37
- return JSON.parse(host.readFile(path));
38
- }
39
- catch {
40
- return undefined;
41
- }
42
- }
43
37
  function declares(manifest, name, sections = DEPENDENCY_SECTIONS) {
44
38
  if (!isRecord(manifest)) {
45
39
  return false;
@@ -55,32 +49,15 @@ function declares(manifest, name, sections = DEPENDENCY_SECTIONS) {
55
49
  * none, or `undefined` where no `node_modules` holds it.
56
50
  */
57
51
  function installedVersion(host, dir, name) {
58
- for (let at = normalizePath(dir);; at = dirnamePath(at)) {
59
- const path = joinPath(at, "node_modules", name, "package.json");
52
+ for (const at of ancestors(dir)) {
53
+ const path = joinPath(at, INSTALLED_PACKAGES, name, MANIFEST);
60
54
  if (host.kindOf(path) === "file") {
61
55
  const manifest = readJSON(host, path);
62
56
  const version = isRecord(manifest) ? manifest["version"] : undefined;
63
57
  return typeof version === "string" ? version : "";
64
58
  }
65
- if (dirnamePath(at) === at) {
66
- return undefined;
67
- }
68
59
  }
69
- }
70
- /** Whether one node wraps an expression without changing its value. */
71
- function isWrapper(node) {
72
- return (isParenthesizedExpression(node) ||
73
- isAsExpression(node) ||
74
- isSatisfiesExpression(node) ||
75
- isNonNullExpression(node));
76
- }
77
- /** The expression a wrapper that changes no value holds. */
78
- function unwrapped(node) {
79
- let at = node;
80
- while (isWrapper(at)) {
81
- at = at.expression;
82
- }
83
- return at;
60
+ return undefined;
84
61
  }
85
62
  /** The key one property is written with, where it is written as a name or as literal text. */
86
63
  function keyOf(property) {
@@ -569,17 +546,7 @@ function expand(template, resolved) {
569
546
  /** The configuration files in one directory that one glob names, by name. */
570
547
  function configurationFiles(host, dir, glob) {
571
548
  const expression = globExpression(glob);
572
- let entries;
573
- try {
574
- entries = host.readDirectory(dir);
575
- }
576
- catch {
577
- return [];
578
- }
579
- return entries
580
- .filter((entry) => !entry.directory && expression.test(entry.name))
581
- .map((entry) => entry.name)
582
- .sort();
549
+ return filesIn(host, dir, (name) => expression.test(name));
583
550
  }
584
551
  function resolveMove(host, parse, dir, row, move, resolved) {
585
552
  const set = [];
@@ -667,7 +634,7 @@ function shortNameUses(host, parse, dir, manifest, readings) {
667
634
  }
668
635
  }
669
636
  }
670
- return [...used].filter((name) => declared.has(name)).sort(compare);
637
+ return [...used].filter((name) => declared.has(name)).sort(bytewise);
671
638
  }
672
639
  /**
673
640
  * The package one value of a prefixed-name reading names, or none: ESLint's own naming,
@@ -718,7 +685,7 @@ function prefixedNameUses(host, parse, dir, manifest, readings) {
718
685
  }
719
686
  }
720
687
  }
721
- return [...used].sort(compare);
688
+ return [...used].sort(bytewise);
722
689
  }
723
690
  /** The declared dependencies an applied row's helper settings use. */
724
691
  function helperUses(host, parse, dir, manifest, readings) {
@@ -775,13 +742,6 @@ function packageKeyUses(host, parse, dir, manifest, keys) {
775
742
  text.startsWith(`${name}/`) ||
776
743
  (text.startsWith(`${name}:`) && text.length > name.length + 1)));
777
744
  }
778
- /** Two strings ordered bytewise. */
779
- function compare(a, b) {
780
- if (a === b) {
781
- return 0;
782
- }
783
- return a < b ? -1 : 1;
784
- }
785
745
  /**
786
746
  * What the rows decide over the manifests the analysis reads. A row `disabled` names
787
747
  * is skipped before anything is read for it.
@@ -795,7 +755,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
795
755
  const selected = new Map();
796
756
  const testFiles = new Set();
797
757
  for (const manifest of manifests) {
798
- const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
758
+ const declared = readJSON(host, joinPath(manifest.dir, MANIFEST));
799
759
  const versions = new Map();
800
760
  const missing = new Set();
801
761
  for (const row of rows) {
@@ -893,15 +853,15 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
893
853
  }
894
854
  }
895
855
  const unique = [...new Map(applied.map((one) => [JSON.stringify(one), one])).values()];
896
- unique.sort((a, b) => compare(a.name, b.name) ||
897
- compare(a.manifest, b.manifest) ||
898
- compare(JSON.stringify(a), JSON.stringify(b)));
856
+ unique.sort((a, b) => bytewise(a.name, b.name) ||
857
+ bytewise(a.manifest, b.manifest) ||
858
+ bytewise(JSON.stringify(a), JSON.stringify(b)));
899
859
  return {
900
860
  applied: unique,
901
861
  globs,
902
862
  generated,
903
863
  failures,
904
- uses: [...uses].sort(compare),
864
+ uses: [...uses].sort(bytewise),
905
865
  selected,
906
866
  testFiles,
907
867
  };
@@ -10,14 +10,15 @@ import { isArrayLiteralExpression, isCallExpression, isExportDeclaration, isExpr
10
10
  import { automaticTypes } from "./automatic-types.js";
11
11
  import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, outsideChain, } from "./configuration-files.js";
12
12
  import { nodeKey, packageScope } from "./inventory.js";
13
- import { scriptTokens } from "./manifest.js";
14
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
13
+ import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
15
14
  import { UNANSWERED } from "./query.js";
16
15
  import { sourceFilesOf } from "./source-files.js";
17
16
  import { workflowCommands, workflowTokens } from "./workflow-steps.js";
18
- const MANIFEST = "package.json";
19
- const MODULES_DIR = "node_modules";
20
- const MODULES_SEGMENT = `/${MODULES_DIR}/`;
17
+ import { bytewise } from "./text/compare.js";
18
+ import { isRecord } from "./json-document.js";
19
+ import { inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, readDocument } from "./fs/manifest.js";
20
+ import { commandsOf, scriptTokens } from "./text/shell.js";
21
+ const MODULES_SEGMENT = `/${INSTALLED_PACKAGES}/`;
21
22
  const TYPES_SCOPE = "@types/";
22
23
  /** A script token naming a JavaScript file by a path no expansion changes. */
23
24
  const SCRIPT_FILE = /^[^$`{*?[~]+\.(?:js|mjs|cjs)$/u;
@@ -39,25 +40,6 @@ export function configurationNeeds(packages) {
39
40
  }
40
41
  /** The key of {@link ProjectNeeds.at} for a use no file of the target governs. */
41
42
  const ANYWHERE = "";
42
- function isRecord(value) {
43
- return typeof value === "object" && value !== null && !Array.isArray(value);
44
- }
45
- /** One JSON document's decoded value, or undefined where it cannot be read or parsed. */
46
- function readJSON(host, path) {
47
- let text;
48
- try {
49
- text = host.readFile(path);
50
- }
51
- catch {
52
- return undefined;
53
- }
54
- try {
55
- return { text, value: JSON.parse(text) };
56
- }
57
- catch {
58
- return undefined;
59
- }
60
- }
61
43
  /**
62
44
  * The package a bare module specifier names: its first segment, or its first two
63
45
  * where it starts with a scope. A relative, absolute or scheme-prefixed specifier and
@@ -280,7 +262,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
280
262
  }
281
263
  for (const { strings, manifestDirs } of configuration.documents) {
282
264
  for (const manifestDir of manifestDirs) {
283
- const names = declaredAt(joinPath(manifestDir, "package.json"));
265
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
284
266
  for (const text of strings) {
285
267
  use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
286
268
  }
@@ -289,7 +271,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
289
271
  const ranFiles = outsideChain(host, targetRoot, files, parse, scriptRunFiles(host, targetRoot, files));
290
272
  for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
291
273
  for (const manifestDir of manifestDirs) {
292
- const names = declaredAt(joinPath(manifestDir, "package.json"));
274
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
293
275
  for (const { text } of moduleStrings(file)) {
294
276
  use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
295
277
  }
@@ -307,8 +289,8 @@ function scriptRunFiles(host, targetRoot, files) {
307
289
  const root = resolvePath(host.workingDirectory(), targetRoot);
308
290
  const dirs = new Set([root]);
309
291
  for (const name of files.byName.keys()) {
310
- for (let dir = dirnamePath(name); !dirs.has(dir); dir = dirnamePath(dir)) {
311
- if (relativePath(root, dir) === undefined) {
292
+ for (const dir of ancestors(dirnamePath(name), root)) {
293
+ if (dirs.has(dir)) {
312
294
  break;
313
295
  }
314
296
  dirs.add(dir);
@@ -318,7 +300,7 @@ function scriptRunFiles(host, targetRoot, files) {
318
300
  [root, new Set(workflowTokens(host, root))],
319
301
  ]);
320
302
  for (const dir of dirs) {
321
- const read = readJSON(host, joinPath(dir, MANIFEST));
303
+ const read = readDocument(host, joinPath(dir, MANIFEST));
322
304
  if (read !== undefined && isRecord(read.value)) {
323
305
  const own = scriptTokensOf(read.value);
324
306
  tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
@@ -330,7 +312,8 @@ function scriptRunFiles(host, targetRoot, files) {
330
312
  const path = SCRIPT_FILE.test(token) ? resolvePath(manifestDir, token) : undefined;
331
313
  const below = path === undefined ? undefined : relativePath(root, path);
332
314
  if (path !== undefined &&
333
- below?.split("/").includes(MODULES_DIR) === false &&
315
+ below !== undefined &&
316
+ !inInstalledPackages(below) &&
334
317
  !files.byName.has(path) &&
335
318
  host.kindOf(path) === "file") {
336
319
  found.push({ path, manifestDir });
@@ -600,8 +583,8 @@ function lineAndColumn(text, offset) {
600
583
  }
601
584
  /** The installed copy of one dependency, read where the package manager puts it for the target. */
602
585
  function installedOf(host, root, name) {
603
- for (let dir = root;; dir = dirnamePath(dir)) {
604
- const read = readJSON(host, joinPath(dir, MODULES_DIR, name, MANIFEST));
586
+ for (const dir of ancestors(root)) {
587
+ const read = readDocument(host, joinPath(dir, INSTALLED_PACKAGES, name, MANIFEST));
605
588
  if (read !== undefined && isRecord(read.value)) {
606
589
  const bin = read.value["bin"];
607
590
  const peers = read.value["peerDependencies"];
@@ -617,10 +600,8 @@ function installedOf(host, root, name) {
617
600
  : [],
618
601
  };
619
602
  }
620
- if (dirnamePath(dir) === dir) {
621
- return undefined;
622
- }
623
603
  }
604
+ return undefined;
624
605
  }
625
606
  /** Every dependency the manifest text at `path` declares, in the order the text writes them. */
626
607
  function declaredIn(text, manifest, path, scope) {
@@ -694,8 +675,6 @@ const PRELOADING_PROGRAMS = new Set([
694
675
  /** The programs that run the command after them, and an environment assignment before it. */
695
676
  const LAUNCHERS = new Set(["cross-env", "env", "npx"]);
696
677
  const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
697
- /** The commands one script string runs, split at the shell's list and pipeline operators. */
698
- const COMMAND_SEPARATORS = /&&|\|\||;|\||&/u;
699
678
  /**
700
679
  * The command strings one manifest runs: each of its `scripts`, and the `exec` command of
701
680
  * nodemon's configuration, in the manifest's `nodemonConfig` or the `nodemon.json` beside
@@ -704,7 +683,10 @@ const COMMAND_SEPARATORS = /&&|\|\||;|\||&/u;
704
683
  */
705
684
  function manifestCommands(host, dir, manifest) {
706
685
  const scripts = manifest["scripts"];
707
- const nodemon = [manifest["nodemonConfig"], readJSON(host, joinPath(dir, "nodemon.json"))?.value];
686
+ const nodemon = [
687
+ manifest["nodemonConfig"],
688
+ readDocument(host, joinPath(dir, "nodemon.json"))?.value,
689
+ ];
708
690
  return [
709
691
  ...(isRecord(scripts) ? Object.values(scripts) : []),
710
692
  ...nodemon.map((config) => (isRecord(config) ? config["exec"] : undefined)),
@@ -718,7 +700,7 @@ function manifestCommands(host, dir, manifest) {
718
700
  function preloadsOf(scripts) {
719
701
  const found = new Set();
720
702
  for (const script of scripts) {
721
- for (const command of script.split(COMMAND_SEPARATORS)) {
703
+ for (const command of commandsOf(script)) {
722
704
  const tokens = scriptTokens(command);
723
705
  let at = 0;
724
706
  while (at < tokens.length &&
@@ -833,7 +815,7 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
833
815
  const peers = new Map();
834
816
  const uninstalledHere = new Set(nested);
835
817
  for (const [dir, path] of manifests) {
836
- const read = readJSON(host, joinPath(dir, MANIFEST));
818
+ const read = readDocument(host, joinPath(dir, MANIFEST));
837
819
  if (read === undefined || !isRecord(read.value)) {
838
820
  continue;
839
821
  }
@@ -909,7 +891,7 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
909
891
  const heldBy = new Map();
910
892
  for (const [key, held] of holders) {
911
893
  if (!live.has(key) && !ran.has(key)) {
912
- heldBy.set(key, [...held].sort((a, b) => (a < b ? -1 : 1)));
894
+ heldBy.set(key, [...held].sort(bytewise));
913
895
  }
914
896
  }
915
897
  const users = new Map();
@@ -921,7 +903,7 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
921
903
  for (const id of candidates) {
922
904
  const sole = [...(uses.get(id) ?? [])].filter((one) => users.get(one) === 1 && names.has(one));
923
905
  if (sole.length > 0) {
924
- lastUses.set(id, sole.sort((a, b) => (a < b ? -1 : 1)));
906
+ lastUses.set(id, sole.sort(bytewise));
925
907
  }
926
908
  }
927
909
  const owners = new Map();
@@ -933,7 +915,7 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
933
915
  }
934
916
  }
935
917
  const fileLastUses = new Map();
936
- for (const [one, held] of [...owners].sort(([a], [b]) => (a < b ? -1 : 1))) {
918
+ for (const [one, held] of [...owners].sort(([a], [b]) => bytewise(a, b))) {
937
919
  const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
938
920
  const [path] = paths;
939
921
  if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
@@ -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
  /**
@@ -362,7 +336,7 @@ function derivedProjects(engine, host, scope) {
362
336
  * package whose sources ship as TypeScript is analyzed as it stands.
363
337
  */
364
338
  export function discoverProjects(engine, host, scope, configurations = []) {
365
- const declared = configurations.filter((entry) => entry.shape === "project");
339
+ const declared = configurations.filter((entry) => "project" in entry);
366
340
  if (declared.length > 0) {
367
341
  return declaredProjects(engine, host, scope.target.path, declared);
368
342
  }
package/dist/src/edges.js CHANGED
@@ -4,153 +4,69 @@
4
4
  * never resolves the paired symbol: the other language's analyzer decides whether the
5
5
  * pairing is a use, and the merge reads the two evaluations together.
6
6
  */
7
- import { ConfigError } from "./config.js";
8
- import { positionAt, walkDocument } from "./json-document.js";
9
- import { joinPath } from "./paths.js";
7
+ import { EDGES_SCHEMA } from "./contract/config.generated.js";
8
+ import { readTargetDocument } from "./fs/document.js";
9
+ import { dotted, placeIn, positionAt, readStrict, StrictJSONError } from "./json-document.js";
10
10
  import { positionKey } from "./position.js";
11
+ import { firstRefusal } from "./schema.js";
11
12
  import { isPartSubject } from "./suppress.js";
13
+ import { bytewise } from "./text/compare.js";
14
+ import { UsageError } from "./usage.js";
12
15
  /** The name and the location the Contract fixes for the edges document. */
13
16
  export const EDGES_FILE = "deadset-edges.json";
14
17
  /** The language prefix of every reference this analyzer evaluates. */
15
18
  const OWN_PREFIX = "ts://";
16
- /** The closed key list of the document and of one edge. */
17
- const DOCUMENT_KEYS = new Set(["description", "edges"]);
18
- const EDGE_KEYS = new Set(["id", "because", "provides", "used_by"]);
19
- /** An edge identifier: solidus-separated segments of ASCII letters, digits, `_`, `.` and `-`. */
20
- const ID_FORM = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/u;
21
- /** A reference naming one symbol of some language: `<language>://<scope>#<fragment>`. */
22
- const REFERENCE_FORM = /^[a-z][a-z0-9]*:\/\/[^ \t\r\n#]+#[^ \t\r\n]*$/u;
23
- const DOCUMENT_WANT = "an object carrying the edges array, and an optional description";
24
- const EDGE_WANT = "an edge naming id, provides and used_by, each a string, and an optional because";
25
- const ID_WANT = "one or more solidus-separated segments of ASCII letters, digits, underscore, full stop and hyphen";
26
- const REFERENCE_WANT = "a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
27
19
  /** An edges document the Contract refuses: a malformed input, answered with the usage code. */
28
- class EdgesError extends ConfigError {
29
- constructor(at, text, want) {
30
- super("malformed", EDGES_FILE, `${at}: ${text}: want ${want}`);
20
+ class EdgesError extends UsageError {
21
+ constructor(at, reason) {
22
+ super(`${at}: ${reason}`);
31
23
  this.name = "EdgesError";
32
24
  }
33
25
  }
34
- /**
35
- * Whether a reference carries a wildcard outside a quoted or a computed component, which
36
- * would name a set of symbols rather than one.
37
- */
38
- function carriesWildcard(ref) {
39
- let quoted = false;
40
- let computed = false;
41
- let escaped = false;
42
- for (const char of ref) {
43
- if (escaped) {
44
- escaped = false;
45
- }
46
- else if (quoted && char === "\\") {
47
- escaped = true;
48
- }
49
- else if (quoted) {
50
- quoted = char !== "'";
51
- }
52
- else if (computed) {
53
- computed = char !== "]";
54
- }
55
- else if (char === "'") {
56
- quoted = true;
57
- }
58
- else if (char === "[") {
59
- computed = true;
60
- }
61
- else if (char === "*") {
62
- return true;
63
- }
64
- }
65
- return false;
66
- }
67
- function isObject(value) {
68
- return typeof value === "object" && value !== null && !Array.isArray(value);
69
- }
70
- /** One edge, checked: the closed key list, the value types and the value forms. */
71
- function readEdge(value, at) {
72
- const refuse = (text, want) => {
73
- throw new EdgesError(at, text, want);
74
- };
75
- if (!isObject(value)) {
76
- return refuse(JSON.stringify(value), EDGE_WANT);
77
- }
78
- for (const [key, held] of Object.entries(value)) {
79
- if (!EDGE_KEYS.has(key) || typeof held !== "string") {
80
- refuse(`the member ${JSON.stringify(key)} holding ${JSON.stringify(held)}`, EDGE_WANT);
81
- }
82
- }
83
- const edge = value;
84
- const id = edge["id"] ?? refuse("an edge naming no id", EDGE_WANT);
85
- const provides = edge["provides"] ?? refuse("an edge naming no provides side", EDGE_WANT);
86
- const usedBy = edge["used_by"] ?? refuse("an edge naming no used_by side", EDGE_WANT);
87
- if (!ID_FORM.test(id)) {
88
- refuse(JSON.stringify(id), ID_WANT);
89
- }
90
- for (const ref of [provides, usedBy]) {
91
- if (!REFERENCE_FORM.test(ref) || carriesWildcard(ref)) {
92
- refuse(JSON.stringify(ref), REFERENCE_WANT);
93
- }
94
- }
95
- return { id, provides, usedBy };
96
- }
26
+ /** The sides of one edge, in the order a reader yields them. */
27
+ const SIDE_NAMES = ["provides", "used_by"];
97
28
  /**
98
29
  * Every side of a declared edge that names a symbol of this language, in document order,
99
30
  * `provides` before `used_by` within an edge. An absent document declares no edge. The
100
- * document is strict JSON with a closed key list at both levels, and a defect in it is an
101
- * {@link EdgesError}.
31
+ * document is strict JSON held to the Contract's edges schema, and a defect in it is an
32
+ * {@link EdgesError} at the line and column of the edge it is in, where it is in one.
102
33
  */
103
34
  export function readEdgeSides(host, targetRoot) {
104
- const file = joinPath(targetRoot, EDGES_FILE);
105
- if (host.kindOf(file) === "absent") {
35
+ const text = readTargetDocument(host, targetRoot, EDGES_FILE);
36
+ if (text === undefined) {
106
37
  return [];
107
38
  }
108
- const text = host.readFile(file);
109
- let parsed;
39
+ let read;
110
40
  try {
111
- parsed = JSON.parse(text);
41
+ read = readStrict(text, "edges");
112
42
  }
113
43
  catch (error) {
114
- throw new EdgesError(EDGES_FILE, error instanceof Error ? error.message : String(error), DOCUMENT_WANT);
115
- }
116
- if (!isObject(parsed)) {
117
- throw new EdgesError(EDGES_FILE, JSON.stringify(parsed), DOCUMENT_WANT);
118
- }
119
- const walk = walkDocument(text, "edges");
120
- if (walk.twice !== undefined) {
121
- throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, walk.twice.offset)), `the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, DOCUMENT_WANT);
44
+ if (!(error instanceof StrictJSONError)) {
45
+ throw error;
46
+ }
47
+ throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, error.offset)), error.message);
122
48
  }
123
- const unknown = Object.keys(parsed).find((key) => !DOCUMENT_KEYS.has(key));
124
- const description = parsed["description"];
125
- const edges = parsed["edges"];
126
- if (unknown !== undefined ||
127
- (description !== undefined && typeof description !== "string") ||
128
- !Array.isArray(edges)) {
129
- throw new EdgesError(EDGES_FILE, unknown === undefined ? EDGES_FILE : `the member ${JSON.stringify(unknown)}`, DOCUMENT_WANT);
49
+ const value = read.value;
50
+ const refused = firstRefusal(EDGES_SCHEMA, value);
51
+ if (refused !== undefined) {
52
+ const place = placeIn(value, refused.pointer, refused.member);
53
+ const offset = place.length > 1 ? read.starts.get(place[1] ?? "") : undefined;
54
+ const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
55
+ const where = dotted(place);
56
+ throw new EdgesError(at, where === "" ? refused.reason : `${where}: ${refused.reason}`);
130
57
  }
58
+ const edges = value.edges;
131
59
  const sides = [];
132
- edges.forEach((value, index) => {
133
- const offset = walk.starts[index];
134
- const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
135
- const edge = readEdge(value, at);
136
- const named = [
137
- ["provides", edge.provides],
138
- ["used_by", edge.usedBy],
139
- ];
140
- for (const [side, symbol] of named) {
141
- if (symbol.startsWith(OWN_PREFIX)) {
142
- sides.push({ edge: edge.id, side, symbol });
60
+ for (const id of read.starts.keys()) {
61
+ const edge = String(id);
62
+ for (const side of SIDE_NAMES) {
63
+ const symbol = edges[edge]?.[side];
64
+ if (symbol?.startsWith(OWN_PREFIX) === true) {
65
+ sides.push({ edge, side, symbol });
143
66
  }
144
67
  }
145
- });
146
- return sides;
147
- }
148
- /** Two strings ordered bytewise. */
149
- function compare(a, b) {
150
- if (a === b) {
151
- return 0;
152
68
  }
153
- return a < b ? -1 : 1;
69
+ return sides;
154
70
  }
155
71
  /**
156
72
  * Evaluates every declared side of this language over the run's findings. A side naming a
@@ -191,6 +107,6 @@ export function evaluateEdges(findings, sides, symbols) {
191
107
  return { ...side, state: "dead", finding };
192
108
  });
193
109
  });
194
- evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
110
+ evaluations.sort((a, b) => bytewise(a.edge, b.edge) || bytewise(a.side, b.side));
195
111
  return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
196
112
  }
@@ -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;