@cplieger/deadset-ts 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +68 -50
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +230 -37
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +186 -0
  10. package/dist/src/conformance-record.js +2 -2
  11. package/dist/src/convention-rows.js +383 -0
  12. package/dist/src/conventions.js +456 -0
  13. package/dist/src/dependencies.js +30 -3
  14. package/dist/src/discover.js +11 -4
  15. package/dist/src/emit-map.js +72 -0
  16. package/dist/src/entry-point-gaps.js +81 -35
  17. package/dist/src/entry-points.js +47 -32
  18. package/dist/src/exempt.js +11 -3
  19. package/dist/src/findings/completion.js +19 -29
  20. package/dist/src/findings/emitters.js +22 -1
  21. package/dist/src/findings/unused-declarations.js +7 -2
  22. package/dist/src/graph.js +13 -5
  23. package/dist/src/inventory.js +4 -2
  24. package/dist/src/kinds.js +2 -2
  25. package/dist/src/manifest.js +37 -11
  26. package/dist/src/matrix.js +21 -13
  27. package/dist/src/partial.js +3 -1
  28. package/dist/src/pnpm-workspace.js +120 -0
  29. package/dist/src/position.js +17 -2
  30. package/dist/src/print.js +2 -0
  31. package/dist/src/references.js +209 -30
  32. package/dist/src/report.js +26 -6
  33. package/dist/src/reporters/annotations.js +10 -3
  34. package/dist/src/reporters/template.js +6 -3
  35. package/dist/src/resolve.js +31 -6
  36. package/dist/src/roots.js +37 -8
  37. package/dist/src/run.js +13 -3
  38. package/dist/src/schema.js +4 -2
  39. package/dist/src/session.js +141 -34
  40. package/dist/src/setup-failure.js +23 -0
  41. package/dist/src/source-files.js +4 -61
  42. package/dist/src/sweep.js +4 -2
  43. package/dist/src/template-field.js +38 -6
  44. package/dist/src/test-support.js +59 -0
  45. package/dist/src/type-errors.js +252 -0
  46. package/dist/src/type-query-alias.js +92 -0
  47. package/dist/src/verbs/analyze.js +24 -4
  48. package/dist/src/verbs/explain.js +4 -1
  49. package/dist/src/verbs/print-projects.js +17 -56
  50. package/dist/src/verbs/print-retained.js +7 -2
  51. package/dist/src/verbs/print-roots.js +7 -2
  52. package/dist/src/version-range.js +255 -0
  53. package/dist/src/version.js +1 -1
  54. package/dist/src/workspace-manifests.js +68 -0
  55. package/dist/src/workspace-programs.js +342 -0
  56. package/dist/src/workspace-resolution.js +307 -0
  57. package/dist/src/workspace-run.js +75 -0
  58. package/dist/src/workspace.js +358 -0
  59. package/jsr.json +1 -1
  60. package/package.json +2 -1
  61. package/src/analysis.ts +395 -71
  62. package/src/component-files.ts +718 -0
  63. package/src/component-layer.ts +160 -0
  64. package/src/config.ts +11 -2
  65. package/src/configuration-files.ts +235 -0
  66. package/src/conformance-record.ts +2 -2
  67. package/src/convention-rows.ts +440 -0
  68. package/src/conventions.ts +640 -0
  69. package/src/dependencies.ts +36 -2
  70. package/src/discover.ts +11 -7
  71. package/src/emit-map.ts +93 -0
  72. package/src/entry-point-gaps.ts +92 -38
  73. package/src/entry-points.ts +66 -36
  74. package/src/exempt.ts +21 -3
  75. package/src/findings/completion.ts +24 -33
  76. package/src/findings/emitters.ts +32 -1
  77. package/src/findings/unused-declarations.ts +9 -2
  78. package/src/graph.ts +15 -4
  79. package/src/host.ts +23 -0
  80. package/src/index.ts +1 -1
  81. package/src/inventory.ts +13 -2
  82. package/src/kinds.ts +2 -2
  83. package/src/manifest.ts +47 -14
  84. package/src/matrix.ts +43 -15
  85. package/src/partial.ts +6 -1
  86. package/src/pnpm-workspace.ts +135 -0
  87. package/src/position.ts +23 -2
  88. package/src/print.ts +2 -0
  89. package/src/references.ts +227 -28
  90. package/src/report.ts +56 -6
  91. package/src/reporters/annotations.ts +12 -3
  92. package/src/reporters/template.ts +6 -3
  93. package/src/resolve.ts +59 -6
  94. package/src/roots.ts +60 -9
  95. package/src/run.ts +15 -5
  96. package/src/schema.ts +4 -2
  97. package/src/session.ts +218 -37
  98. package/src/setup-failure.ts +39 -0
  99. package/src/source-files.ts +4 -66
  100. package/src/sweep.ts +9 -2
  101. package/src/template-field.ts +42 -7
  102. package/src/test-support.ts +77 -0
  103. package/src/type-errors.ts +340 -0
  104. package/src/type-query-alias.ts +133 -0
  105. package/src/verbs/analyze.ts +38 -4
  106. package/src/verbs/explain.ts +4 -1
  107. package/src/verbs/print-projects.ts +17 -69
  108. package/src/verbs/print-retained.ts +7 -2
  109. package/src/verbs/print-roots.ts +7 -2
  110. package/src/verbs/verb.ts +6 -3
  111. package/src/version-range.ts +309 -0
  112. package/src/version.ts +1 -1
  113. package/src/workspace-manifests.ts +100 -0
  114. package/src/workspace-programs.ts +486 -0
  115. package/src/workspace-resolution.ts +393 -0
  116. package/src/workspace-run.ts +148 -0
  117. package/src/workspace.ts +463 -0
@@ -0,0 +1,252 @@
1
+ /**
2
+ * What the errors a project's own files carry do to the run. An import of a module the
3
+ * project expects and nothing provides is a setup failure. Any other error in an own file
4
+ * is a type error: the function, method or file-level statement holding it is skipped,
5
+ * which only ever withholds a finding. An error the compiler places in no file is read as
6
+ * a project that cannot be analyzed.
7
+ */
8
+ import { isClassDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
9
+ import { packageOfSpecifier } from "./dependencies.js";
10
+ import { nodeKey } from "./inventory.js";
11
+ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
12
+ import { isAnswered } from "./query.js";
13
+ /** The compiler's codes for an import whose module it cannot find. */
14
+ const MODULE_NOT_FOUND = new Set([2307, 2792]);
15
+ const MANIFEST = "package.json";
16
+ const MANIFEST_SECTIONS = [
17
+ "dependencies",
18
+ "devDependencies",
19
+ "peerDependencies",
20
+ "optionalDependencies",
21
+ ];
22
+ /** Whether a finding's position falls inside a skipped unit. */
23
+ export function insideSkipped(skipped, position) {
24
+ return skipped.some((unit) => unit.path === position.path && unit.fromLine <= position.line && position.line <= unit.toLine);
25
+ }
26
+ /** The string literal holding one offset, innermost first. */
27
+ function literalAt(file, offset) {
28
+ let found;
29
+ const visit = (node) => {
30
+ if (offset < node.pos || offset >= node.end) {
31
+ return;
32
+ }
33
+ if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
34
+ found = node.text;
35
+ }
36
+ node.forEachChild(visit);
37
+ };
38
+ file.forEachChild(visit);
39
+ return found;
40
+ }
41
+ /** Whether one `paths` key matches one specifier: exactly, or around its one `*`. */
42
+ function mappedBy(key, specifier) {
43
+ const star = key.indexOf("*");
44
+ if (star < 0) {
45
+ return key === specifier;
46
+ }
47
+ const prefix = key.slice(0, star);
48
+ const suffix = key.slice(star + 1);
49
+ return (specifier.length >= prefix.length + suffix.length &&
50
+ specifier.startsWith(prefix) &&
51
+ specifier.endsWith(suffix));
52
+ }
53
+ /** Whether a relative import's path names something on disk, as written or with any extension. */
54
+ function provided(host, path) {
55
+ if (host.kindOf(path) !== "absent") {
56
+ return true;
57
+ }
58
+ const dir = dirnamePath(path);
59
+ const stem = `${path.slice(dir.length + 1)}.`;
60
+ try {
61
+ return host.readDirectory(dir).some((entry) => entry.name.startsWith(stem));
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /** The manifest at or above `dir`, no higher than the target root, that declares `name`. */
68
+ function declaringManifest(host, dir, targetRoot, name) {
69
+ for (let at = dir;; at = dirnamePath(at)) {
70
+ const path = joinPath(at, MANIFEST);
71
+ let manifest;
72
+ try {
73
+ manifest = JSON.parse(host.readFile(path));
74
+ }
75
+ catch {
76
+ manifest = undefined;
77
+ }
78
+ if (typeof manifest === "object" && manifest !== null) {
79
+ const sections = manifest;
80
+ for (const section of MANIFEST_SECTIONS) {
81
+ const declared = sections[section];
82
+ if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
83
+ return path;
84
+ }
85
+ }
86
+ }
87
+ if (at === targetRoot || relativePath(targetRoot, at) === undefined || dirnamePath(at) === at) {
88
+ return undefined;
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Why one import that the compiler cannot resolve names a module the project expects,
94
+ * with the fix, or undefined where the module is one the project does not expect.
95
+ */
96
+ function expectedModule(context, importer, specifier) {
97
+ const { host, targetRoot } = context;
98
+ const fix = "run the generator or the build that writes it";
99
+ if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
100
+ return provided(host, resolvePath(dirnamePath(importer), specifier))
101
+ ? undefined
102
+ : `which names no file of the target: ${fix}`;
103
+ }
104
+ if (specifier.startsWith("#")) {
105
+ return `which the manifest's imports map to no file: ${fix}`;
106
+ }
107
+ if (context.paths.some((key) => mappedBy(key, specifier))) {
108
+ return `which the compiler configuration's paths map to no file: ${fix}`;
109
+ }
110
+ const name = packageOfSpecifier(specifier);
111
+ const manifest = name === undefined
112
+ ? undefined
113
+ : declaringManifest(host, dirnamePath(importer), targetRoot, name);
114
+ if (manifest === undefined) {
115
+ return undefined;
116
+ }
117
+ const where = relativePath(targetRoot, manifest) ?? manifest;
118
+ return `a dependency ${where} declares that is not installed: run the package manager's install`;
119
+ }
120
+ /**
121
+ * The unit a type error at one offset skips: the class member holding it where a
122
+ * top-level class does, else the top-level statement.
123
+ */
124
+ function unitAt(file, offset) {
125
+ const statement = file.statements.find((one) => one.pos <= offset && offset < one.end);
126
+ if (statement === undefined || !isClassDeclaration(statement)) {
127
+ return statement;
128
+ }
129
+ return statement.members.find((one) => one.pos <= offset && offset < one.end) ?? statement;
130
+ }
131
+ /** The compiler's message on one line, the target root spelled relative to itself. */
132
+ function messageOf(text, targetRoot) {
133
+ const line = text.split(`${targetRoot}/`).join("").split(/\s+/u).join(" ").trim();
134
+ return line === "" ? "a type error" : line;
135
+ }
136
+ /**
137
+ * Reads the errors one project's own files carry. A diagnostic marking unnecessary code
138
+ * is no error here, and one in a file that is not the project's own is that file's
139
+ * package's to answer for.
140
+ */
141
+ export function readErrors(project, errors, context) {
142
+ const files = new Map(project.ownSourceFiles().map((file) => [file.fileName, file]));
143
+ const missing = [];
144
+ const skips = [];
145
+ const failing = [];
146
+ for (const located of errors) {
147
+ const { diagnostic, at } = located;
148
+ if (diagnostic.fileName === undefined) {
149
+ failing.push(located);
150
+ continue;
151
+ }
152
+ const file = files.get(diagnostic.fileName);
153
+ if (file === undefined || diagnostic.reportsUnnecessary === true) {
154
+ continue;
155
+ }
156
+ const path = relativePath(context.targetRoot, file.fileName) ?? file.fileName;
157
+ const line = at?.line ?? 1;
158
+ const specifier = MODULE_NOT_FOUND.has(diagnostic.code)
159
+ ? literalAt(file, diagnostic.pos)
160
+ : undefined;
161
+ const expected = specifier === undefined ? undefined : expectedModule(context, file.fileName, specifier);
162
+ if (specifier !== undefined && expected !== undefined) {
163
+ missing.push({
164
+ setupClass: "missing-module",
165
+ detail: `${path}:${String(line)}: ${path} imports ${JSON.stringify(specifier)}, ${expected}`,
166
+ });
167
+ continue;
168
+ }
169
+ const unit = unitAt(file, diagnostic.pos);
170
+ if (unit !== undefined) {
171
+ skips.push({
172
+ record: { path, line, message: messageOf(diagnostic.text, context.targetRoot) },
173
+ file,
174
+ unit,
175
+ });
176
+ }
177
+ }
178
+ return { missing, skips, failing };
179
+ }
180
+ /** The lines each skip withholds, below the target root. */
181
+ export function skippedUnits(skips, targetRoot) {
182
+ return skips.map(({ file, unit }) => ({
183
+ path: relativePath(targetRoot, file.fileName) ?? file.fileName,
184
+ fromLine: file.getLineAndCharacterOfPosition(unit.getStart()).line + 1,
185
+ toLine: file.getLineAndCharacterOfPosition(unit.end).line + 1,
186
+ }));
187
+ }
188
+ /**
189
+ * The declarations the skips keep live: every one a skipped unit declares, and every
190
+ * member of the type of each operand on which the unit selects a member the checker
191
+ * could not resolve, because the selection could have named any of them.
192
+ */
193
+ export function skippedRoots(project, held, skips) {
194
+ const kept = new Set();
195
+ const selections = [];
196
+ for (const { file, unit } of skips) {
197
+ const visit = (node) => {
198
+ const id = held.declarations.get(nodeKey(file, node));
199
+ if (id !== undefined) {
200
+ kept.add(id);
201
+ }
202
+ if (isPropertyAccessExpression(node)) {
203
+ selections.push(node);
204
+ }
205
+ node.forEachChild(visit);
206
+ };
207
+ visit(unit);
208
+ }
209
+ if (selections.length === 0) {
210
+ return [...kept];
211
+ }
212
+ const names = project.symbolsAt(selections.map((node) => project.handle(node.name)));
213
+ // A name the checker did not answer for may have named any member, as an unresolved one may.
214
+ const operands = selections.flatMap((node, at) => {
215
+ const name = names[at];
216
+ return name === undefined || !isAnswered(name) ? [node.expression] : [];
217
+ });
218
+ const own = project.ownPaths();
219
+ const keepMembers = (type) => {
220
+ const parts = type.isUnionType() ? project.queries.constituents(type) : [type];
221
+ for (const part of isAnswered(parts) ? parts : []) {
222
+ const properties = project.queries.propertiesOf(part);
223
+ for (const property of isAnswered(properties) ? properties : []) {
224
+ for (const handle of property.declarations) {
225
+ const node = own.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
226
+ const id = node === undefined
227
+ ? undefined
228
+ : held.declarations.get(nodeKey(node.getSourceFile(), node));
229
+ if (id !== undefined) {
230
+ kept.add(id);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ };
236
+ for (const type of project.queries.typesAt(operands)) {
237
+ if (type !== undefined && isAnswered(type)) {
238
+ keepMembers(type);
239
+ }
240
+ }
241
+ return [...kept];
242
+ }
243
+ /** The keys of one compiler configuration's `paths` mapping, none where it cannot be read. */
244
+ export function pathsOf(engine, configFile) {
245
+ try {
246
+ const paths = engine.parseConfigFile(configFile).options.paths;
247
+ return typeof paths === "object" && paths !== null ? Object.keys(paths) : [];
248
+ }
249
+ catch {
250
+ return [];
251
+ }
252
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The ambient globals a declaration file declares as an alias of a module's export:
3
+ * a variable whose type is `typeof import("<module>")["<name>"]` or
4
+ * `typeof import("<module>").<name>`. A use of the global is a use of the export,
5
+ * and the type query that says so is no use of its own.
6
+ */
7
+ import { isIdentifier, isImportTypeNode, isIndexedAccessTypeNode, isLiteralTypeNode, isModuleBlock, isModuleDeclaration, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ /** Whether one type is `typeof import("<string>")`, with no type argument. */
9
+ function typeOfImport(node) {
10
+ if (!isImportTypeNode(node) || !node.isTypeOf || node.typeArguments !== undefined) {
11
+ return false;
12
+ }
13
+ const argument = node.argument;
14
+ return isLiteralTypeNode(argument) && isStringLiteral(argument.literal);
15
+ }
16
+ /** The node naming the export one alias type selects, or undefined where the type is no alias. */
17
+ function exportedBy(type) {
18
+ if (isIndexedAccessTypeNode(type)) {
19
+ const index = type.indexType;
20
+ const object = type.objectType;
21
+ if (typeOfImport(object) &&
22
+ isImportTypeNode(object) &&
23
+ object.qualifier === undefined &&
24
+ isLiteralTypeNode(index) &&
25
+ isStringLiteral(index.literal)) {
26
+ return index.literal;
27
+ }
28
+ return undefined;
29
+ }
30
+ if (typeOfImport(type) && isImportTypeNode(type) && type.qualifier !== undefined) {
31
+ return isIdentifier(type.qualifier) ? type.qualifier : undefined;
32
+ }
33
+ return undefined;
34
+ }
35
+ /** Whether one module declaration is a `declare global` block. */
36
+ function isGlobalBlock(node) {
37
+ return (isModuleDeclaration(node) &&
38
+ node.keyword !== SyntaxKind.NamespaceKeyword &&
39
+ isIdentifier(node.name) &&
40
+ node.name.text === "global");
41
+ }
42
+ /** The aliases one list of statements declares as globals. */
43
+ function aliasesIn(statements, containers) {
44
+ const found = [];
45
+ for (const statement of statements) {
46
+ if (!isVariableStatement(statement)) {
47
+ continue;
48
+ }
49
+ for (const declaration of statement.declarationList.declarations) {
50
+ const type = declaration.type;
51
+ const exported = type === undefined ? undefined : exportedBy(type);
52
+ if (type === undefined ||
53
+ exported === undefined ||
54
+ !isIdentifier(declaration.name) ||
55
+ !(isIdentifier(exported) || isStringLiteral(exported))) {
56
+ continue;
57
+ }
58
+ found.push({ declaration, type, exported, name: exported.text, containers });
59
+ }
60
+ }
61
+ return found;
62
+ }
63
+ /**
64
+ * Every global one file declares as an alias of a module's export: at the top level of
65
+ * a declaration file that is no module, and in a `declare global` block written at the
66
+ * top level of a declaration file or of an ambient module it declares.
67
+ */
68
+ export function typeQueryAliases(file) {
69
+ if (!file.isDeclarationFile) {
70
+ return [];
71
+ }
72
+ const found = file.externalModuleIndicator === undefined ? aliasesIn(file.statements, []) : [];
73
+ const blocks = (statements, outer) => {
74
+ for (const statement of statements) {
75
+ if (!isModuleDeclaration(statement) || statement.body === undefined) {
76
+ continue;
77
+ }
78
+ const body = statement.body;
79
+ if (!isModuleBlock(body)) {
80
+ continue;
81
+ }
82
+ if (isGlobalBlock(statement)) {
83
+ found.push(...aliasesIn(body.statements, [...outer, statement]));
84
+ }
85
+ else if (!isIdentifier(statement.name)) {
86
+ blocks(body.statements, [statement]);
87
+ }
88
+ }
89
+ };
90
+ blocks(file.statements, []);
91
+ return found;
92
+ }
@@ -1,4 +1,4 @@
1
- import { runAnalysis, runBaselineWrite } from "../analysis.js";
1
+ import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
2
2
  import { ConfigError } from "../config.js";
3
3
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
4
4
  import { moduleIdentity } from "../consumers.js";
@@ -160,22 +160,30 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
160
160
  let recorded;
161
161
  let verdict;
162
162
  let partial;
163
+ let warnings;
164
+ let unread;
163
165
  try {
164
166
  const root = runRelative(invoked, targetRoot);
165
167
  const mode = { production: true };
166
168
  let analysis;
167
169
  if (documents.baseline === undefined) {
168
- analysis = runAnalysis(openClient(false), host, scoped, config, provenance, mode);
170
+ analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
169
171
  recorded = [];
170
172
  }
171
173
  else {
172
174
  const reason = rowReason(analyzerProvenance(version));
173
- const write = runBaselineWrite(openClient(false), host, scoped, config, provenance, mode, reason);
175
+ const write = runBaselineWrite(openClient(config), host, scoped, config, provenance, mode, reason);
174
176
  analysis = write.first;
175
177
  recorded = write.rows;
176
178
  }
177
179
  const { result, run } = analysis;
178
- partial = partialNotes({ notBuilt: run.notBuilt, unanswered: run.unanswered });
180
+ warnings = run.componentWarnings;
181
+ unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
182
+ partial = partialNotes({
183
+ notBuilt: run.notBuilt,
184
+ unanswered: run.unanswered,
185
+ targetRoot: resolvePath(invoked, targetRoot),
186
+ });
179
187
  const built = buildReport({
180
188
  contractVersion: CONTRACT_VERSION,
181
189
  version,
@@ -191,6 +199,9 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
191
199
  unavailable: [],
192
200
  result,
193
201
  testFileRules: run.testFileRules,
202
+ typeErrorSkips: run.typeErrorSkips,
203
+ unanswered: unansweredCounts(analysis),
204
+ conventionsApplied: run.conventionsApplied,
194
205
  });
195
206
  report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
196
207
  verdict = verdictOf(result, config);
@@ -226,6 +237,15 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
226
237
  for (const note of partial) {
227
238
  err.write(`deadset-ts: ${note}\n`);
228
239
  }
240
+ for (const line of unread) {
241
+ err.write(`deadset-ts: ${line}\n`);
242
+ }
243
+ for (const warning of warnings) {
244
+ err.write(`deadset-ts: ${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}\n`);
245
+ }
246
+ for (const skip of report.type_error_skips) {
247
+ err.write(`deadset-ts: ${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated\n`);
248
+ }
229
249
  if (report.totals.pending > 0) {
230
250
  err.write(`deadset-ts: ${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it\n`);
231
251
  }
@@ -4,6 +4,7 @@ import { ConfigError } from "../config.js";
4
4
  import { OUTSIDE, swept as judged } from "../graph.js";
5
5
  import { referenceKey } from "../matrix.js";
6
6
  import { partialNotes } from "../partial.js";
7
+ import { resolvePath } from "../paths.js";
7
8
  import { positionKey } from "../position.js";
8
9
  import { resolve } from "../resolve.js";
9
10
  import { EXIT_CLEAN, EXIT_USAGE } from "./verb.js";
@@ -303,13 +304,15 @@ function explanation(analysis, subject, cascade) {
303
304
  export const explainVerb = ({ out, err, host, inputs, scope, openClient, option }) => {
304
305
  const named = namedSymbol(option);
305
306
  const { config, provenance } = resolve(inputs());
306
- const analysis = runAnalysis(openClient(false), host, scope(), config, provenance, {
307
+ const scoped = scope();
308
+ const analysis = runAnalysis(openClient(config), host, scoped, config, provenance, {
307
309
  production: true,
308
310
  });
309
311
  const { subject, candidates } = subjectOf(analysis.swept.matrix.union.symbols, named);
310
312
  const notes = partialNotes({
311
313
  notBuilt: analysis.run.notBuilt,
312
314
  unanswered: analysis.run.unanswered,
315
+ targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
313
316
  });
314
317
  if (subject === undefined) {
315
318
  err.write(`deadset-ts: ${JSON.stringify(named)} names no one symbol of the target\n`);
@@ -1,66 +1,27 @@
1
- import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "../discover.js";
1
+ import { runProjects } from "../analysis.js";
2
2
  import { partialNotes } from "../partial.js";
3
- import { relativePath, resolvePath } from "../paths.js";
4
- import { resolveMatrix } from "../resolve.js";
5
- import { diagnosticsOf, runSession } from "../session.js";
6
- import { EXIT_CLEAN, EXIT_FAILURE } from "./verb.js";
3
+ import { resolvePath } from "../paths.js";
4
+ import { resolveUnkinded } from "../resolve.js";
5
+ import { EXIT_CLEAN } from "./verb.js";
7
6
  /**
8
- * Writes the name of every project one run analyzes, one per line, having read each
9
- * project's diagnostics first: the identifier the build matrix declares for it, or,
10
- * where discovery derived the project, its configuration file's path below the
11
- * target root. A derived project that does not load is not one the run analyzes, so it
12
- * is named on the error stream instead, as the analysis drops it.
7
+ * Writes the name of every project one run analyzes, one per line: the identifier the
8
+ * build matrix declares for it, or, where discovery derived the project, its
9
+ * configuration file's path below the target root. The projects are read as the
10
+ * analysis reads them, so a project the analysis drops is named on the error stream
11
+ * instead, and one that fails the analysis fails this command the same way.
13
12
  */
14
13
  export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient }) => {
15
- const matrix = resolveMatrix(inputs());
14
+ const config = resolveUnkinded(inputs());
16
15
  const scoped = scope();
17
- const engine = openClient(false);
18
- const discovered = discoverProjects(engine, host, scoped, matrix);
19
- const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
20
- const root = resolvePath(host.workingDirectory(), scoped.target.path);
21
- const notBuilt = [...discovered.notBuilt];
22
- const failures = [];
23
- const session = runSession(engine, discovered.configFiles, (project) => {
24
- const id = ids.get(project.configFile) ?? project.configFile;
25
- const errors = projectErrors(diagnosticsOf(project));
26
- const [unloaded] = errors.load;
27
- if (unloaded !== undefined && isGuess(discovered, project.configFile)) {
28
- const error = renderDiagnostic(unloaded, (path) => relativePath(root, path) ?? path);
29
- notBuilt.push({ id, configFile: project.configFile, error: targetRelative(error, root) });
30
- return undefined;
31
- }
32
- failures.push(...[...errors.load, ...errors.check].map((one) => renderDiagnostic(one)));
33
- return id;
34
- }, discovered.derived ? "record" : "refuse");
35
- for (const configFile of session.unopened) {
36
- if (!isGuess(discovered, configFile)) {
37
- throw new DiscoveryError(`${configFile} opened no project`, []);
38
- }
39
- notBuilt.push({
40
- id: ids.get(configFile) ?? configFile,
41
- configFile,
42
- error: "the compiler opened no project for the configuration",
43
- });
44
- }
45
- if (failures.length > 0) {
46
- for (const line of failures) {
47
- err.write(`${line}\n`);
48
- }
49
- err.write(`deadset-ts: ${String(failures.length)} error(s); no answer was produced\n`);
50
- return EXIT_FAILURE;
51
- }
52
- const projects = session.projects.filter((id) => id !== undefined);
53
- if (projects.length === 0 && notBuilt.length > 0) {
54
- for (const note of partialNotes({ notBuilt, unanswered: [] })) {
55
- err.write(`deadset-ts: ${note}\n`);
56
- }
57
- err.write("deadset-ts: no configuration discovery derived could be built\n");
58
- return EXIT_FAILURE;
59
- }
60
- for (const id of projects) {
16
+ const answer = runProjects(openClient(config), host, scoped, config);
17
+ for (const id of answer.configurations) {
61
18
  out.write(`${id}\n`);
62
19
  }
63
- for (const note of partialNotes({ notBuilt, unanswered: [] })) {
20
+ for (const note of partialNotes({
21
+ notBuilt: answer.notBuilt,
22
+ unanswered: answer.unanswered,
23
+ targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
24
+ })) {
64
25
  err.write(`deadset-ts: ${note}\n`);
65
26
  }
66
27
  return EXIT_CLEAN;
@@ -1,6 +1,7 @@
1
1
  import { runSweep } from "../analysis.js";
2
2
  import { retainedLines } from "../exempt.js";
3
3
  import { partialNotes } from "../partial.js";
4
+ import { resolvePath } from "../paths.js";
4
5
  import { resolve } from "../resolve.js";
5
6
  import { EXIT_CLEAN } from "./verb.js";
6
7
  /**
@@ -13,14 +14,18 @@ import { EXIT_CLEAN } from "./verb.js";
13
14
  export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient }) => {
14
15
  const { config } = resolve(inputs());
15
16
  const scoped = scope();
16
- const swept = runSweep(openClient(false), host, scoped, config, {
17
+ const swept = runSweep(openClient(config), host, scoped, config, {
17
18
  marked: [],
18
19
  mode: { production: true },
19
20
  });
20
21
  for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
21
22
  out.write(`${line}\n`);
22
23
  }
23
- for (const note of partialNotes({ notBuilt: swept.notBuilt, unanswered: swept.unanswered })) {
24
+ for (const note of partialNotes({
25
+ notBuilt: swept.notBuilt,
26
+ unanswered: swept.unanswered,
27
+ targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
28
+ })) {
24
29
  err.write(`deadset-ts: ${note}\n`);
25
30
  }
26
31
  return EXIT_CLEAN;
@@ -1,5 +1,6 @@
1
1
  import { runRoots } from "../analysis.js";
2
2
  import { partialNotes } from "../partial.js";
3
+ import { resolvePath } from "../paths.js";
3
4
  import { resolve } from "../resolve.js";
4
5
  import { EXIT_CLEAN, EXIT_FINDINGS } from "./verb.js";
5
6
  /**
@@ -25,7 +26,7 @@ function rootLine(root, several) {
25
26
  export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) => {
26
27
  const { config, provenance } = resolve(inputs());
27
28
  const scoped = scope();
28
- const answer = runRoots(openClient(false), host, scoped, config, provenance);
29
+ const answer = runRoots(openClient(config), host, scoped, config, provenance);
29
30
  const several = answer.configurations.length > 1;
30
31
  for (const root of answer.roots) {
31
32
  out.write(`${rootLine(root, several)}\n`);
@@ -33,7 +34,11 @@ export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) =>
33
34
  for (const finding of answer.findings) {
34
35
  err.write(`${finding.code}: roots.patterns names nothing: ${finding.symbol.ref}\n`);
35
36
  }
36
- for (const note of partialNotes({ notBuilt: answer.notBuilt, unanswered: answer.unanswered })) {
37
+ for (const note of partialNotes({
38
+ notBuilt: answer.notBuilt,
39
+ unanswered: answer.unanswered,
40
+ targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
41
+ })) {
37
42
  err.write(`deadset-ts: ${note}\n`);
38
43
  }
39
44
  return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;