@cplieger/deadset-ts 5.0.0 → 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 (55) hide show
  1. package/dist/src/analysis.js +77 -10
  2. package/dist/src/configuration-files.js +107 -3
  3. package/dist/src/conformance-record.js +3 -9
  4. package/dist/src/contextual-uses.js +240 -0
  5. package/dist/src/convention-rows.js +6 -0
  6. package/dist/src/conventions.js +123 -14
  7. package/dist/src/dependencies.js +8 -2
  8. package/dist/src/destructuring.js +6 -6
  9. package/dist/src/entry-points.js +8 -5
  10. package/dist/src/exempt-classes.js +6 -1
  11. package/dist/src/exempt.js +21 -17
  12. package/dist/src/findings/completion.js +27 -3
  13. package/dist/src/findings/interfaces.js +6 -2
  14. package/dist/src/findings/reads-and-writes.js +53 -9
  15. package/dist/src/findings/unused-declarations.js +40 -1
  16. package/dist/src/generated-file.js +24 -0
  17. package/dist/src/interface-satisfaction.js +2 -68
  18. package/dist/src/inventory.js +4 -1
  19. package/dist/src/kinds.js +4 -3
  20. package/dist/src/query.js +15 -0
  21. package/dist/src/references.js +199 -10
  22. package/dist/src/resolve.js +12 -11
  23. package/dist/src/roots.js +1 -1
  24. package/dist/src/stores.js +3 -0
  25. package/dist/src/value-flow.js +74 -0
  26. package/dist/src/version.js +1 -1
  27. package/jsr.json +1 -1
  28. package/package.json +2 -2
  29. package/src/analysis.ts +92 -13
  30. package/src/configuration-files.ts +136 -1
  31. package/src/conformance-record.ts +3 -10
  32. package/src/contextual-uses.ts +354 -0
  33. package/src/convention-rows.ts +8 -0
  34. package/src/conventions.ts +161 -13
  35. package/src/dependencies.ts +14 -1
  36. package/src/destructuring.ts +16 -6
  37. package/src/entry-points.ts +16 -3
  38. package/src/exempt-classes.ts +6 -1
  39. package/src/exempt.ts +48 -17
  40. package/src/file-facts.ts +2 -0
  41. package/src/findings/completion.ts +31 -3
  42. package/src/findings/interfaces.ts +9 -2
  43. package/src/findings/reads-and-writes.ts +70 -9
  44. package/src/findings/unused-declarations.ts +51 -1
  45. package/src/generated-file.ts +27 -0
  46. package/src/interface-satisfaction.ts +1 -92
  47. package/src/inventory.ts +4 -1
  48. package/src/kinds.ts +5 -3
  49. package/src/query.ts +26 -0
  50. package/src/references.ts +254 -21
  51. package/src/resolve.ts +14 -13
  52. package/src/roots.ts +11 -1
  53. package/src/stores.ts +9 -0
  54. package/src/value-flow.ts +104 -0
  55. package/src/version.ts +1 -1
@@ -14,10 +14,12 @@ import { decorator } from "./decorator.js";
14
14
  import { dependenciesOf, projectNeeds } from "./dependencies.js";
15
15
  import { deprecatedDeclarations } from "./deprecation.js";
16
16
  import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "./discover.js";
17
- import { conventionFailuresFor, readConventions, } from "./conventions.js";
17
+ import { conventionFailuresFor, generatedFiles, readConventions, } from "./conventions.js";
18
18
  import { readEdgeSides } from "./edges.js";
19
19
  import { enumGroup } from "./enum-group.js";
20
- import { computeExemptions, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
20
+ import { generatedFile } from "./generated-file.js";
21
+ import { sourceFilesOf } from "./source-files.js";
22
+ import { detectExemptions, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
21
23
  import { heldByInclusion, sourceTree } from "./file-facts.js";
22
24
  import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
23
25
  import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js";
@@ -110,6 +112,7 @@ function readProjects(engine, host, scope, config, stage) {
110
112
  entryFiles: config.ts.entryFiles,
111
113
  conventions,
112
114
  testFiles: config.ts.testFiles,
115
+ parse: (fileName, text) => engine.parseSourceFile(fileName, text),
113
116
  publishedAPI: config.targetKind === "library",
114
117
  };
115
118
  const notBuilt = [...discovered.notBuilt];
@@ -225,6 +228,7 @@ function readProjects(engine, host, scope, config, stage) {
225
228
  .ownSourceFiles()
226
229
  .filter((file) => referenceOnly(file.fileName))
227
230
  .flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
231
+ generated: generatedFiles(conventions, sourceFilesOf(project, targetRoot)),
228
232
  };
229
233
  };
230
234
  return stage(project, { configuration, read });
@@ -321,6 +325,7 @@ export function runRoots(engine, host, scope, config, provenance) {
321
325
  const DETECTORS = new Map([
322
326
  ["interface-satisfaction", interfaceSatisfaction],
323
327
  ["enum-group", enumGroup],
328
+ ["generated-file", generatedFile],
324
329
  ["template-field", templateField],
325
330
  ["reflective-lookup", reflectiveLookup],
326
331
  ["decorator", decorator],
@@ -347,11 +352,27 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
347
352
  testFiles: config.ts.testFiles,
348
353
  });
349
354
  const support = testSupportFiles(projectRead.held.symbols, resolved.references, projectRead.rooted.liveUnderReachability, resolved.testFilePaths);
355
+ const detected = detectExemptions({
356
+ project,
357
+ held: projectRead.held,
358
+ targetRoot,
359
+ templates,
360
+ ts: config.ts,
361
+ consumers,
362
+ ...(config.analysis.generatedFiles === "include"
363
+ ? {}
364
+ : { generated: projectRead.generated }),
365
+ }, detectors, {
366
+ disabled,
367
+ mode,
368
+ testFiles: new Set([...resolved.testFilePaths, ...support]),
369
+ });
370
+ const exempt = detected.records;
350
371
  const made = supportReferences(resolved.references, support);
351
372
  const configured = {
352
373
  configuration: projectRead.configuration,
353
374
  symbols: projectRead.held.symbols,
354
- references: made,
375
+ references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
355
376
  roots: projectRead.rooted.liveUnderReachability,
356
377
  testFiles: resolved.testFilePaths,
357
378
  ...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
@@ -359,11 +380,6 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
359
380
  ? {}
360
381
  : { referenceOnly: projectRead.referenceOnly }),
361
382
  };
362
- const exempt = computeExemptions({ project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers }, detectors, {
363
- disabled,
364
- mode,
365
- testFiles: new Set([...resolved.testFilePaths, ...support]),
366
- });
367
383
  const unanswered = [
368
384
  ...projectRead.held.unanswered,
369
385
  ...projectRead.rooted.unanswered,
@@ -440,6 +456,55 @@ function holdingSweep(read, input) {
440
456
  live = next;
441
457
  }
442
458
  }
459
+ /**
460
+ * The test references exemption evidence written in a test file makes: each is a read of
461
+ * the declaration it would retain, made by the test file at the evidence's site.
462
+ */
463
+ function evidenceReferences(symbols, inTestFiles) {
464
+ if (inTestFiles.length === 0) {
465
+ return [];
466
+ }
467
+ const files = new Map(symbols
468
+ .filter((symbol) => symbol.kind === "file")
469
+ .map((symbol) => [symbol.position.path, symbol.id]));
470
+ return inTestFiles.flatMap((record) => {
471
+ const from = files.get(record.site.path);
472
+ return from === undefined
473
+ ? []
474
+ : [
475
+ {
476
+ from,
477
+ to: record.id,
478
+ position: record.site,
479
+ use: "read",
480
+ resolution: "syntax",
481
+ test: true,
482
+ },
483
+ ];
484
+ });
485
+ }
486
+ /**
487
+ * The declarations every reference the mode counts stores into, a loaded consumer's
488
+ * reference counting as a read: what the write-only kind would report but for a record.
489
+ */
490
+ function writtenOnly(read, mode) {
491
+ const written = new Set();
492
+ const readSome = new Set();
493
+ for (const { configured } of read.projects) {
494
+ for (const reference of configured.references) {
495
+ if (mode.production && reference.test) {
496
+ continue;
497
+ }
498
+ if (reference.use === "write" && reference.consumer === undefined) {
499
+ written.add(reference.to);
500
+ }
501
+ else {
502
+ readSome.add(reference.to);
503
+ }
504
+ }
505
+ }
506
+ return new Set([...written].filter((id) => !readSome.has(id)));
507
+ }
443
508
  /**
444
509
  * The sweep of a run read once, under one set of marks. Where a question went unanswered,
445
510
  * the run is swept once more without what that question could have kept live and without
@@ -483,7 +548,7 @@ function sweptOf(read, input) {
483
548
  matrix: read.matrix,
484
549
  sweep: result,
485
550
  exempt,
486
- retained: retainedIn(read.matrix, swept),
551
+ retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
487
552
  notBuilt: read.notBuilt,
488
553
  unanswered: read.unanswered,
489
554
  heldByUnanswered: [...held],
@@ -510,9 +575,10 @@ function readEmitterRun(engine, host, scope, config, mode, detectors) {
510
575
  const run = readRun(engine, host, scope, config, mode, detectors, (project, read, references) => ({
511
576
  deprecation: deprecatedDeclarations(project, read.held),
512
577
  accessors: accessorsOf(project, read.held),
513
- needs: projectNeeds(project, read.held, host, targetRoot),
578
+ needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text)),
514
579
  implementations: implementations(project, read.held),
515
580
  heldByInclusion: heldByInclusion(project, targetRoot),
581
+ generated: [...read.generated.keys()],
516
582
  rooted: read.rooted,
517
583
  directives: inlineDirectives(project, read.held, targetRoot).filter((directive) => !read.referenceOnly.includes(directive.site.path)),
518
584
  declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
@@ -546,6 +612,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
546
612
  files: {
547
613
  tree: sourceTree(host, targetRoot),
548
614
  heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
615
+ generated: new Set(projects.flatMap((one) => one.extra.generated)),
549
616
  },
550
617
  boundary: {
551
618
  consumers: {
@@ -6,7 +6,7 @@
6
6
  * every string either holds as it likes, so each one that spells a declared dependency
7
7
  * uses it and each relative one that names a source file of the program roots the file.
8
8
  */
9
- import { isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/native/unstable/ast";
9
+ import { isExportDeclaration, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/native/unstable/ast";
10
10
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
11
  const MANIFEST = "package.json";
12
12
  /** A configuration module's name, each form with the spelling a root reports it by. */
@@ -29,6 +29,15 @@ const DEPENDENCY_MEMBERS = [
29
29
  ];
30
30
  /** The prefixes a string names a file with, relative to the file that writes it. */
31
31
  const RELATIVE_PREFIXES = ["./", "../"];
32
+ /** The JavaScript extensions a relative specifier from a configuration file stands for TypeScript under. */
33
+ const COUNTERPARTS = [
34
+ [".js", [".ts", ".tsx"]],
35
+ [".jsx", [".tsx", ".ts"]],
36
+ [".mjs", [".mts"]],
37
+ [".cjs", [".cts"]],
38
+ ];
39
+ /** The extensions appended to a relative specifier from a configuration file, in order. */
40
+ const APPENDED = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
32
41
  /** The extensions the program's module resolution appends to a specifier written without one. */
33
42
  const RESOLVED_EXTENSIONS = [
34
43
  ".ts",
@@ -85,7 +94,7 @@ export function moduleStrings(file) {
85
94
  * in a directory that holds a manifest. A document that cannot be read or parsed as
86
95
  * JSON holds no string.
87
96
  */
88
- export function configurationFiles(host, targetRoot, files) {
97
+ export function configurationFiles(host, targetRoot, files, parse) {
89
98
  const root = resolvePath(host.workingDirectory(), targetRoot);
90
99
  const manifests = new Map();
91
100
  const holdsManifest = (dir) => {
@@ -135,7 +144,102 @@ export function configurationFiles(host, targetRoot, files) {
135
144
  documents.push({ path, strings: documentStrings(value) });
136
145
  }
137
146
  }
138
- return { modules, documents };
147
+ return {
148
+ modules,
149
+ documents,
150
+ outside: parse === undefined ? [] : outsideModules(host, [...dirs], files, holdsManifest, parse),
151
+ };
152
+ }
153
+ /** The relative specifiers one file's imports and re-exports name. */
154
+ function relativeSpecifiers(file) {
155
+ return file.statements.flatMap((statement) => {
156
+ const specifier = isImportDeclaration(statement) || isExportDeclaration(statement)
157
+ ? statement.moduleSpecifier
158
+ : undefined;
159
+ return specifier !== undefined &&
160
+ isStringLiteral(specifier) &&
161
+ RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))
162
+ ? [specifier.text]
163
+ : [];
164
+ });
165
+ }
166
+ /**
167
+ * The module one relative specifier from a configuration file reaches: the path it names,
168
+ * that path with its JavaScript extension replaced by the TypeScript counterparts, that
169
+ * path with an extension appended, and a directory's `index` with one appended.
170
+ */
171
+ function reachedBy(host, dir, specifier) {
172
+ const path = resolvePath(dir, specifier);
173
+ const isFile = (candidate) => host.kindOf(candidate) === "file";
174
+ // A data file such as `./package.json` is no module, so its strings use no dependency.
175
+ if (isFile(path) && APPENDED.some((extension) => path.endsWith(extension))) {
176
+ return path;
177
+ }
178
+ for (const [extension, counterparts] of COUNTERPARTS) {
179
+ if (path.endsWith(extension)) {
180
+ const stem = path.slice(0, -extension.length);
181
+ const found = counterparts.map((one) => stem + one).find(isFile);
182
+ if (found !== undefined) {
183
+ return found;
184
+ }
185
+ }
186
+ }
187
+ const appended = APPENDED.map((extension) => path + extension).find(isFile);
188
+ if (appended !== undefined) {
189
+ return appended;
190
+ }
191
+ return host.kindOf(path) === "directory"
192
+ ? APPENDED.map((extension) => joinPath(path, `index${extension}`)).find(isFile)
193
+ : undefined;
194
+ }
195
+ /**
196
+ * The configuration modules of the directories that hold a manifest which the program
197
+ * does not hold, and the files they import by a relative specifier, until no further
198
+ * file joins. A file that cannot be read is skipped.
199
+ */
200
+ function outsideModules(host, dirs, files, holdsManifest, parse) {
201
+ const pending = [];
202
+ for (const dir of [...dirs].sort()) {
203
+ if (!holdsManifest(dir)) {
204
+ continue;
205
+ }
206
+ let names;
207
+ try {
208
+ names = host
209
+ .readDirectory(dir)
210
+ .filter((entry) => !entry.directory && configurationModuleForm(entry.name) !== undefined)
211
+ .map((entry) => entry.name)
212
+ .sort();
213
+ }
214
+ catch {
215
+ continue;
216
+ }
217
+ pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
218
+ }
219
+ const found = [];
220
+ const seen = new Set();
221
+ for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
222
+ if (seen.has(next.path) || files.byName.has(next.path)) {
223
+ continue;
224
+ }
225
+ seen.add(next.path);
226
+ let text;
227
+ try {
228
+ text = host.readFile(next.path);
229
+ }
230
+ catch {
231
+ continue;
232
+ }
233
+ const file = parse(next.path, text);
234
+ found.push({ file, manifestDir: next.manifestDir });
235
+ for (const specifier of relativeSpecifiers(file)) {
236
+ const reached = reachedBy(host, dirnamePath(next.path), specifier);
237
+ if (reached !== undefined) {
238
+ pending.push({ path: reached, manifestDir: next.manifestDir });
239
+ }
240
+ }
241
+ }
242
+ return found;
139
243
  }
140
244
  /** Every dependency the manifest in one directory declares, or none where it holds none to read. */
141
245
  export function declaredIn(host, dir) {
@@ -2,15 +2,9 @@
2
2
  // Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
3
3
  export const RECORD = {
4
4
  conformance: {
5
- corpusVersion: "2.1.1",
5
+ corpusVersion: "2.2.0",
6
6
  result: "pass",
7
- digest: "sha256:e5e456bf3a706f1fa9f906ae71e10ea98a78da52d7d99a5ed59dca8a72bbe927",
7
+ digest: "sha256:ce7c9087ed7b2baf3de9fa2df436b500706f4f9e94fd9e0fed633054305f06a4",
8
8
  },
9
- gaps: [
10
- {
11
- fixture: "private-member-unread",
12
- capability: "reflective-lookup",
13
- reason: "The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
14
- },
15
- ],
9
+ gaps: [],
16
10
  };
@@ -0,0 +1,240 @@
1
+ /**
2
+ * The uses contextual types make of the target's members, which no name node resolves to.
3
+ * An object-literal property writes the member of its name on each constituent of the
4
+ * literal's contextual type that declares one. A value reaching a position typed by another
5
+ * object type reads each of its members that type declares, through array elements and
6
+ * function parameters and results too; a class instance there is the
7
+ * interface-satisfaction class's evidence instead.
8
+ */
9
+ import { isComputedPropertyName, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAssignment, isSetAccessorDeclaration, isShorthandPropertyAssignment, isStringLiteral, } from "@typescript/native/unstable/ast";
10
+ import { SignatureKind } from "@typescript/native/unstable/sync";
11
+ import { isAnswered } from "./query.js";
12
+ /** The member kinds a contextual type writes and reads: data members and the members of object types. */
13
+ const MEMBER_KINDS = new Set(["type-member", "class-member"]);
14
+ /** The name one literal property is written under, where the name is literal text. */
15
+ function propertyName(property) {
16
+ if (!isPropertyAssignment(property) &&
17
+ !isShorthandPropertyAssignment(property) &&
18
+ !isMethodDeclaration(property) &&
19
+ !isGetAccessorDeclaration(property) &&
20
+ !isSetAccessorDeclaration(property)) {
21
+ return undefined;
22
+ }
23
+ const name = property.name;
24
+ if (isIdentifier(name) || isStringLiteral(name) || isNumericLiteral(name)) {
25
+ return { node: name, text: name.text };
26
+ }
27
+ if (isComputedPropertyName(name)) {
28
+ const key = name.expression;
29
+ if (isStringLiteral(key) || isNumericLiteral(key) || isNoSubstitutionTemplateLiteral(key)) {
30
+ return { node: name, text: key.text };
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ /** How many element and signature steps the structural walk takes below a value's own type. */
36
+ const MAX_DEPTH = 3;
37
+ /**
38
+ * The contextual uses of one project's literals and values, asked in batches: one
39
+ * contextual type per literal and per value whose type carries a member of the target,
40
+ * one type per value, and each distinct type's properties once.
41
+ */
42
+ export function contextualUses(project, held, chains, literals, flows, cap, properties) {
43
+ const queries = project.queries;
44
+ const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
45
+ let batches = 0;
46
+ let lookups = 0;
47
+ const batchesOf = (count) => Math.ceil(count / cap);
48
+ const parts = new Map();
49
+ /** The object types one type is: itself, or its constituents at any depth. */
50
+ const partsOf = (type) => {
51
+ const known = parts.get(type.id);
52
+ if (known !== undefined) {
53
+ return known;
54
+ }
55
+ let found = [];
56
+ if (type.isUnionType() || type.isIntersectionType()) {
57
+ lookups += 1;
58
+ const constituents = queries.constituents(type);
59
+ found = isAnswered(constituents) ? constituents.flatMap(partsOf) : [];
60
+ }
61
+ else if (type.isObjectType()) {
62
+ found = [type];
63
+ }
64
+ parts.set(type.id, found);
65
+ return found;
66
+ };
67
+ const tables = new Map();
68
+ /** The members one object type has, by name. */
69
+ const membersOf = (type) => {
70
+ const known = tables.get(type.id);
71
+ if (known !== undefined) {
72
+ return known;
73
+ }
74
+ const table = new Map();
75
+ const read = properties.of(type);
76
+ lookups += read.asked ? 1 : 0;
77
+ for (const property of isAnswered(read.properties) ? read.properties : []) {
78
+ table.set(property.name, {
79
+ ids: chains
80
+ .declarationsOf(property)
81
+ .filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
82
+ handles: new Set(property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`)),
83
+ });
84
+ }
85
+ tables.set(type.id, table);
86
+ return table;
87
+ };
88
+ const writes = [];
89
+ batches += batchesOf(literals.length);
90
+ const contexts = queries.contextualTypes(literals.map((one) => one.literal), cap);
91
+ literals.forEach(({ literal, from }, index) => {
92
+ const context = contexts[index];
93
+ if (context === undefined || !isAnswered(context)) {
94
+ return;
95
+ }
96
+ const tablesOf = partsOf(context).map(membersOf);
97
+ for (const property of literal.properties) {
98
+ const name = propertyName(property);
99
+ if (name === undefined) {
100
+ continue;
101
+ }
102
+ const ids = [...new Set(tablesOf.flatMap((table) => table.get(name.text)?.ids ?? []))];
103
+ if (ids.length > 0) {
104
+ writes.push({ at: name.node, from, ids });
105
+ }
106
+ }
107
+ });
108
+ const carried = flows.filter((flow) => !isObjectLiteralExpression(flow.value));
109
+ batches += batchesOf(carried.length);
110
+ const types = queries.typesAt(carried.map((flow) => flow.value), cap);
111
+ /** The members a position may read structurally: those of object types, not of classes. */
112
+ const readable = (member) => member.ids.filter((id) => kinds.get(id) === "type-member");
113
+ const carries = (part) => [...membersOf(part).values()].some((member) => readable(member).length > 0);
114
+ const elements = new Map();
115
+ /** The element type of an array type, read once per type. */
116
+ const elementOf = (type) => {
117
+ if (elements.has(type.id)) {
118
+ return elements.get(type.id);
119
+ }
120
+ lookups += 1;
121
+ const isArray = queries.isArray(type);
122
+ let element;
123
+ if (isAnswered(isArray) && isArray) {
124
+ lookups += 1;
125
+ const held = queries.typeArguments(type);
126
+ element = isAnswered(held) ? held[0] : undefined;
127
+ }
128
+ elements.set(type.id, element);
129
+ return element;
130
+ };
131
+ const calls = new Map();
132
+ /** The parameter and return types of a type's one call signature, read once per type. */
133
+ const callOf = (type) => {
134
+ if (calls.has(type.id)) {
135
+ return calls.get(type.id);
136
+ }
137
+ lookups += 1;
138
+ const signatures = queries.signaturesOf(type, SignatureKind.Call);
139
+ let called;
140
+ const [signature, ...more] = isAnswered(signatures) ? signatures : [];
141
+ if (signature !== undefined && more.length === 0) {
142
+ const parameters = signature.parameters.map((_parameter, index) => queries.parameterType(signature, index));
143
+ const result = queries.returnTypeOf(signature);
144
+ lookups += parameters.length + 1;
145
+ called = {
146
+ parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
147
+ result: isAnswered(result) ? result : undefined,
148
+ };
149
+ }
150
+ calls.set(type.id, called);
151
+ return called;
152
+ };
153
+ /**
154
+ * Each object type a value of `source` is checked as against the object types of
155
+ * `target`: the value's own, and through array elements and through a function's
156
+ * parameters, checked the other way, and its result. A source that is one of the
157
+ * target's own object types matches it as it stands.
158
+ */
159
+ const compared = (source, target, depth, into, seen) => {
160
+ const key = `${String(source?.id)}>${String(target?.id)}`;
161
+ if (source === undefined || target === undefined || source.id === target.id) {
162
+ return;
163
+ }
164
+ if (depth > MAX_DEPTH || seen.has(key)) {
165
+ return;
166
+ }
167
+ seen.add(key);
168
+ const targets = partsOf(target);
169
+ const sources = partsOf(source).filter((one) => !targets.some((part) => part.id === one.id));
170
+ for (const one of sources) {
171
+ if (carries(one)) {
172
+ into.push({ source: one, targets });
173
+ }
174
+ const element = elementOf(one);
175
+ const called = element === undefined ? callOf(one) : undefined;
176
+ for (const part of targets) {
177
+ if (element !== undefined) {
178
+ compared(element, elementOf(part), depth + 1, into, seen);
179
+ continue;
180
+ }
181
+ const other = called === undefined ? undefined : callOf(part);
182
+ if (called === undefined || other === undefined) {
183
+ continue;
184
+ }
185
+ other.parameters.forEach((parameter, index) => {
186
+ compared(parameter, called.parameters[index], depth + 1, into, seen);
187
+ });
188
+ compared(called.result, other.result, depth + 1, into, seen);
189
+ }
190
+ }
191
+ };
192
+ /** Whether a value of one type may carry a member a position reads, at any depth the walk takes. */
193
+ const mayCarry = (type, depth) => depth <= MAX_DEPTH &&
194
+ partsOf(type).some((part) => {
195
+ if (carries(part)) {
196
+ return true;
197
+ }
198
+ const element = elementOf(part);
199
+ if (element !== undefined) {
200
+ return mayCarry(element, depth + 1);
201
+ }
202
+ return callOf(part) !== undefined;
203
+ });
204
+ const sourced = carried.flatMap((flow, index) => {
205
+ const type = types[index];
206
+ return type === undefined || !isAnswered(type) || !mayCarry(type, 0) ? [] : [{ flow, type }];
207
+ });
208
+ batches += batchesOf(sourced.length);
209
+ const positions = queries.contextualTypes(sourced.map((one) => one.flow.value), cap);
210
+ const reads = [];
211
+ sourced.forEach(({ flow, type }, index) => {
212
+ const position = positions[index];
213
+ if (position === undefined || !isAnswered(position)) {
214
+ return;
215
+ }
216
+ const checked = [];
217
+ compared(type, position, 0, checked, new Set());
218
+ const ids = new Set();
219
+ for (const { source, targets } of checked) {
220
+ const tables = targets.map(membersOf);
221
+ for (const [name, member] of membersOf(source)) {
222
+ const read = tables.some((table) => {
223
+ const declared = table.get(name);
224
+ return (declared !== undefined &&
225
+ declared.handles.size > 0 &&
226
+ ![...declared.handles].some((handle) => member.handles.has(handle)));
227
+ });
228
+ if (read) {
229
+ for (const id of readable(member)) {
230
+ ids.add(id);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ if (ids.size > 0) {
236
+ reads.push({ at: flow.value, from: flow.from, ids: [...ids] });
237
+ }
238
+ });
239
+ return { writes, reads, batches, lookups };
240
+ }
@@ -45,6 +45,7 @@ export const CONVENTION_ROWS = [
45
45
  `<src>/content/config.${MODULE}`,
46
46
  ],
47
47
  excludes: ["<src>/pages/**/_*", "<src>/pages/**/_*/**"],
48
+ generated: [".astro"],
48
49
  moves: [
49
50
  {
50
51
  id: "src",
@@ -71,6 +72,7 @@ export const CONVENTION_ROWS = [
71
72
  name: "next",
72
73
  package: "next",
73
74
  range: ">=13.4.0 <17.0.0",
75
+ generated: [".next"],
74
76
  entries: [
75
77
  `{,src/}app/**/{layout,page,loading,not-found,error,route,template,default,forbidden,unauthorized}.${SCRIPT}`,
76
78
  `{,src/}app/global-{error,not-found}.${SCRIPT}`,
@@ -87,6 +89,7 @@ export const CONVENTION_ROWS = [
87
89
  name: "nuxt",
88
90
  package: "nuxt",
89
91
  range: ">=3.0.0 <5.0.0",
92
+ generated: [".nuxt"],
90
93
  entries: [
91
94
  "<srcDir>/{app,error}.{vue,jsx,tsx}",
92
95
  `<srcDir>/app.config.${MODULE}`,
@@ -181,6 +184,7 @@ export const CONVENTION_ROWS = [
181
184
  package: "@react-router/dev",
182
185
  range: ">=7.0.0 <9.0.0",
183
186
  entries: APP_DIRECTORY_ENTRIES,
187
+ generated: [".react-router"],
184
188
  moves: [
185
189
  {
186
190
  id: "app",
@@ -259,6 +263,7 @@ export const CONVENTION_ROWS = [
259
263
  package: "@sveltejs/kit",
260
264
  range: ">=2.0.0 <3.0.0",
261
265
  entries: [...SVELTEKIT_ENTRIES, "<params>/*.{js,ts}"],
266
+ generated: [".svelte-kit"],
262
267
  moves: [
263
268
  {
264
269
  id: "src",
@@ -323,6 +328,7 @@ export const CONVENTION_ROWS = [
323
328
  package: "@sveltejs/kit",
324
329
  range: ">=3.0.0 <4.0.0",
325
330
  entries: [...SVELTEKIT_ENTRIES, "<params>.{js,ts}"],
331
+ generated: [".svelte-kit"],
326
332
  moves: [
327
333
  {
328
334
  id: "src",