@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
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { componentModule } from "../src/component-files.js";
9
9
  import { extensionOf } from "../src/paths.js";
10
+ import { PRODUCT } from "../src/product.js";
10
11
  /** The JSON-RPC code of a method this mapper does not serve. */
11
12
  const METHOD_NOT_FOUND = -32601;
12
13
  /** The JSON-RPC code of a request whose parameters are not the protocol's. */
@@ -37,7 +38,7 @@ function answer(message) {
37
38
  });
38
39
  switch (message.method) {
39
40
  case "initialize":
40
- return reply({ positionEncoding: "utf-16", diagnosticSource: "deadset-ts" });
41
+ return reply({ positionEncoding: "utf-16", diagnosticSource: PRODUCT });
41
42
  case "openProject":
42
43
  return reply({});
43
44
  case "closeProject":
@@ -1,10 +1,11 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { closeSync, fchmodSync, fsyncSync, mkdtempSync, openSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeSync, } from "node:fs";
2
+ import { closeSync, fchmodSync, fsyncSync, mkdtempSync, openSync, readdirSync, readFileSync, readSync, realpathSync, renameSync, rmSync, statSync, writeSync, } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { PRODUCT } from "../src/product.js";
6
7
  /** The name the manifest of this package declares. */
7
- const PACKAGE_NAME = "@cplieger/deadset-ts";
8
+ const PACKAGE_NAME = `@cplieger/${PRODUCT}`;
8
9
  /** The version the walk found, read once per process. */
9
10
  let found;
10
11
  /**
@@ -76,9 +77,24 @@ function writeAtomically(path, pieces) {
76
77
  throw error;
77
78
  }
78
79
  }
80
+ /** The text of a file's first `limit` bytes, or of all of it where it holds fewer. */
81
+ function readHead(path, limit) {
82
+ const bytes = Buffer.alloc(limit);
83
+ const fd = openSync(path, "r");
84
+ try {
85
+ let held = 0;
86
+ for (let got = -1; got !== 0 && held < limit; held += got) {
87
+ got = readSync(fd, bytes, held, limit - held, null);
88
+ }
89
+ return bytes.toString("utf8", 0, held);
90
+ }
91
+ finally {
92
+ closeSync(fd);
93
+ }
94
+ }
79
95
  /** A new directory under the platform's temporary directory. */
80
96
  function temporaryDirectory() {
81
- const path = mkdtempSync(join(tmpdir(), "deadset-ts-mapper-"));
97
+ const path = mkdtempSync(join(tmpdir(), `${PRODUCT}-mapper-`));
82
98
  return {
83
99
  path,
84
100
  remove: () => {
@@ -105,7 +121,7 @@ function componentMapperCommand() {
105
121
  export function nodeHost() {
106
122
  return {
107
123
  workingDirectory: () => process.cwd(),
108
- readFile: (path) => readFileSync(path, "utf8"),
124
+ readFile: (path, limit) => limit === undefined ? readFileSync(path, "utf8") : readHead(path, limit),
109
125
  readDirectory: (path) => readdirSync(path, { withFileTypes: true }).map((entry) => ({
110
126
  name: entry.name,
111
127
  directory: entry.isDirectory(),
@@ -115,7 +131,10 @@ export function nodeHost() {
115
131
  if (stat === undefined) {
116
132
  return "absent";
117
133
  }
118
- return stat.isDirectory() ? "directory" : "file";
134
+ if (stat.isDirectory()) {
135
+ return "directory";
136
+ }
137
+ return stat.isFile() ? "file" : "other";
119
138
  },
120
139
  realPath: (path) => realpathSync(path),
121
140
  analyzerVersion: () => {
@@ -22,7 +22,6 @@ import { inOperandNames } from "./in-operands.js";
22
22
  import { sourceFilesOf } from "./source-files.js";
23
23
  import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, writtenRecords, } from "./exempt.js";
24
24
  import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
25
- import { globExpression } from "./glob.js";
26
25
  import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
27
26
  import { unmatchedRootFindings } from "./findings/self-check.js";
28
27
  import { frameworkLifecycle } from "./framework-lifecycle.js";
@@ -33,9 +32,8 @@ import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
33
32
  import { inventory } from "./inventory.js";
34
33
  import { packageEntries, readManifest } from "./manifest.js";
35
34
  import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
36
- import { dirnamePath, extensionOf, joinPath, normalizePath, relativePath, resolvePath, } from "./paths.js";
35
+ import { ancestors, dirnamePath, extensionOf, joinPath, normalizePath, relativePath, resolvePath, } from "./paths.js";
37
36
  import { byPosition, isComponentFile } from "./position.js";
38
- import { LANGUAGE } from "./report.js";
39
37
  import { SetupError, setupLine } from "./setup-failure.js";
40
38
  import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
41
39
  import { reflectiveLookup } from "./reflective-lookup.js";
@@ -55,6 +53,9 @@ import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
55
53
  import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
56
54
  import { readTemplates, templateField } from "./template-field.js";
57
55
  import { ownErrors, workspaceRun } from "./workspace-run.js";
56
+ import { bytewise } from "./text/compare.js";
57
+ import { holdsManifest, inInstalledPackages, MANIFEST, manifestAt } from "./fs/manifest.js";
58
+ import { globExpression } from "./text/glob.js";
58
59
  /** The setting whose source decides where a finding about a configured root sits. */
59
60
  const ROOTS_SETTING = "roots.patterns";
60
61
  /**
@@ -71,21 +72,14 @@ function documentOf(host, provenance, setting, targetRoot) {
71
72
  const document = resolvePath(host.workingDirectory(), origin.label);
72
73
  return relativePath(resolvePath(host.workingDirectory(), targetRoot), document) ?? "";
73
74
  }
74
- /** Two strings ordered bytewise. */
75
- function compare(a, b) {
76
- if (a === b) {
77
- return 0;
78
- }
79
- return a < b ? -1 : 1;
80
- }
81
75
  /**
82
76
  * The manifests the analysis reads, each once: the target's own, then every workspace
83
77
  * member's and every nested package's below the target root.
84
78
  */
85
79
  function conventionManifests(host, targetRoot, memberDirs, nestedDirs) {
86
80
  const found = new Map();
87
- if (host.kindOf(joinPath(targetRoot, "package.json")) === "file") {
88
- found.set(targetRoot, { dir: targetRoot, path: "package.json" });
81
+ if (holdsManifest(host, targetRoot)) {
82
+ found.set(targetRoot, { dir: targetRoot, path: MANIFEST });
89
83
  }
90
84
  for (const [dirs, nested] of [
91
85
  [memberDirs, false],
@@ -109,17 +103,18 @@ function nestedPackageDirs(host, targetRoot, files, known) {
109
103
  const seen = new Set();
110
104
  const found = [];
111
105
  for (const file of files) {
112
- for (let dir = dirnamePath(file); dir !== targetRoot && !seen.has(dir) && relativePath(targetRoot, dir) !== undefined; dir = dirnamePath(dir)) {
106
+ for (const dir of ancestors(dirnamePath(file), targetRoot)) {
107
+ if (seen.has(dir) || relativePath(targetRoot, dir) === undefined) {
108
+ break;
109
+ }
113
110
  seen.add(dir);
114
111
  const below = relativePath(targetRoot, dir) ?? "";
115
- if (!below.split("/").includes("node_modules") &&
116
- !known.has(dir) &&
117
- host.kindOf(joinPath(dir, "package.json")) === "file") {
112
+ if (!inInstalledPackages(below) && !known.has(dir) && holdsManifest(host, dir)) {
118
113
  found.push(dir);
119
114
  }
120
115
  }
121
116
  }
122
- return found.sort(compare);
117
+ return found.sort(bytewise);
123
118
  }
124
119
  /**
125
120
  * Reads every project the scope discovers, and each declared consumer's, in one snapshot,
@@ -145,13 +140,13 @@ function readProjects(engine, host, scope, config, stage) {
145
140
  // The nested packages are known only once every program is open, and every visit reads
146
141
  // the conventions and options they decide, so both are set in the session's first step.
147
142
  let nestedDirs = [];
148
- let conventions = readConventions(host, parse, [], config.ts.disabledConventions);
143
+ let conventions = readConventions(host, parse, [], config.ts.disabled_conventions);
149
144
  let options;
150
145
  const readManifests = (opened) => {
151
146
  nestedDirs = nestedPackageDirs(host, normalizePath(absoluteRoot), opened
152
147
  .filter((one) => !consumerOf.has(one.configFile))
153
148
  .flatMap((one) => [one.configFile, ...one.fileNames()]), new Set(memberDirs));
154
- conventions = readConventions(host, parse, conventionManifests(host, absoluteRoot, memberDirs, nestedDirs), config.ts.disabledConventions);
149
+ conventions = readConventions(host, parse, conventionManifests(host, absoluteRoot, memberDirs, nestedDirs), config.ts.disabled_conventions);
155
150
  options = {
156
151
  host,
157
152
  manifest: workspace?.manifest ?? readManifest(host, targetRoot),
@@ -159,12 +154,12 @@ function readProjects(engine, host, scope, config, stage) {
159
154
  ...(workspace?.entries ?? []),
160
155
  ...packageEntries(host, normalizePath(absoluteRoot), nestedDirs),
161
156
  ],
162
- patterns: config.rootPatterns,
163
- entryFiles: config.ts.entryFiles,
157
+ patterns: config.roots.patterns,
158
+ entryFiles: config.ts.entry_files,
164
159
  conventions,
165
- testFiles: config.ts.testFiles,
160
+ testFiles: config.ts.test_files,
166
161
  parse,
167
- publishedAPI: config.targetKind !== "" && kindOf(config.targetKind, LANGUAGE) === "library",
162
+ publishedAPI: config.target.kind !== undefined && kindOf(config.target.kind) === "library",
168
163
  publishedMembers: new Set((workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir)),
169
164
  runFiles: new Set(opened.flatMap((one) => one.fileNames().map((name) => resolvePath(host.workingDirectory(), name)))),
170
165
  };
@@ -194,7 +189,7 @@ function readProjects(engine, host, scope, config, stage) {
194
189
  unbuildable.push(...met);
195
190
  }
196
191
  };
197
- const testPatterns = config.ts.testFiles.map(globExpression);
192
+ const testPatterns = config.ts.test_files.map(globExpression);
198
193
  const joins = discovered.derived
199
194
  ? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
200
195
  : new Map();
@@ -400,8 +395,10 @@ export function runRoots(engine, host, scope, config, provenance) {
400
395
  }
401
396
  }
402
397
  }
403
- const ordered = [...merged.values()].sort((a, b) => byPosition(a.position, b.position) || compare(a.kind, b.kind) || compare(a.source, b.source));
404
- const unmatched = unmatchedEverywhere(config.rootPatterns, projects.map((answer) => answer.rooted));
398
+ const ordered = [...merged.values()].sort((a, b) => byPosition(a.position, b.position) ||
399
+ bytewise(a.kind, b.kind) ||
400
+ bytewise(a.source, b.source));
401
+ const unmatched = unmatchedEverywhere(config.roots.patterns, projects.map((answer) => answer.rooted));
405
402
  return {
406
403
  configurations,
407
404
  roots: ordered,
@@ -438,18 +435,9 @@ function testEvidence(host, targetRoot, projects) {
438
435
  }
439
436
  }
440
437
  const manifests = new Map();
441
- const manifestAt = (dir) => {
438
+ const manifestIn = (dir) => {
442
439
  if (!manifests.has(dir)) {
443
- let value;
444
- try {
445
- value = JSON.parse(host.readFile(joinPath(dir, "package.json")));
446
- }
447
- catch {
448
- value = undefined;
449
- }
450
- manifests.set(dir, typeof value === "object" && value !== null && !Array.isArray(value)
451
- ? value
452
- : undefined);
440
+ manifests.set(dir, manifestAt(host, dir));
453
441
  }
454
442
  return manifests.get(dir);
455
443
  };
@@ -458,16 +446,14 @@ function testEvidence(host, targetRoot, projects) {
458
446
  return {
459
447
  imports,
460
448
  devDependency: (path, name) => {
461
- for (let dir = dirnamePath(joinPath(root, path));; dir = dirnamePath(dir)) {
462
- const manifest = manifestAt(dir);
449
+ for (const dir of ancestors(dirnamePath(joinPath(root, path)), root)) {
450
+ const manifest = manifestIn(dir);
463
451
  const other = OTHER_SECTIONS.some((one) => declares(manifest?.[one], name));
464
452
  if (declares(manifest?.[DEV_SECTION], name) || other) {
465
453
  return !other;
466
454
  }
467
- if (normalizePath(dir) === normalizePath(root) || dirnamePath(dir) === dir) {
468
- return false;
469
- }
470
455
  }
456
+ return false;
471
457
  },
472
458
  };
473
459
  }
@@ -481,13 +467,13 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
481
467
  const targetRoot = scope.target.path;
482
468
  const disabled = disabledClasses(config);
483
469
  const templates = disabled.has("template-field")
484
- ? { delimiters: config.analysis.templateDelimiters, files: [] }
470
+ ? { delimiters: config.analysis.template_delimiters, files: [] }
485
471
  : readTemplates(host, targetRoot, config.analysis);
486
472
  const consumers = scope.consumers.map((consumer) => consumer.path);
487
473
  const read = readProjects(engine, host, scope, config, (project, opened) => {
488
474
  const projectRead = opened.read();
489
475
  const resolved = references(project, projectRead.held, targetRoot, {
490
- testFiles: config.ts.testFiles,
476
+ testFiles: config.ts.test_files,
491
477
  testFileNames: projectRead.testFileNames,
492
478
  });
493
479
  const evidence = evidenceRecords({
@@ -497,7 +483,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
497
483
  templates,
498
484
  ts: config.ts,
499
485
  consumers,
500
- ...(config.analysis.generatedFiles === "include"
486
+ ...(config.analysis.generated_files === "include"
501
487
  ? {}
502
488
  : { generated: projectRead.generated }),
503
489
  }, detectors, disabled);
@@ -574,8 +560,8 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
574
560
  const exempt = detected.records;
575
561
  const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
576
562
  const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
577
- const supportFiles = [...support].filter((path) => held.has(path)).sort();
578
- const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort();
563
+ const supportFiles = [...support].filter((path) => held.has(path)).sort(bytewise);
564
+ const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort(bytewise);
579
565
  const configured = {
580
566
  configuration: projectRead.configuration,
581
567
  symbols: projectRead.held.symbols,
@@ -774,7 +760,7 @@ function sweptOf(read, input) {
774
760
  unanswered: read.unanswered,
775
761
  heldByUnanswered: [...held],
776
762
  skipped: read.projects.flatMap((one) => one.skipped),
777
- componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(compare),
763
+ componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(bytewise),
778
764
  };
779
765
  }
780
766
  /**
@@ -853,12 +839,7 @@ function judgedMembers(host, targetRoot, read) {
853
839
  }
854
840
  /** The directory of the nearest `package.json` at or above a file, inside `root`, else `root`. */
855
841
  function nearestManifestDir(host, root, file) {
856
- for (let dir = dirnamePath(file); relativePath(root, dir) !== undefined; dir = dirnamePath(dir)) {
857
- if (host.kindOf(joinPath(dir, "package.json")) === "file") {
858
- return dir;
859
- }
860
- }
861
- return root;
842
+ return ancestors(dirnamePath(file), root).find((dir) => holdsManifest(host, dir)) ?? root;
862
843
  }
863
844
  /**
864
845
  * The input every emitter reads over one sweep of a read run: the facts beside the sweep
@@ -922,7 +903,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
922
903
  id: one.configured.configuration,
923
904
  configFile: one.configFile,
924
905
  })),
925
- testFileRules: testFileRulesOf(config.ts.testFiles, read.projects.flatMap((one) => one.configured.testFiles), read.projects.flatMap((one) => one.configured.supportFiles ?? [])),
906
+ testFileRules: testFileRulesOf(config.ts.test_files, read.projects.flatMap((one) => one.configured.testFiles), read.projects.flatMap((one) => one.configured.supportFiles ?? [])),
926
907
  typeErrorSkips: read.projects.flatMap((one) => one.typeErrorSkips),
927
908
  componentWarnings: [
928
909
  ...new Map(read.projects
@@ -952,7 +933,7 @@ function inputsOver(host, scope, config, provenance, mode, read, documents) {
952
933
  };
953
934
  const facts = {
954
935
  refusals: suppressions.refusals,
955
- unmatchedRoots: unmatchedEverywhere(config.rootPatterns, read.projects.map((one) => one.extra.rooted)),
936
+ unmatchedRoots: unmatchedEverywhere(config.roots.patterns, read.projects.map((one) => one.extra.rooted)),
956
937
  rootsDocument: documentOf(host, provenance, ROOTS_SETTING, targetRoot),
957
938
  unmatchedDeclarations: configuredDeclarations(config)
958
939
  .filter((_, at) => !read.projects.some((one) => one.extra.declarationsNamed[at] === true))
@@ -5,7 +5,9 @@
5
5
  * declarations, and a use of one resolves to nothing; each such use is a use of the package.
6
6
  */
7
7
  import { isClassDeclaration, isEnumDeclaration, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, isStringLiteral, isTypeAliasDeclaration, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
8
- import { dirnamePath, joinPath, resolvePath } from "./paths.js";
8
+ import { ancestors, dirnamePath, joinPath, resolvePath } from "./paths.js";
9
+ import { entriesOf } from "./fs/tree.js";
10
+ import { manifestAt } from "./fs/manifest.js";
9
11
  const TYPE_ROOT = "node_modules/@types";
10
12
  const NONE = { globals: new Map(), modules: new Map() };
11
13
  /**
@@ -73,19 +75,11 @@ function fileDeclarations(file, globals, modules) {
73
75
  }
74
76
  /** The entry declaration file of one installed type package's directory. */
75
77
  function entryOf(host, dir) {
76
- try {
77
- const manifest = JSON.parse(host.readFile(joinPath(dir, "package.json")));
78
- const named = typeof manifest === "object" && manifest !== null
79
- ? (manifest["types"] ??
80
- manifest["typings"])
81
- : undefined;
82
- if (typeof named === "string" && named !== "") {
83
- const path = resolvePath(dir, named);
84
- return path.endsWith(".ts") ? path : `${path}.d.ts`;
85
- }
86
- }
87
- catch {
88
- // A package with no readable manifest is read from its index, as the compiler reads it.
78
+ const manifest = manifestAt(host, dir);
79
+ const named = manifest?.["types"] ?? manifest?.["typings"];
80
+ if (typeof named === "string" && named !== "") {
81
+ const path = resolvePath(dir, named);
82
+ return path.endsWith(".ts") ? path : `${path}.d.ts`;
89
83
  }
90
84
  return joinPath(dir, "index.d.ts");
91
85
  }
@@ -146,14 +140,7 @@ export function automaticTypes(host, configFile, options, parse) {
146
140
  };
147
141
  const read = new Set();
148
142
  for (const root of typeRoots(configFile, options)) {
149
- let entries;
150
- try {
151
- entries = host.readDirectory(root);
152
- }
153
- catch {
154
- entries = [];
155
- }
156
- for (const entry of entries) {
143
+ for (const entry of entriesOf(host, root)) {
157
144
  if (read.has(entry.name) || host.kindOf(joinPath(root, entry.name)) !== "directory") {
158
145
  continue;
159
146
  }
@@ -179,11 +166,5 @@ function typeRoots(configFile, options) {
179
166
  if (Array.isArray(listed)) {
180
167
  return listed.filter((root) => typeof root === "string" && root.endsWith(`/${TYPE_ROOT}`));
181
168
  }
182
- const roots = [];
183
- for (let dir = dirnamePath(configFile);; dir = dirnamePath(dir)) {
184
- roots.push(joinPath(dir, TYPE_ROOT));
185
- if (dirnamePath(dir) === dir) {
186
- return roots;
187
- }
188
- }
169
+ return ancestors(dirnamePath(configFile)).map((dir) => joinPath(dir, TYPE_ROOT));
189
170
  }
@@ -4,11 +4,15 @@
4
4
  * A script is read without running it, so a value that is not written out, a computed
5
5
  * path or an options object built at run time among them, names nothing.
6
6
  */
7
- import { isArrayBindingPattern, isArrayLiteralExpression, isArrowFunction, isAsExpression, isCallExpression, isConstructorDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isImportDeclaration, isMetaProperty, isMethodDeclaration, isNamedImports, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isSetAccessorDeclaration, isStringLiteral, isVariableDeclaration, isVariableStatement, NodeFlags, } from "@typescript/native/ast";
7
+ import { isArrayBindingPattern, isArrayLiteralExpression, isArrowFunction, isCallExpression, isConstructorDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isImportDeclaration, isMetaProperty, isMethodDeclaration, isNamedImports, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectBindingPattern, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSetAccessorDeclaration, isStringLiteral, isVariableDeclaration, isVariableStatement, NodeFlags, } from "@typescript/native/ast";
8
8
  import { filesNamedBy } from "./configuration-files.js";
9
- import { globExpression } from "./glob.js";
10
- import { scriptTokens } from "./manifest.js";
11
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
9
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.js";
10
+ import { bytewise } from "./text/compare.js";
11
+ import { isRecord } from "./json-document.js";
12
+ import { unwrapped } from "./unwrap.js";
13
+ import { holdsManifest, inInstalledPackages, manifestAt } from "./fs/manifest.js";
14
+ import { globExpression } from "./text/glob.js";
15
+ import { scriptTokens } from "./text/shell.js";
12
16
  /**
13
17
  * The bundlers whose build call names its entries: esbuild resolves `entryPoints` against
14
18
  * `absWorkingDir` (https://esbuild.github.io/api/#working-directory), webpack `entry`
@@ -27,18 +31,6 @@ const BUNDLERS = [
27
31
  { module: "rollup", calls: ["rollup", "watch"], option: "input", base: "", paths: true },
28
32
  ];
29
33
  const MODULE_EXTENSION = /\.(?:js|mjs|cjs|ts|mts|cts)$/u;
30
- const MANIFEST = "package.json";
31
- function isRecord(value) {
32
- return typeof value === "object" && value !== null && !Array.isArray(value);
33
- }
34
- /** The expression a wrapper that changes no value holds. */
35
- function unwrapped(node) {
36
- let at = node;
37
- while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
38
- at = at.expression;
39
- }
40
- return at;
41
- }
42
34
  function literalOf(node) {
43
35
  const at = unwrapped(node);
44
36
  return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
@@ -372,14 +364,7 @@ function scriptEntries(file, dir, files) {
372
364
  }
373
365
  /** The modules one manifest's scripts run, by absolute path, each below the target root. */
374
366
  function scriptModules(host, root, dir) {
375
- let manifest;
376
- try {
377
- manifest = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
378
- }
379
- catch {
380
- return [];
381
- }
382
- const scripts = isRecord(manifest) ? manifest["scripts"] : undefined;
367
+ const scripts = manifestAt(host, dir)?.["scripts"];
383
368
  if (!isRecord(scripts)) {
384
369
  return [];
385
370
  }
@@ -392,13 +377,13 @@ function scriptModules(host, root, dir) {
392
377
  const path = resolvePath(dir, token);
393
378
  if (MODULE_EXTENSION.test(path) &&
394
379
  relativePath(root, path) !== undefined &&
395
- !path.split("/").includes("node_modules") &&
380
+ !inInstalledPackages(path) &&
396
381
  host.kindOf(path) === "file") {
397
382
  found.add(path);
398
383
  }
399
384
  }
400
385
  }
401
- return [...found].sort();
386
+ return [...found].sort(bytewise);
402
387
  }
403
388
  /**
404
389
  * Every own file of one project a build script names as a bundler's entry: a module a
@@ -409,13 +394,16 @@ export function buildScriptEntries(host, targetRoot, files, parse) {
409
394
  const root = resolvePath(host.workingDirectory(), targetRoot);
410
395
  const dirs = new Set([root]);
411
396
  for (const file of files.byName.values()) {
412
- for (let dir = dirnamePath(file.fileName); relativePath(root, dir) !== undefined && !dirs.has(dir); dir = dirnamePath(dir)) {
397
+ for (const dir of ancestors(dirnamePath(file.fileName), root)) {
398
+ if (dirs.has(dir)) {
399
+ break;
400
+ }
413
401
  dirs.add(dir);
414
402
  }
415
403
  }
416
404
  const found = [];
417
- for (const dir of [...dirs].sort()) {
418
- if (host.kindOf(joinPath(dir, MANIFEST)) !== "file") {
405
+ for (const dir of [...dirs].sort(bytewise)) {
406
+ if (!holdsManifest(host, dir)) {
419
407
  continue;
420
408
  }
421
409
  for (const path of scriptModules(host, root, dir)) {
@@ -5,9 +5,11 @@
5
5
  * member wins over one it extends. The mapper resolves as a package linked into the
6
6
  * `node_modules` beside each configuration, to a directory holding only its manifest.
7
7
  */
8
+ import { INSTALLED_PACKAGES } from "./fs/manifest.js";
8
9
  import { dirnamePath, joinPath } from "./paths.js";
10
+ import { PRODUCT } from "./product.js";
9
11
  /** The name the mapper's manifest declares and every configuration's entry names. */
10
- const MAPPER_PACKAGE = "deadset-ts-component-files";
12
+ const MAPPER_PACKAGE = `${PRODUCT}-component-files`;
11
13
  /** The manifest of the mapper's package, which runs `command`. */
12
14
  export function mapperManifest(command) {
13
15
  return `${JSON.stringify({
@@ -122,7 +124,7 @@ export function componentLayer(host, configFiles, mapper, written = new Map()) {
122
124
  for (const configFile of configFiles) {
123
125
  files[configFile] = withContentMappers(written.get(configFile) ?? host.readFile(configFile), mappers);
124
126
  if (mapper !== undefined) {
125
- symlinks[joinPath(dirnamePath(configFile), "node_modules", MAPPER_PACKAGE)] = {
127
+ symlinks[joinPath(dirnamePath(configFile), INSTALLED_PACKAGES, MAPPER_PACKAGE)] = {
126
128
  target: mapper.packageDirectory,
127
129
  host: true,
128
130
  };
@@ -4,13 +4,12 @@
4
4
  * and every dead declaration that falls with them, being dead only through them.
5
5
  */
6
6
  import { OUTSIDE } from "./graph.js";
7
- /** The name every component identifier this analyzer mints starts with. */
8
- const ANALYZER = "deadset-ts";
7
+ import { PRODUCT } from "./product.js";
9
8
  /** The fewest digits a component identifier's number is written with. */
10
9
  const ID_DIGITS = 4;
11
10
  /** The identifier of the component at one place of the run's order, counted from one. */
12
11
  export function componentId(place) {
13
- return `${ANALYZER}/c-${String(place).padStart(ID_DIGITS, "0")}`;
12
+ return `${PRODUCT}/c-${String(place).padStart(ID_DIGITS, "0")}`;
14
13
  }
15
14
  /** The index of a declaration the component walk has not reached. */
16
15
  const UNVISITED = -1;
@@ -1,74 +1,18 @@
1
- import { CONTRACT_VERSION, } from "./contract/contract.generated.js";
1
+ import { SEVERITIES } from "./contract/contract.generated.js";
2
+ import { LANGUAGE } from "./product.js";
2
3
  /**
3
4
  * The name of the repository configuration at the target root, which is also the
4
5
  * document a finding about the configuration names where no document of the
5
6
  * target supplied the setting it is about.
6
7
  */
7
8
  export const REPOSITORY_DOCUMENT = "deadset.json";
8
- /** The kind a target kind setting gives one language's declarations. */
9
- export function kindOf(setting, language) {
10
- return typeof setting === "string" ? setting : setting[language];
9
+ /** The kind a target kind setting gives this analyzer's declarations. */
10
+ export function kindOf(kind) {
11
+ return typeof kind === "string" ? kind : kind[LANGUAGE];
11
12
  }
12
- /** The severities ranked, the strongest highest. */
13
- const SEVERITY_RANK = { allow: 0, warn: 1, deny: 2 };
14
13
  /** Whether a finding at one severity fails a run under the failing severity. */
15
14
  export function fails(severity, failOn) {
16
- return SEVERITY_RANK[severity] >= SEVERITY_RANK[failOn];
17
- }
18
- /**
19
- * The configuration every documented default states. `targetKind` is empty: the
20
- * field has no default and is never inferred.
21
- */
22
- export function defaultConfig() {
23
- return {
24
- contractVersion: CONTRACT_VERSION,
25
- targetKind: "",
26
- analysis: {
27
- languages: [],
28
- minConfidence: "probable",
29
- generatedFiles: "exclude",
30
- consumerTests: "test",
31
- configurations: [],
32
- matrixComplete: false,
33
- templateDirs: [],
34
- templateDelimiters: { left: "{{", right: "}}" },
35
- },
36
- consumersComplete: false,
37
- rootPatterns: [],
38
- severity: new Map(),
39
- exemptionsDisabled: [],
40
- reporters: {
41
- formats: ["text"],
42
- sort: "position",
43
- cascade: "roots",
44
- maxFindings: 0,
45
- failOn: "deny",
46
- },
47
- providers: {
48
- analyzers: [
49
- { shape: "installed", name: "deadset-go", languages: ["go"], command: "deadset-go" },
50
- { shape: "installed", name: "deadset-ts", languages: ["ts"], command: "deadset-ts" },
51
- ],
52
- },
53
- ts: {
54
- testFiles: [
55
- "**/*.test.{ts,tsx,mts,cts}",
56
- "**/*.test-d.{ts,tsx,mts,cts}",
57
- "**/*.spec-d.{ts,tsx,mts,cts}",
58
- "**/*.spec.*",
59
- "**/__tests__/**",
60
- "**/__mocks__/**",
61
- "**/test-d/**",
62
- ],
63
- entryFiles: [],
64
- componentExtensions: [".vue", ".svelte", ".astro"],
65
- disabledConventions: [],
66
- injectionRegistrations: [],
67
- lifecycleContracts: [],
68
- serializers: [],
69
- rootsComplete: false,
70
- },
71
- };
15
+ return SEVERITIES.indexOf(severity) >= SEVERITIES.indexOf(failOn);
72
16
  }
73
17
  /**
74
18
  * Renders the origin as the provenance value the Contract declares: `default`,
@@ -77,23 +21,3 @@ export function defaultConfig() {
77
21
  export function renderOrigin(origin) {
78
22
  return origin.source === "default" ? "default" : `${origin.source}: ${origin.label}`;
79
23
  }
80
- /**
81
- * Every refusal configuration resolution makes. Each maps to the usage exit
82
- * code, and each is thrown rather than returned: a refusal ends the run, and the
83
- * one caller that turns it into an exit code is the command line.
84
- */
85
- export class ConfigError extends Error {
86
- /** Which refusal this is. */
87
- kind;
88
- /**
89
- * The key or field the message names, spelled as the document spells it. It is
90
- * empty when the refusal names none.
91
- */
92
- key;
93
- constructor(kind, key, message) {
94
- super(message);
95
- this.name = "ConfigError";
96
- this.kind = kind;
97
- this.key = key;
98
- }
99
- }