@cplieger/deadset-ts 4.0.1 → 5.1.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 (138) hide show
  1. package/README.md +4 -4
  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 +301 -41
  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 +290 -0
  10. package/dist/src/conformance-record.js +3 -9
  11. package/dist/src/contextual-uses.js +240 -0
  12. package/dist/src/convention-rows.js +389 -0
  13. package/dist/src/conventions.js +565 -0
  14. package/dist/src/dependencies.js +36 -3
  15. package/dist/src/destructuring.js +6 -6
  16. package/dist/src/discover.js +10 -3
  17. package/dist/src/emit-map.js +72 -0
  18. package/dist/src/entry-point-gaps.js +81 -35
  19. package/dist/src/entry-points.js +50 -32
  20. package/dist/src/exempt-classes.js +6 -1
  21. package/dist/src/exempt.js +32 -20
  22. package/dist/src/findings/completion.js +46 -32
  23. package/dist/src/findings/emitters.js +22 -1
  24. package/dist/src/findings/interfaces.js +6 -2
  25. package/dist/src/findings/reads-and-writes.js +53 -9
  26. package/dist/src/findings/unused-declarations.js +47 -3
  27. package/dist/src/generated-file.js +24 -0
  28. package/dist/src/graph.js +13 -5
  29. package/dist/src/interface-satisfaction.js +2 -68
  30. package/dist/src/inventory.js +8 -3
  31. package/dist/src/kinds.js +6 -5
  32. package/dist/src/manifest.js +37 -11
  33. package/dist/src/matrix.js +21 -13
  34. package/dist/src/partial.js +3 -1
  35. package/dist/src/pnpm-workspace.js +120 -0
  36. package/dist/src/position.js +17 -2
  37. package/dist/src/print.js +2 -0
  38. package/dist/src/query.js +15 -0
  39. package/dist/src/references.js +404 -36
  40. package/dist/src/report.js +26 -6
  41. package/dist/src/reporters/annotations.js +10 -3
  42. package/dist/src/reporters/template.js +3 -0
  43. package/dist/src/resolve.js +37 -11
  44. package/dist/src/roots.js +37 -8
  45. package/dist/src/run.js +13 -3
  46. package/dist/src/schema.js +2 -0
  47. package/dist/src/session.js +141 -34
  48. package/dist/src/setup-failure.js +23 -0
  49. package/dist/src/source-files.js +4 -61
  50. package/dist/src/stores.js +3 -0
  51. package/dist/src/sweep.js +4 -2
  52. package/dist/src/template-field.js +38 -6
  53. package/dist/src/test-support.js +59 -0
  54. package/dist/src/type-errors.js +252 -0
  55. package/dist/src/type-query-alias.js +92 -0
  56. package/dist/src/value-flow.js +74 -0
  57. package/dist/src/verbs/analyze.js +24 -4
  58. package/dist/src/verbs/explain.js +4 -1
  59. package/dist/src/verbs/print-projects.js +17 -56
  60. package/dist/src/verbs/print-retained.js +7 -2
  61. package/dist/src/verbs/print-roots.js +7 -2
  62. package/dist/src/version-range.js +255 -0
  63. package/dist/src/version.js +1 -1
  64. package/dist/src/workspace-manifests.js +68 -0
  65. package/dist/src/workspace-programs.js +342 -0
  66. package/dist/src/workspace-resolution.js +307 -0
  67. package/dist/src/workspace-run.js +75 -0
  68. package/dist/src/workspace.js +358 -0
  69. package/jsr.json +1 -1
  70. package/package.json +3 -2
  71. package/src/analysis.ts +475 -72
  72. package/src/component-files.ts +718 -0
  73. package/src/component-layer.ts +160 -0
  74. package/src/config.ts +11 -2
  75. package/src/configuration-files.ts +370 -0
  76. package/src/conformance-record.ts +3 -10
  77. package/src/contextual-uses.ts +354 -0
  78. package/src/convention-rows.ts +448 -0
  79. package/src/conventions.ts +788 -0
  80. package/src/dependencies.ts +49 -2
  81. package/src/destructuring.ts +16 -6
  82. package/src/discover.ts +10 -6
  83. package/src/emit-map.ts +93 -0
  84. package/src/entry-point-gaps.ts +92 -38
  85. package/src/entry-points.ts +79 -36
  86. package/src/exempt-classes.ts +6 -1
  87. package/src/exempt.ts +69 -20
  88. package/src/file-facts.ts +2 -0
  89. package/src/findings/completion.ts +55 -36
  90. package/src/findings/emitters.ts +32 -1
  91. package/src/findings/interfaces.ts +9 -2
  92. package/src/findings/reads-and-writes.ts +70 -9
  93. package/src/findings/unused-declarations.ts +60 -3
  94. package/src/generated-file.ts +27 -0
  95. package/src/graph.ts +15 -4
  96. package/src/host.ts +23 -0
  97. package/src/index.ts +1 -1
  98. package/src/interface-satisfaction.ts +1 -92
  99. package/src/inventory.ts +17 -3
  100. package/src/kinds.ts +7 -5
  101. package/src/manifest.ts +47 -14
  102. package/src/matrix.ts +43 -15
  103. package/src/partial.ts +6 -1
  104. package/src/pnpm-workspace.ts +135 -0
  105. package/src/position.ts +23 -2
  106. package/src/print.ts +2 -0
  107. package/src/query.ts +26 -0
  108. package/src/references.ts +472 -40
  109. package/src/report.ts +56 -6
  110. package/src/reporters/annotations.ts +12 -3
  111. package/src/reporters/template.ts +3 -0
  112. package/src/resolve.ts +65 -11
  113. package/src/roots.ts +70 -9
  114. package/src/run.ts +15 -5
  115. package/src/schema.ts +2 -0
  116. package/src/session.ts +218 -37
  117. package/src/setup-failure.ts +39 -0
  118. package/src/source-files.ts +4 -66
  119. package/src/stores.ts +9 -0
  120. package/src/sweep.ts +9 -2
  121. package/src/template-field.ts +42 -7
  122. package/src/test-support.ts +77 -0
  123. package/src/type-errors.ts +340 -0
  124. package/src/type-query-alias.ts +133 -0
  125. package/src/value-flow.ts +104 -0
  126. package/src/verbs/analyze.ts +38 -4
  127. package/src/verbs/explain.ts +4 -1
  128. package/src/verbs/print-projects.ts +17 -69
  129. package/src/verbs/print-retained.ts +7 -2
  130. package/src/verbs/print-roots.ts +7 -2
  131. package/src/verbs/verb.ts +6 -3
  132. package/src/version-range.ts +309 -0
  133. package/src/version.ts +1 -1
  134. package/src/workspace-manifests.ts +100 -0
  135. package/src/workspace-programs.ts +486 -0
  136. package/src/workspace-resolution.ts +393 -0
  137. package/src/workspace-run.ts +148 -0
  138. package/src/workspace.ts +463 -0
package/src/report.ts CHANGED
@@ -13,18 +13,16 @@ import type {
13
13
  FindingPosition,
14
14
  PositionedSymbol,
15
15
  } from "./finding.ts";
16
+ import type { AppliedConvention } from "./conventions.ts";
16
17
  import type { PassResult } from "./findings-pass.ts";
17
18
  import type { StaleSuppression } from "./findings/self-check.ts";
18
19
  import type { TestFileRule } from "./references.ts";
19
20
 
20
21
  /** The version of the report schema this analyzer writes a report to. */
21
- const SCHEMA_VERSION = "6.1.0";
22
+ const SCHEMA_VERSION = "7.0.0";
22
23
 
23
- /**
24
- * Every report schema version this analyzer reads: the schema versions the Contract it
25
- * implements admits, each an instance of the one it writes.
26
- */
27
- export const SCHEMA_VERSIONS_ACCEPTED: readonly string[] = ["6.0.0", SCHEMA_VERSION];
24
+ /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
25
+ export const SCHEMA_VERSIONS_ACCEPTED: readonly string[] = [SCHEMA_VERSION];
28
26
 
29
27
  /** The name this analyzer writes every document under and mints every identifier with. */
30
28
  export const ANALYZER_NAME = "deadset-ts";
@@ -151,6 +149,22 @@ export interface ReportNotBuilt {
151
149
  readonly error: string;
152
150
  }
153
151
 
152
+ /** One type error that skipped the function or statement holding it. */
153
+ export interface TypeErrorSkip {
154
+ /** The file holding the error, below the target root. */
155
+ readonly path: string;
156
+ readonly line: number;
157
+ /** The compiler's message, on one line. */
158
+ readonly message: string;
159
+ }
160
+
161
+ /** One configuration in which the checker left questions unanswered, with what they held. */
162
+ export interface UnansweredCount {
163
+ readonly configuration: string;
164
+ readonly questions: number;
165
+ readonly declarations: number;
166
+ }
167
+
154
168
  /** One consumer the run loaded, at the directory a report names it by. */
155
169
  interface ReportConsumer {
156
170
  readonly id: string;
@@ -201,6 +215,10 @@ export interface Report {
201
215
  readonly declared_gaps: readonly WireDeclaredGap[];
202
216
  readonly excluded_by_cgo: readonly never[];
203
217
  readonly test_file_rules: readonly TestFileRule[];
218
+ readonly type_error_skips: readonly TypeErrorSkip[];
219
+ readonly notes: readonly never[];
220
+ readonly unanswered_questions: readonly UnansweredCount[];
221
+ readonly conventions_applied: readonly AppliedConvention[];
204
222
  readonly totals: WireTotals;
205
223
  }
206
224
 
@@ -217,6 +235,10 @@ export interface ReportInput {
217
235
  readonly unavailable: readonly UnavailableConsumer[];
218
236
  readonly result: PassResult;
219
237
  readonly testFileRules: readonly TestFileRule[];
238
+ readonly typeErrorSkips: readonly TypeErrorSkip[];
239
+ readonly unanswered: readonly UnansweredCount[];
240
+ /** The convention rows the run applied, in the report's order. */
241
+ readonly conventionsApplied: readonly AppliedConvention[];
220
242
  }
221
243
 
222
244
  /** Two strings ordered bytewise. */
@@ -462,6 +484,34 @@ export function buildReport(input: ReportInput): Report {
462
484
  test_file_rules: [...input.testFileRules]
463
485
  .sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
464
486
  .map((one) => ({ rule: one.rule, matched: one.matched })),
487
+ type_error_skips: [
488
+ ...new Map(
489
+ input.typeErrorSkips.map((one) => {
490
+ const skip = { path: one.path, line: one.line, message: one.message };
491
+ return [JSON.stringify(skip), skip] as const;
492
+ }),
493
+ ),
494
+ ]
495
+ .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
496
+ .map(([, skip]) => skip),
497
+ notes: [],
498
+ unanswered_questions: [...input.unanswered]
499
+ .sort(
500
+ (a, b) =>
501
+ compare(a.configuration, b.configuration) ||
502
+ compare(JSON.stringify(a), JSON.stringify(b)),
503
+ )
504
+ .map((one) => ({
505
+ configuration: one.configuration,
506
+ questions: one.questions,
507
+ declarations: one.declarations,
508
+ })),
509
+ conventions_applied: input.conventionsApplied.map((one) => ({
510
+ name: one.name,
511
+ package: one.package,
512
+ version: one.version,
513
+ manifest: one.manifest,
514
+ })),
465
515
  totals: totalsOf(findings, input.result),
466
516
  };
467
517
  }
@@ -37,11 +37,17 @@ function command(
37
37
  return `::${level} file=${escapeProperty(at.path)},line=${String(at.line)},col=${String(at.column)},endLine=${String(endLine)},title=${escapeProperty(title)}::${escapeData(message)}`;
38
38
  }
39
39
 
40
+ /** The title and the message prefix of a type-error skip's annotation. */
41
+ const SKIP_TITLE = "type error skipped";
42
+ const SKIP_MESSAGE =
43
+ "the analysis did not evaluate the function or statement holding this type error: ";
44
+
40
45
  /**
41
46
  * One workflow annotation per finding, then one per stale suppression, each naming its
42
- * file, line, column and last line, titled by its code and kind. A finding at or above the
43
- * failing severity is an error and one below it a warning; a stale suppression is an error,
44
- * because its kind is fixed on at the failing severity.
47
+ * file, line, column and last line, titled by its code and kind, then one warning per
48
+ * type-error skip naming its file and line. A finding at or above the failing severity is
49
+ * an error and one below it a warning; a stale suppression is an error, because its kind
50
+ * is fixed on at the failing severity.
45
51
  */
46
52
  export function annotations(report: Report, options: RenderOptions): Iterable<string> {
47
53
  return coalesced(
@@ -64,6 +70,9 @@ export function annotations(report: Report, options: RenderOptions): Iterable<st
64
70
  stale.message,
65
71
  )}\n`;
66
72
  }
73
+ for (const skip of report.type_error_skips) {
74
+ yield `::warning file=${escapeProperty(skip.path)},line=${String(skip.line)},title=${escapeProperty(SKIP_TITLE)}::${escapeData(SKIP_MESSAGE + skip.message)}\n`;
75
+ }
67
76
  })(),
68
77
  );
69
78
  }
@@ -925,6 +925,9 @@ class Execution {
925
925
  if (head === undefined) {
926
926
  throw new RenderError("empty command");
927
927
  }
928
+ if (head.k === "lit" && head.value === null) {
929
+ throw new RenderError("nil is not a command");
930
+ }
928
931
  if (head.k !== "func") {
929
932
  if (rest.length > 0 || piped !== undefined) {
930
933
  throw new RenderError("cannot give an argument to a non-function");
package/src/resolve.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  type TargetKind,
22
22
  type TemplateDelimiters,
23
23
  } from "./config.ts";
24
+ import { CONVENTION_ROWS } from "./convention-rows.ts";
24
25
  import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.ts";
25
26
  import {
26
27
  checkDocument,
@@ -105,6 +106,8 @@ interface Doc {
105
106
  readonly providers: readonly Provider[] | undefined;
106
107
  readonly testFiles: readonly string[] | undefined;
107
108
  readonly entryFiles: readonly string[] | undefined;
109
+ readonly componentExtensions: readonly string[] | undefined;
110
+ readonly disabledConventions: readonly string[] | undefined;
108
111
  readonly injectionRegistrations: readonly DeclarationEntry[] | undefined;
109
112
  readonly lifecycleContracts: readonly LifecycleContract[] | undefined;
110
113
  readonly serializers: readonly DeclarationEntry[] | undefined;
@@ -259,24 +262,63 @@ function readStrings<T extends string>(
259
262
  );
260
263
  }
261
264
  const seen = new Set<string>();
262
- for (const entry of value as unknown[]) {
265
+ for (const [index, entry] of (value as unknown[]).entries()) {
266
+ const at = `${path}[${String(index)}]`;
263
267
  if (typeof entry !== "string") {
264
- throw malformed(label, path, "holds an entry that is not a string");
268
+ throw malformed(label, at, "is not a string");
265
269
  }
266
270
  if (entry === "") {
267
- throw malformed(label, path, "holds an empty entry");
271
+ throw malformed(label, at, "is empty");
268
272
  }
269
273
  if (seen.has(entry)) {
270
- throw malformed(label, path, `names ${JSON.stringify(entry)} twice`);
274
+ throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
271
275
  }
272
276
  if (allowed !== undefined && !(allowed as readonly string[]).includes(entry)) {
273
- throw malformed(label, path, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
277
+ throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
274
278
  }
275
279
  seen.add(entry);
276
280
  }
277
281
  return value as readonly T[];
278
282
  }
279
283
 
284
+ /** A component file's extension: a full stop followed by letters and digits. */
285
+ const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
286
+
287
+ function readComponentExtensions(
288
+ ts: Record<string, unknown> | undefined,
289
+ label: string,
290
+ ): readonly string[] | undefined {
291
+ const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
292
+ const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
293
+ if (extensions !== undefined && wrong !== -1) {
294
+ throw malformed(
295
+ label,
296
+ `ts.component_extensions[${String(wrong)}]`,
297
+ `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`,
298
+ );
299
+ }
300
+ return extensions;
301
+ }
302
+
303
+ /** The names of the convention rows this analyzer's table carries. */
304
+ const CONVENTION_NAMES: ReadonlySet<string> = new Set(CONVENTION_ROWS.map((row) => row.name));
305
+
306
+ function readDisabledConventions(
307
+ ts: Record<string, unknown> | undefined,
308
+ label: string,
309
+ ): readonly string[] | undefined {
310
+ const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
311
+ const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
312
+ if (names !== undefined && unknown !== -1) {
313
+ throw malformed(
314
+ label,
315
+ `ts.disabled_conventions[${String(unknown)}]`,
316
+ `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`,
317
+ );
318
+ }
319
+ return names;
320
+ }
321
+
280
322
  function requireMember(value: string | undefined, path: string, label: string): string {
281
323
  if (value === undefined || value === "") {
282
324
  throw malformed(label, path, "is required and names nothing");
@@ -792,6 +834,8 @@ function readDoc(value: unknown, label: string): Doc {
792
834
  providers: readProviders(providers, label),
793
835
  testFiles: readStrings(ts, "test_files", "ts.test_files", label, 1),
794
836
  entryFiles: readStrings(ts, "entry_files", "ts.entry_files", label, 0),
837
+ componentExtensions: readComponentExtensions(ts, label),
838
+ disabledConventions: readDisabledConventions(ts, label),
795
839
  injectionRegistrations: readDeclarations(
796
840
  ts,
797
841
  "injection_registrations",
@@ -986,13 +1030,13 @@ export function resolve(inputs: Inputs): { config: Config; provenance: Provenanc
986
1030
  }
987
1031
 
988
1032
  /**
989
- * The build matrix the configuration documents resolve to, every document read and
990
- * refused as {@link resolve} refuses it, and nothing else required of them: which
991
- * projects a run analyzes depends on the matrix alone, so a question about the
992
- * projects asks no target kind.
1033
+ * The configuration the documents resolve to, every document read and refused as
1034
+ * {@link resolve} refuses it, with no target kind required of them: which projects a
1035
+ * run analyzes does not depend on the kind, so a question about the projects asks
1036
+ * none. The kind is empty where no document names one.
993
1037
  */
994
- export function resolveMatrix(inputs: Inputs): readonly BuildConfiguration[] {
995
- return settleAll(inputs).config.analysis.configurations;
1038
+ export function resolveUnkinded(inputs: Inputs): Config {
1039
+ return settleAll(inputs).config;
996
1040
  }
997
1041
 
998
1042
  /**
@@ -1076,6 +1120,16 @@ function settleAll(inputs: Inputs): { config: Config; provenance: Map<string, Or
1076
1120
  ts: {
1077
1121
  testFiles: at("ts.test_files", defaults.ts.testFiles, (doc) => doc.testFiles),
1078
1122
  entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
1123
+ componentExtensions: at(
1124
+ "ts.component_extensions",
1125
+ defaults.ts.componentExtensions,
1126
+ (doc) => doc.componentExtensions,
1127
+ ),
1128
+ disabledConventions: at(
1129
+ "ts.disabled_conventions",
1130
+ defaults.ts.disabledConventions,
1131
+ (doc) => doc.disabledConventions,
1132
+ ),
1079
1133
  injectionRegistrations: at(
1080
1134
  "ts.injection_registrations",
1081
1135
  defaults.ts.injectionRegistrations,
package/src/roots.ts CHANGED
@@ -10,29 +10,44 @@
10
10
 
11
11
  import type { SourceFile } from "@typescript/native/unstable/ast";
12
12
  import { aliasChains } from "./alias-chain.ts";
13
+ import type { ParseFile } from "./configuration-files.ts";
14
+ import { conventionEntries, type Conventions } from "./conventions.ts";
13
15
  import { entryPoints, type EntryRule } from "./entry-points.ts";
14
16
  import { globExpression } from "./glob.ts";
17
+ import type { Host } from "./host.ts";
15
18
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
16
19
  import type { Manifest, ManifestEntry } from "./manifest.ts";
17
20
  import { byPosition } from "./position.ts";
18
21
  import { UNANSWERED } from "./query.ts";
19
22
  import type { ProjectView } from "./session.ts";
20
23
  import { sourceFilesOf } from "./source-files.ts";
24
+ import { typeQueryAliases } from "./type-query-alias.ts";
21
25
 
22
26
  /** Why one declaration is a root. */
23
27
  export type RootKind =
24
28
  /** A file `ts.entry_files` names, and what it exports. */
25
29
  | "entry-file"
30
+ /** A file an applied convention row's globs match, and what it exports. */
31
+ | "convention"
26
32
  /** A file the manifest names as an importable entry point, and what it exports outside the published API. */
27
33
  | "manifest-entry"
28
34
  /** A file the manifest names as a command, and what it exports outside the published API. */
29
35
  | "manifest-binary"
36
+ /** A file a token of a manifest's script names, and what it exports. */
37
+ | "script"
38
+ /**
39
+ * A global a declaration file declares as an alias of a module's export, and the
40
+ * module declarations around it: a use of the global is a use of the export.
41
+ */
42
+ | "type-query-alias"
30
43
  /** A declaration a consumer of a library target can name. */
31
44
  | "published-api"
32
45
  /** A declaration an exact reference from the configuration names. */
33
46
  | "configured"
34
47
  /** A declaration a pattern from the configuration names. */
35
48
  | "pattern"
49
+ /** A declaration of a function or statement a type error skipped, and what it may have selected. */
50
+ | "type-error"
36
51
  /** A file a tool or the platform enters by its own convention, and what it exports where the tool reads it. */
37
52
  | EntryRule;
38
53
 
@@ -81,18 +96,36 @@ export interface Roots {
81
96
 
82
97
  /** What decides the roots beside the declarations themselves. */
83
98
  export interface RootOptions {
99
+ /** The platform the configuration files beside the program are read from. */
100
+ readonly host: Host;
84
101
  /** The target's own manifest. */
85
102
  readonly manifest: Manifest;
103
+ /**
104
+ * The entry points the other packages of the target's workspace name, each already
105
+ * marked published or not by its own manifest's rule.
106
+ */
107
+ readonly workspaceEntries?: readonly ManifestEntry[] | undefined;
86
108
  /** `roots.patterns` from the resolved configuration. */
87
109
  readonly patterns: readonly string[];
88
110
  /** `ts.entry_files` from the resolved configuration. */
89
111
  readonly entryFiles: readonly string[];
112
+ /** The convention rows the run applied. */
113
+ readonly conventions?: Conventions | undefined;
90
114
  /** `ts.test_files` from the resolved configuration. */
91
115
  readonly testFiles: readonly string[];
116
+ /** The parser the configuration files no program holds are read with, where they are read. */
117
+ readonly parse?: ParseFile | undefined;
92
118
  /** Whether the target is a library, so a consumer outside it reaches its published API. */
93
119
  readonly publishedAPI: boolean;
94
120
  }
95
121
 
122
+ /** The kind of root each role of a manifest entry makes. */
123
+ const ENTRY_KINDS = {
124
+ import: "manifest-entry",
125
+ run: "manifest-binary",
126
+ script: "script",
127
+ } as const satisfies Record<ManifestEntry["role"], RootKind>;
128
+
96
129
  /** Whether a configured string carries either special character. */
97
130
  function isPattern(text: string): boolean {
98
131
  return text.includes("*") || text.includes("?");
@@ -204,19 +237,24 @@ export function roots<Brand>(
204
237
  };
205
238
 
206
239
  const published: SourceFile[] = [];
207
- const publishes = (entry: ManifestEntry): boolean =>
208
- options.publishedAPI &&
209
- entry.published &&
210
- (options.manifest.declaresExports ? entry.member.startsWith("exports") : true);
211
- for (const entry of options.manifest.entries) {
212
- if (publishes(entry)) {
240
+ const entries = [
241
+ ...options.manifest.entries.map((entry) => ({
242
+ entry,
243
+ publishes:
244
+ entry.published &&
245
+ (options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
246
+ })),
247
+ ...(options.workspaceEntries ?? []).map((entry) => ({ entry, publishes: entry.published })),
248
+ ];
249
+ for (const { entry, publishes } of entries) {
250
+ if (options.publishedAPI && publishes) {
213
251
  published.push(...files.named(entry.path));
214
252
  }
215
253
  }
216
254
  const publishedNames = new Set(published.map((file) => file.fileName));
217
- for (const entry of options.manifest.entries) {
255
+ for (const { entry } of entries) {
218
256
  for (const file of files.named(entry.path)) {
219
- const kind = entry.role === "run" ? "manifest-binary" : "manifest-entry";
257
+ const kind = ENTRY_KINDS[entry.role];
220
258
  enter(file, kind, entry.member, !publishedNames.has(file.fileName));
221
259
  }
222
260
  }
@@ -228,9 +266,32 @@ export function roots<Brand>(
228
266
  }
229
267
  }
230
268
  }
231
- for (const point of entryPoints(project, files, options.testFiles)) {
269
+ for (const { file, row } of options.conventions === undefined
270
+ ? []
271
+ : conventionEntries(options.conventions, files)) {
272
+ enter(file, "convention", row, true);
273
+ }
274
+ for (const point of entryPoints(
275
+ project,
276
+ files,
277
+ options.testFiles,
278
+ options.host,
279
+ targetRoot,
280
+ options.parse,
281
+ )) {
232
282
  enter(point.file, point.rule, point.source, point.exports);
233
283
  }
284
+ for (const file of files.byPath.values()) {
285
+ for (const alias of typeQueryAliases(file)) {
286
+ const source = alias.type.getText().replace(/\s+/gu, " ");
287
+ for (const node of [alias.declaration, ...alias.containers]) {
288
+ const id = declared.get(nodeKey(file, node));
289
+ if (id !== undefined) {
290
+ add(id, "type-query-alias", source);
291
+ }
292
+ }
293
+ }
294
+ }
234
295
 
235
296
  const { tables: exportsOf, answered } = exportTables(project, held, [
236
297
  ...entered.filter((entry) => entry.exports).map((entry) => entry.file),
package/src/run.ts CHANGED
@@ -4,7 +4,8 @@ import { EMITTERS } from "./findings/emitters.ts";
4
4
  import type { Host } from "./host.ts";
5
5
  import { joinPath, resolvePath } from "./paths.ts";
6
6
  import { readScope, ScopeError, scopeForDir, type Scope } from "./scope.ts";
7
- import { openEngine, type Engine } from "./session.ts";
7
+ import { SetupError, setupLine } from "./setup-failure.ts";
8
+ import { openEngine, type Engine, type EngineOptions } from "./session.ts";
8
9
  import { printConfigVerb } from "./verbs/print-config.ts";
9
10
  import { printProjectsVerb } from "./verbs/print-projects.ts";
10
11
  import { printRetainedVerb } from "./verbs/print-retained.ts";
@@ -204,7 +205,7 @@ export function run(
204
205
  out: Writer,
205
206
  err: Writer,
206
207
  host: Host,
207
- openClient: (collectTiming: boolean) => Engine = (collectTiming) => openEngine({ collectTiming }),
208
+ openClient: (options: EngineOptions) => Engine = openEngine,
208
209
  ): number {
209
210
  const editing = sourceEditOption(args);
210
211
  if (editing !== undefined) {
@@ -243,7 +244,7 @@ function invoke(
243
244
  own: VerbOptions,
244
245
  args: readonly string[],
245
246
  streams: { readonly out: Writer; readonly err: Writer; readonly host: Host },
246
- openClient: (collectTiming: boolean) => Engine,
247
+ openClient: (options: EngineOptions) => Engine,
247
248
  ): number {
248
249
  let options: Options | undefined;
249
250
  const read = (): Options => (options ??= readOptions(args, own));
@@ -256,8 +257,11 @@ function invoke(
256
257
  scope: () => scopeOf(streams.host, read()),
257
258
  option: (name) => read().named.get(name),
258
259
  repeated: (name) => read().repeated.get(name) ?? [],
259
- openClient: (collectTiming) => {
260
- opened = openClient(collectTiming);
260
+ openClient: (config) => {
261
+ opened = openClient({
262
+ collectTiming: false,
263
+ components: { extensions: config.ts.componentExtensions, host: streams.host },
264
+ });
261
265
  return opened;
262
266
  },
263
267
  emitters: EMITTERS,
@@ -282,6 +286,12 @@ function refuse(error: unknown, err: Writer): number {
282
286
  err.write(`deadset-ts: ${error.message}\n`);
283
287
  return EXIT_FAILURE;
284
288
  }
289
+ if (error instanceof SetupError) {
290
+ for (const failure of error.failures) {
291
+ err.write(`${setupLine(failure)}\n`);
292
+ }
293
+ return EXIT_FAILURE;
294
+ }
285
295
  if (error instanceof ScopeError) {
286
296
  err.write(`deadset-ts: ${error.message}\n`);
287
297
  return EXIT_FAILURE;
package/src/schema.ts CHANGED
@@ -102,6 +102,8 @@ export const SCHEMA_ROOT: KeyNode = {
102
102
  members: {
103
103
  test_files: leaf,
104
104
  entry_files: leaf,
105
+ component_extensions: leaf,
106
+ disabled_conventions: leaf,
105
107
  injection_registrations: declarations,
106
108
  lifecycle_contracts: {
107
109
  kind: "list",