@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
@@ -7,8 +7,12 @@
7
7
  * uses it and each relative one that names a source file of the program roots the file.
8
8
  */
9
9
  import { isCallExpression, isExportDeclaration, isIdentifier, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
10
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
- const MANIFEST = "package.json";
10
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
+ import { bytewise } from "./text/compare.js";
12
+ import { isRecord } from "./json-document.js";
13
+ import { filesIn } from "./fs/tree.js";
14
+ import { holdsManifest, inInstalledPackages, manifestAt, readJSON } from "./fs/manifest.js";
15
+ import { shellWords } from "./text/shell.js";
12
16
  /** A configuration module's name, each form with the spelling a root reports it by. */
13
17
  const MODULE_FORMS = [
14
18
  { name: /^[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u, form: "<stem>.config.<ext>" },
@@ -50,18 +54,14 @@ export const RESOLVED_EXTENSIONS = [
50
54
  ".mjs",
51
55
  ".cjs",
52
56
  ];
53
- /** The directory a package manager installs packages into, whose files are no project's own. */
54
- const MODULES_DIR = "node_modules";
55
57
  /** The file's name, the last segment of its path. */
56
58
  function basename(path) {
57
59
  return path.slice(path.lastIndexOf("/") + 1);
58
60
  }
59
61
  /** Whether one path is the project's own: below the target root and in no `node_modules`. */
60
62
  function isProjectPath(root, path) {
61
- return relativePath(root, path)?.split("/").includes(MODULES_DIR) === false;
62
- }
63
- function isRecord(value) {
64
- return typeof value === "object" && value !== null && !Array.isArray(value);
63
+ const below = relativePath(root, path);
64
+ return below !== undefined && !inInstalledPackages(below);
65
65
  }
66
66
  /** The form one file name is as a configuration module, or undefined where it is none. */
67
67
  export function configurationModuleForm(name) {
@@ -75,122 +75,13 @@ export function isConfigurationDocumentName(name) {
75
75
  const CONFIG_FLAGS = new Set(["--config", "-c"]);
76
76
  /** The extensions of a module a configuration flag may name. */
77
77
  const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
78
- /** The characters that end a word outside quotes and separate nothing: blanks. */
79
- // Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
80
- const BLANK = /[ \t\r\f\v]/u;
81
- /** The characters that end a word outside quotes and begin an operator: newline included. */
82
- const OPERATOR = /[\n;&|()<>]/u;
83
- /** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
84
- const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
85
- /**
86
- * The words of one command line, as POSIX token recognition reads quotes, backslashes,
87
- * line continuations, comments and operators. A word an expansion may change (an unquoted
88
- * `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
89
- * is computed, and so is the rest of a line whose quote is never closed. A computed word
90
- * and each operator are undefined, so a flag's file is never read across them.
91
- */
92
- function shellWords(command) {
93
- const words = [];
94
- let word;
95
- let computed = false;
96
- const end = () => {
97
- if (word !== undefined) {
98
- words.push(computed ? undefined : word);
99
- }
100
- word = undefined;
101
- computed = false;
102
- };
103
- for (let at = 0; at < command.length; at += 1) {
104
- const char = command.charAt(at);
105
- if (char === "\\" && command.charAt(at + 1) === "\n") {
106
- at += 1;
107
- }
108
- else if (BLANK.test(char)) {
109
- end();
110
- }
111
- else if (OPERATOR.test(char)) {
112
- end();
113
- words.push(undefined);
114
- }
115
- else if (char === "#" && word === undefined) {
116
- const newline = command.indexOf("\n", at);
117
- at = newline < 0 ? command.length : newline - 1;
118
- }
119
- else if (char === "\\") {
120
- at += 1;
121
- word = (word ?? "") + command.charAt(at);
122
- }
123
- else if (char === "'" || char === '"') {
124
- const close = closingQuote(command, at);
125
- if (close < 0) {
126
- word ??= "";
127
- computed = true;
128
- break;
129
- }
130
- const quoted = char === "'"
131
- ? { text: command.slice(at + 1, close), computed: false }
132
- : doubleQuoted(command.slice(at + 1, close));
133
- word = (word ?? "") + quoted.text;
134
- computed ||= quoted.computed;
135
- at = close;
136
- }
137
- else {
138
- computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
139
- word = (word ?? "") + char;
140
- }
141
- }
142
- end();
143
- return words;
144
- }
145
- /** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
146
- function doubleQuoted(inner) {
147
- let text = "";
148
- let computed = false;
149
- for (let at = 0; at < inner.length; at += 1) {
150
- const char = inner.charAt(at);
151
- const next = inner.charAt(at + 1);
152
- if (char === "\\" && next === "\n") {
153
- at += 1;
154
- }
155
- else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
156
- text += next;
157
- at += 1;
158
- }
159
- else {
160
- computed ||= char === "$" || char === "`";
161
- text += char;
162
- }
163
- }
164
- return { text, computed };
165
- }
166
- /** The index of the quote that closes the one at `open`, or -1 where none does. */
167
- function closingQuote(command, open) {
168
- const quote = command.charAt(open);
169
- for (let at = open + 1; at < command.length; at += 1) {
170
- const char = command.charAt(at);
171
- if (char === quote) {
172
- return at;
173
- }
174
- if (quote === '"' && char === "\\") {
175
- at += 1;
176
- }
177
- }
178
- return -1;
179
- }
180
78
  /**
181
79
  * The files the scripts of the manifest in one directory name after a configuration flag,
182
80
  * `--config <file>`, `-c <file>` or `--config=<file>`, each a JSON file or a module below
183
81
  * the target root and in no `node_modules`.
184
82
  */
185
83
  function scriptConfigurations(host, root, dir) {
186
- let scripts;
187
- try {
188
- const manifest = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
189
- scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
190
- }
191
- catch {
192
- return [];
193
- }
84
+ const scripts = manifestAt(host, dir)?.["scripts"];
194
85
  if (!isRecord(scripts)) {
195
86
  return [];
196
87
  }
@@ -217,15 +108,6 @@ function scriptConfigurations(host, root, dir) {
217
108
  }
218
109
  return found;
219
110
  }
220
- /** The decoded value of one JSON file, or undefined where it cannot be read or parsed. */
221
- function readDocument(host, path) {
222
- try {
223
- return JSON.parse(host.readFile(path));
224
- }
225
- catch {
226
- return undefined;
227
- }
228
- }
229
111
  /** Every string value one decoded JSON value holds at any depth. */
230
112
  export function documentStrings(value) {
231
113
  if (typeof value === "string") {
@@ -258,10 +140,10 @@ export function moduleStrings(file) {
258
140
  export function configurationFiles(host, targetRoot, files, parse) {
259
141
  const root = resolvePath(host.workingDirectory(), targetRoot);
260
142
  const manifests = new Map();
261
- const holdsManifest = (dir) => {
143
+ const manifestHeld = (dir) => {
262
144
  let held = manifests.get(dir);
263
145
  if (held === undefined) {
264
- held = host.kindOf(joinPath(dir, MANIFEST)) === "file";
146
+ held = holdsManifest(host, dir);
265
147
  manifests.set(dir, held);
266
148
  }
267
149
  return held;
@@ -271,30 +153,23 @@ export function configurationFiles(host, targetRoot, files, parse) {
271
153
  for (const file of files.byPath.values()) {
272
154
  const form = configurationModuleForm(basename(file.fileName));
273
155
  const fileDir = dirnamePath(file.fileName);
274
- if (form !== undefined && holdsManifest(fileDir)) {
156
+ if (form !== undefined && manifestHeld(fileDir)) {
275
157
  modules.push({ file, form, manifestDir: fileDir });
276
158
  }
277
- for (let dir = dirnamePath(file.fileName); relativePath(root, dir) !== undefined && !dirs.has(dir); dir = dirnamePath(dir)) {
159
+ for (const dir of ancestors(dirnamePath(file.fileName), root)) {
160
+ if (dirs.has(dir)) {
161
+ break;
162
+ }
278
163
  dirs.add(dir);
279
164
  }
280
165
  }
281
166
  const pending = [];
282
167
  const named = [];
283
- for (const dir of [...dirs].sort()) {
284
- if (!holdsManifest(dir)) {
168
+ for (const dir of [...dirs].sort(bytewise)) {
169
+ if (!manifestHeld(dir)) {
285
170
  continue;
286
171
  }
287
- let names;
288
- try {
289
- names = host
290
- .readDirectory(dir)
291
- .filter((entry) => !entry.directory && isConfigurationDocumentName(entry.name))
292
- .map((entry) => entry.name)
293
- .sort();
294
- }
295
- catch {
296
- names = [];
297
- }
172
+ const names = filesIn(host, dir, isConfigurationDocumentName);
298
173
  pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
299
174
  for (const path of scriptConfigurations(host, root, dir)) {
300
175
  const held = files.byName.get(path);
@@ -318,7 +193,7 @@ export function configurationFiles(host, targetRoot, files, parse) {
318
193
  continue;
319
194
  }
320
195
  if (document === undefined) {
321
- const value = readDocument(host, next.path);
196
+ const value = readJSON(host, next.path);
322
197
  if (value === undefined) {
323
198
  unreadable.add(next.path);
324
199
  continue;
@@ -343,7 +218,7 @@ export function configurationFiles(host, targetRoot, files, parse) {
343
218
  documents: [...documents.values()],
344
219
  outside: parse === undefined
345
220
  ? []
346
- : outsideModules(host, root, [...dirs], files, holdsManifest, parse, named),
221
+ : outsideModules(host, root, [...dirs], files, manifestHeld, parse, named),
347
222
  };
348
223
  }
349
224
  /** The literal module specifier one `require(...)` or `import(...)` call names, if any. */
@@ -429,23 +304,13 @@ export function resolveRelativeSpecifier(host, dir, specifier) {
429
304
  * does not hold, and the project files below `root` they import by a relative specifier,
430
305
  * until no further file joins. A file that cannot be read is skipped.
431
306
  */
432
- function outsideModules(host, root, dirs, files, holdsManifest, parse, named) {
307
+ function outsideModules(host, root, dirs, files, manifestHeld, parse, named) {
433
308
  const pending = [...named];
434
- for (const dir of [...dirs].sort()) {
435
- if (!holdsManifest(dir)) {
436
- continue;
437
- }
438
- let names;
439
- try {
440
- names = host
441
- .readDirectory(dir)
442
- .filter((entry) => !entry.directory && configurationModuleForm(entry.name) !== undefined)
443
- .map((entry) => entry.name)
444
- .sort();
445
- }
446
- catch {
309
+ for (const dir of [...dirs].sort(bytewise)) {
310
+ if (!manifestHeld(dir)) {
447
311
  continue;
448
312
  }
313
+ const names = filesIn(host, dir, (name) => configurationModuleForm(name) !== undefined);
449
314
  pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
450
315
  }
451
316
  const found = new Map();
@@ -481,14 +346,8 @@ function outsideModules(host, root, dirs, files, holdsManifest, parse, named) {
481
346
  }
482
347
  /** Every dependency the manifest in one directory declares, or none where it holds none to read. */
483
348
  export function declaredIn(host, dir) {
484
- let value;
485
- try {
486
- value = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
487
- }
488
- catch {
489
- return [];
490
- }
491
- if (!isRecord(value)) {
349
+ const value = manifestAt(host, dir);
350
+ if (value === undefined) {
492
351
  return [];
493
352
  }
494
353
  const names = new Set();
@@ -19,6 +19,19 @@ export const FORMATS = [
19
19
  "sarif",
20
20
  "template",
21
21
  ];
22
+ /** Each analyzed language's row: its analyzer, the names that mark it and the directories it skips. */
23
+ export const LANGUAGE_ROWS = {
24
+ go: {
25
+ analyzer: "deadset-go",
26
+ markers: ["go.mod"],
27
+ skippedDirectories: ["node_modules", "testdata", "vendor", ".*", "_*"],
28
+ },
29
+ ts: {
30
+ analyzer: "deadset-ts",
31
+ markers: ["tsconfig*.json"],
32
+ skippedDirectories: ["node_modules", ".git"],
33
+ },
34
+ };
22
35
  export const EXIT_CLEAN = 0;
23
36
  export const EXIT_FINDINGS = 1;
24
37
  export const EXIT_USAGE = 2;
@@ -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) {