@cplieger/deadset-ts 5.0.0 → 5.1.1

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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +145 -39
  3. package/dist/src/configuration-files.js +108 -4
  4. package/dist/src/conformance-record.js +3 -9
  5. package/dist/src/contextual-uses.js +247 -0
  6. package/dist/src/convention-rows.js +6 -0
  7. package/dist/src/conventions.js +123 -14
  8. package/dist/src/dependencies.js +8 -2
  9. package/dist/src/destructuring.js +6 -6
  10. package/dist/src/entry-points.js +8 -5
  11. package/dist/src/exempt-classes.js +6 -1
  12. package/dist/src/exempt.js +35 -16
  13. package/dist/src/file-facts.js +23 -20
  14. package/dist/src/findings/completion.js +27 -3
  15. package/dist/src/findings/interfaces.js +6 -2
  16. package/dist/src/findings/reads-and-writes.js +53 -9
  17. package/dist/src/findings/unused-declarations.js +40 -1
  18. package/dist/src/generated-file.js +24 -0
  19. package/dist/src/graph.js +1 -1
  20. package/dist/src/interface-satisfaction.js +2 -68
  21. package/dist/src/inventory.js +17 -4
  22. package/dist/src/kinds.js +4 -3
  23. package/dist/src/manifest.js +29 -4
  24. package/dist/src/outside-tests.js +139 -0
  25. package/dist/src/overrides.js +104 -0
  26. package/dist/src/paths.js +17 -0
  27. package/dist/src/query.js +15 -0
  28. package/dist/src/references.js +229 -11
  29. package/dist/src/resolve.js +12 -11
  30. package/dist/src/roots.js +121 -37
  31. package/dist/src/session.js +4 -3
  32. package/dist/src/stores.js +3 -0
  33. package/dist/src/template-field.js +60 -3
  34. package/dist/src/value-flow.js +74 -0
  35. package/dist/src/version.js +1 -1
  36. package/jsr.json +1 -1
  37. package/package.json +2 -2
  38. package/src/analysis.ts +194 -49
  39. package/src/configuration-files.ts +137 -2
  40. package/src/conformance-record.ts +3 -10
  41. package/src/contextual-uses.ts +358 -0
  42. package/src/convention-rows.ts +8 -0
  43. package/src/conventions.ts +161 -13
  44. package/src/dependencies.ts +14 -1
  45. package/src/destructuring.ts +16 -6
  46. package/src/entry-points.ts +16 -3
  47. package/src/exempt-classes.ts +6 -1
  48. package/src/exempt.ts +67 -16
  49. package/src/file-facts.ts +26 -20
  50. package/src/findings/completion.ts +31 -3
  51. package/src/findings/interfaces.ts +9 -2
  52. package/src/findings/reads-and-writes.ts +70 -9
  53. package/src/findings/unused-declarations.ts +51 -1
  54. package/src/generated-file.ts +27 -0
  55. package/src/graph.ts +4 -4
  56. package/src/interface-satisfaction.ts +1 -92
  57. package/src/inventory.ts +19 -4
  58. package/src/kinds.ts +5 -3
  59. package/src/manifest.ts +40 -4
  60. package/src/outside-tests.ts +191 -0
  61. package/src/overrides.ts +136 -0
  62. package/src/paths.ts +18 -0
  63. package/src/query.ts +26 -0
  64. package/src/references.ts +294 -22
  65. package/src/resolve.ts +14 -13
  66. package/src/roots.ts +152 -44
  67. package/src/session.ts +4 -2
  68. package/src/stores.ts +9 -0
  69. package/src/template-field.ts +65 -3
  70. package/src/value-flow.ts +104 -0
  71. package/src/version.ts +1 -1
@@ -0,0 +1,247 @@
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
+ /**
36
+ * The contextual uses of one project's literals and values, asked in batches: one
37
+ * contextual type per literal and per value whose type carries a member of the target,
38
+ * one type per value, and each distinct type's properties once.
39
+ */
40
+ export function contextualUses(project, held, chains, literals, flows, cap, properties) {
41
+ const queries = project.queries;
42
+ const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
43
+ let batches = 0;
44
+ let lookups = 0;
45
+ const batchesOf = (count) => Math.ceil(count / cap);
46
+ const parts = new Map();
47
+ /** The object types one type is: itself, or its constituents at any depth. */
48
+ const partsOf = (type) => {
49
+ const known = parts.get(type.id);
50
+ if (known !== undefined) {
51
+ return known;
52
+ }
53
+ let found = [];
54
+ if (type.isUnionType() || type.isIntersectionType()) {
55
+ lookups += 1;
56
+ const constituents = queries.constituents(type);
57
+ found = isAnswered(constituents) ? constituents.flatMap(partsOf) : [];
58
+ }
59
+ else if (type.isObjectType()) {
60
+ found = [type];
61
+ }
62
+ parts.set(type.id, found);
63
+ return found;
64
+ };
65
+ const tables = new Map();
66
+ /** The members one object type has, by name. */
67
+ const membersOf = (type) => {
68
+ const known = tables.get(type.id);
69
+ if (known !== undefined) {
70
+ return known;
71
+ }
72
+ const table = new Map();
73
+ const read = properties.of(type);
74
+ lookups += read.asked ? 1 : 0;
75
+ for (const property of isAnswered(read.properties) ? read.properties : []) {
76
+ table.set(property.name, {
77
+ ids: chains
78
+ .declarationsOf(property)
79
+ .filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
80
+ handles: new Set(property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`)),
81
+ });
82
+ }
83
+ tables.set(type.id, table);
84
+ return table;
85
+ };
86
+ const writes = [];
87
+ batches += batchesOf(literals.length);
88
+ const contexts = queries.contextualTypes(literals.map((one) => one.literal), cap);
89
+ literals.forEach(({ literal, from }, index) => {
90
+ const context = contexts[index];
91
+ if (context === undefined || !isAnswered(context)) {
92
+ return;
93
+ }
94
+ const tablesOf = partsOf(context).map(membersOf);
95
+ for (const property of literal.properties) {
96
+ const name = propertyName(property);
97
+ if (name === undefined) {
98
+ continue;
99
+ }
100
+ const ids = [...new Set(tablesOf.flatMap((table) => table.get(name.text)?.ids ?? []))];
101
+ if (ids.length > 0) {
102
+ writes.push({ at: name.node, from, ids });
103
+ }
104
+ }
105
+ });
106
+ const carried = flows.filter((flow) => !isObjectLiteralExpression(flow.value));
107
+ batches += batchesOf(carried.length);
108
+ const types = queries.typesAt(carried.map((flow) => flow.value), cap);
109
+ /** The members a position may read structurally: those of object types, not of classes. */
110
+ const readable = (member) => member.ids.filter((id) => kinds.get(id) === "type-member");
111
+ const carries = (part) => [...membersOf(part).values()].some((member) => readable(member).length > 0);
112
+ const elements = new Map();
113
+ /** The element type of an array type, read once per type. */
114
+ const elementOf = (type) => {
115
+ if (elements.has(type.id)) {
116
+ return elements.get(type.id);
117
+ }
118
+ lookups += 1;
119
+ const isArray = queries.isArray(type);
120
+ let element;
121
+ if (isAnswered(isArray) && isArray) {
122
+ lookups += 1;
123
+ const held = queries.typeArguments(type);
124
+ element = isAnswered(held) ? held[0] : undefined;
125
+ }
126
+ elements.set(type.id, element);
127
+ return element;
128
+ };
129
+ const calls = new Map();
130
+ /** The parameter and return types of a type's one call signature, read once per type. */
131
+ const callOf = (type) => {
132
+ if (calls.has(type.id)) {
133
+ return calls.get(type.id);
134
+ }
135
+ lookups += 1;
136
+ const signatures = queries.signaturesOf(type, SignatureKind.Call);
137
+ let called;
138
+ const [signature, ...more] = isAnswered(signatures) ? signatures : [];
139
+ if (signature !== undefined && more.length === 0) {
140
+ const parameters = signature.parameters.map((_parameter, index) => queries.parameterType(signature, index));
141
+ const result = queries.returnTypeOf(signature);
142
+ lookups += parameters.length + 1;
143
+ called = {
144
+ parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
145
+ result: isAnswered(result) ? result : undefined,
146
+ };
147
+ }
148
+ calls.set(type.id, called);
149
+ return called;
150
+ };
151
+ /**
152
+ * Each object type a value of `source` is checked as against the object types of
153
+ * `target`: the value's own, and through array elements and through a function's
154
+ * parameters, checked the other way, and its result. A source that is one of the
155
+ * target's own object types matches it as it stands. Each pair is compared once, which
156
+ * ends the walk over a recursive type.
157
+ */
158
+ const compared = (source, target, into, seen) => {
159
+ const key = `${String(source?.id)}>${String(target?.id)}`;
160
+ if (source === undefined || target === undefined || source.id === target.id) {
161
+ return;
162
+ }
163
+ if (seen.has(key)) {
164
+ return;
165
+ }
166
+ seen.add(key);
167
+ const targets = partsOf(target);
168
+ const sources = partsOf(source).filter((one) => !targets.some((part) => part.id === one.id));
169
+ for (const one of sources) {
170
+ if (carries(one)) {
171
+ into.push({ source: one, targets });
172
+ }
173
+ const element = elementOf(one);
174
+ const called = element === undefined ? callOf(one) : undefined;
175
+ for (const part of targets) {
176
+ if (element !== undefined) {
177
+ compared(element, elementOf(part), into, seen);
178
+ continue;
179
+ }
180
+ const other = called === undefined ? undefined : callOf(part);
181
+ if (called === undefined || other === undefined) {
182
+ continue;
183
+ }
184
+ other.parameters.forEach((parameter, index) => {
185
+ compared(parameter, called.parameters[index], into, seen);
186
+ });
187
+ compared(called.result, other.result, into, seen);
188
+ }
189
+ }
190
+ };
191
+ /**
192
+ * Whether a value of one type may carry a member a position reads, at any depth the
193
+ * walk takes. A type met again on the way adds nothing its first visit did not.
194
+ */
195
+ const mayCarry = (type, seen = new Set()) => {
196
+ if (seen.has(type.id)) {
197
+ return false;
198
+ }
199
+ seen.add(type.id);
200
+ return partsOf(type).some((part) => {
201
+ if (carries(part)) {
202
+ return true;
203
+ }
204
+ const element = elementOf(part);
205
+ if (element !== undefined) {
206
+ return mayCarry(element, seen);
207
+ }
208
+ return callOf(part) !== undefined;
209
+ });
210
+ };
211
+ const sourced = carried.flatMap((flow, index) => {
212
+ const type = types[index];
213
+ return type === undefined || !isAnswered(type) || !mayCarry(type) ? [] : [{ flow, type }];
214
+ });
215
+ batches += batchesOf(sourced.length);
216
+ const positions = queries.contextualTypes(sourced.map((one) => one.flow.value), cap);
217
+ const reads = [];
218
+ sourced.forEach(({ flow, type }, index) => {
219
+ const position = positions[index];
220
+ if (position === undefined || !isAnswered(position)) {
221
+ return;
222
+ }
223
+ const checked = [];
224
+ compared(type, position, checked, new Set());
225
+ const ids = new Set();
226
+ for (const { source, targets } of checked) {
227
+ const tables = targets.map(membersOf);
228
+ for (const [name, member] of membersOf(source)) {
229
+ const read = tables.some((table) => {
230
+ const declared = table.get(name);
231
+ return (declared !== undefined &&
232
+ declared.handles.size > 0 &&
233
+ ![...declared.handles].some((handle) => member.handles.has(handle)));
234
+ });
235
+ if (read) {
236
+ for (const id of readable(member)) {
237
+ ids.add(id);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ if (ids.size > 0) {
243
+ reads.push({ at: flow.value, from: flow.from, ids: [...ids] });
244
+ }
245
+ });
246
+ return { writes, reads, batches, lookups };
247
+ }
@@ -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",
@@ -5,15 +5,16 @@
5
5
  * directory a configuration property moves read from the property's literal value.
6
6
  * No configuration file is run.
7
7
  */
8
- import { isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ import { isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
9
9
  import { lineOf } from "./component-files.js";
10
10
  import { CONVENTION_ROWS, } from "./convention-rows.js";
11
11
  import { globExpression } from "./glob.js";
12
12
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
13
13
  import { satisfies } from "./version-range.js";
14
+ /** The sections an installed package is required by, so its absence fails the run. */
15
+ const REQUIRED_SECTIONS = ["dependencies", "devDependencies"];
14
16
  const DEPENDENCY_SECTIONS = [
15
- "dependencies",
16
- "devDependencies",
17
+ ...REQUIRED_SECTIONS,
17
18
  "peerDependencies",
18
19
  "optionalDependencies",
19
20
  ];
@@ -28,11 +29,11 @@ function readJSON(host, path) {
28
29
  return undefined;
29
30
  }
30
31
  }
31
- function declares(manifest, name) {
32
+ function declares(manifest, name, sections = DEPENDENCY_SECTIONS) {
32
33
  if (!isRecord(manifest)) {
33
34
  return false;
34
35
  }
35
- return DEPENDENCY_SECTIONS.some((section) => {
36
+ return sections.some((section) => {
36
37
  const held = manifest[section];
37
38
  return isRecord(held) && Object.hasOwn(held, name);
38
39
  });
@@ -70,20 +71,29 @@ function unwrapped(node) {
70
71
  }
71
72
  return at;
72
73
  }
73
- /** The key one property is written with, where it is written as a name or a literal. */
74
+ /** The key one property is written with, where it is written as a name or as literal text. */
74
75
  function keyOf(property) {
75
76
  if (!isPropertyAssignment(property) && !isShorthandPropertyAssignment(property)) {
76
77
  return undefined;
77
78
  }
78
79
  const { name } = property;
79
- if (isComputedPropertyName(name)) {
80
- return undefined;
81
- }
82
- if (isIdentifier(name) || isStringLiteral(name) || isNoSubstitutionTemplateLiteral(name)) {
83
- return name.text;
80
+ const key = isComputedPropertyName(name) ? name.expression : name;
81
+ if ((isIdentifier(key) && key === name) ||
82
+ isStringLiteral(key) ||
83
+ isNoSubstitutionTemplateLiteral(key)) {
84
+ return key.text;
84
85
  }
85
86
  return undefined;
86
87
  }
88
+ /** Whether one member's computed name is no literal, so it may set any key. */
89
+ function computesKey(member) {
90
+ const name = member.name;
91
+ if (name === undefined || !isComputedPropertyName(name)) {
92
+ return false;
93
+ }
94
+ const key = name.expression;
95
+ return !(isStringLiteral(key) || isNumericLiteral(key) || isNoSubstitutionTemplateLiteral(key));
96
+ }
87
97
  /** The values one file's top-level variable declarations bind, by name. */
88
98
  function declaredValues(file) {
89
99
  const values = new Map();
@@ -134,6 +144,67 @@ function defaultExports(file) {
134
144
  }
135
145
  return found;
136
146
  }
147
+ /** The declarations whose name is a binding of the scope they are written in. */
148
+ const BINDING_KINDS = new Set([
149
+ SyntaxKind.VariableDeclaration,
150
+ SyntaxKind.Parameter,
151
+ SyntaxKind.BindingElement,
152
+ SyntaxKind.FunctionDeclaration,
153
+ SyntaxKind.ClassDeclaration,
154
+ SyntaxKind.ImportClause,
155
+ SyntaxKind.ImportSpecifier,
156
+ SyntaxKind.NamespaceImport,
157
+ SyntaxKind.ImportEqualsDeclaration,
158
+ ]);
159
+ /** Whether one file declares a binding named `require` at any depth, which shadows the global. */
160
+ function declaresRequire(file) {
161
+ let found = false;
162
+ const visit = (node) => {
163
+ const name = node.name;
164
+ if (name !== undefined &&
165
+ isIdentifier(name) &&
166
+ name.text === "require" &&
167
+ BINDING_KINDS.has(node.kind)) {
168
+ found = true;
169
+ }
170
+ if (!found) {
171
+ node.forEachChild(visit);
172
+ }
173
+ };
174
+ file.forEachChild(visit);
175
+ return found;
176
+ }
177
+ /**
178
+ * The top-level `const`, `let` and `var` declarations that bind a `require` of one module,
179
+ * written with the module's name as its one string-literal argument, in a file that
180
+ * declares no binding named `require`: each binds as an import of the module does.
181
+ */
182
+ function requireBindings(file, module) {
183
+ const found = [];
184
+ for (const statement of file.statements) {
185
+ if (!isVariableStatement(statement)) {
186
+ continue;
187
+ }
188
+ for (const declaration of statement.declarationList.declarations) {
189
+ const value = declaration.initializer === undefined ? undefined : unwrapped(declaration.initializer);
190
+ const [argument, ...rest] = value !== undefined && isCallExpression(value) ? value.arguments : [];
191
+ if (value === undefined ||
192
+ !isCallExpression(value) ||
193
+ !isIdentifier(value.expression) ||
194
+ value.expression.text !== "require" ||
195
+ argument === undefined ||
196
+ rest.length > 0 ||
197
+ !isStringLiteral(argument) ||
198
+ argument.text !== module) {
199
+ continue;
200
+ }
201
+ if (isIdentifier(declaration.name) || isObjectBindingPattern(declaration.name)) {
202
+ found.push(declaration.name);
203
+ }
204
+ }
205
+ }
206
+ return found.length > 0 && declaresRequire(file) ? [] : found;
207
+ }
137
208
  /**
138
209
  * The first argument of every call one file makes to one module's export, through a
139
210
  * named or a namespace import; `undefined` for a call with no argument.
@@ -141,6 +212,24 @@ function defaultExports(file) {
141
212
  function optionsArguments(file, call) {
142
213
  const names = new Set();
143
214
  const namespaces = new Set();
215
+ for (const name of requireBindings(file, call.module)) {
216
+ if (isIdentifier(name)) {
217
+ namespaces.add(name.text);
218
+ continue;
219
+ }
220
+ for (const element of name.elements) {
221
+ const local = element.name;
222
+ const exported = element.propertyName ?? local;
223
+ if (element.dotDotDotToken === undefined &&
224
+ local !== undefined &&
225
+ isIdentifier(local) &&
226
+ exported !== undefined &&
227
+ (isIdentifier(exported) || isStringLiteral(exported)) &&
228
+ exported.text === call.export) {
229
+ names.add(local.text);
230
+ }
231
+ }
232
+ }
144
233
  for (const statement of file.statements) {
145
234
  const bindings = isImportDeclaration(statement)
146
235
  ? statement.importClause?.namedBindings
@@ -207,7 +296,7 @@ function objectOf(node, declared, throughCalls, seen = new Set()) {
207
296
  function readPath(object, keys, values) {
208
297
  const [key, ...rest] = keys;
209
298
  for (const member of object.properties) {
210
- if (isSpreadAssignment(member)) {
299
+ if (isSpreadAssignment(member) || computesKey(member)) {
211
300
  return member;
212
301
  }
213
302
  if (keyOf(member) !== key) {
@@ -352,6 +441,7 @@ function compare(a, b) {
352
441
  export function readConventions(host, parse, manifests, disabled, rows = CONVENTION_ROWS) {
353
442
  const applied = [];
354
443
  const globs = [];
444
+ const generated = [];
355
445
  const failures = [];
356
446
  for (const manifest of manifests) {
357
447
  const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
@@ -366,7 +456,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
366
456
  }
367
457
  const version = versions.get(row.package);
368
458
  if (version === undefined) {
369
- if (missing.has(row.package)) {
459
+ if (missing.has(row.package) || !declares(declared, row.package, REQUIRED_SECTIONS)) {
370
460
  continue;
371
461
  }
372
462
  missing.add(row.package);
@@ -413,6 +503,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
413
503
  const excludes = [
414
504
  ...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
415
505
  ].map(globExpression);
506
+ for (const below of new Set((row.generated ?? []).flatMap((one) => expand(one, resolved)))) {
507
+ generated.push({ dir: manifest.dir, row: row.name, below });
508
+ }
416
509
  for (const glob of new Set(row.entries.flatMap((entry) => expand(entry, resolved)))) {
417
510
  globs.push({
418
511
  dir: manifest.dir,
@@ -427,7 +520,23 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
427
520
  unique.sort((a, b) => compare(a.name, b.name) ||
428
521
  compare(a.manifest, b.manifest) ||
429
522
  compare(JSON.stringify(a), JSON.stringify(b)));
430
- return { applied: unique, globs, failures };
523
+ return { applied: unique, globs, generated, failures };
524
+ }
525
+ /**
526
+ * Every own file of one project below a directory an applied row names as generated, by
527
+ * its path below the target root, with the name of the first such row.
528
+ */
529
+ export function generatedFiles(conventions, files) {
530
+ const found = new Map();
531
+ for (const { dir, row, below } of conventions.generated) {
532
+ const root = joinPath(dir, below);
533
+ for (const [path, file] of files.byPath) {
534
+ if (!found.has(path) && relativePath(root, file.fileName) !== undefined) {
535
+ found.set(path, row);
536
+ }
537
+ }
538
+ }
539
+ return found;
431
540
  }
432
541
  /** Every own file of one project an applied row's globs match, once per row. */
433
542
  export function conventionEntries(conventions, files) {
@@ -187,7 +187,7 @@ function bindingsOf(node) {
187
187
  * shadows the name is not one. A string of a configuration file uses each dependency
188
188
  * the manifest beside the file declares that it spells.
189
189
  */
190
- export function projectNeeds(project, held, host, targetRoot) {
190
+ export function projectNeeds(project, held, host, targetRoot, parse) {
191
191
  const programPackages = new Set();
192
192
  for (const name of project.program.getSourceFileNames()) {
193
193
  const named = packageOfPath(name);
@@ -225,7 +225,7 @@ export function projectNeeds(project, held, host, targetRoot) {
225
225
  }
226
226
  return names;
227
227
  };
228
- const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot));
228
+ const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot), parse);
229
229
  for (const { file } of configuration.modules) {
230
230
  const names = declaredAt(file.fileName);
231
231
  for (const { node, text } of moduleStrings(file)) {
@@ -238,6 +238,12 @@ export function projectNeeds(project, held, host, targetRoot) {
238
238
  use([PROJECT_OWNER], dependenciesNamed(text, names));
239
239
  }
240
240
  }
241
+ for (const { file, manifestDir } of configuration.outside) {
242
+ const names = declaredAt(joinPath(manifestDir, "package.json"));
243
+ for (const { text } of moduleStrings(file)) {
244
+ use([PROJECT_OWNER], dependenciesNamed(text, names));
245
+ }
246
+ }
241
247
  return { packages, uses, unanswered };
242
248
  }
243
249
  /** The declarations of the inventory a use written at one node belongs to. */
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
9
  import { TypeFlags } from "@typescript/native/unstable/sync";
10
- import { isAnswered, UNANSWERED } from "./query.js";
10
+ import { isAnswered, propertyTables, UNANSWERED, } from "./query.js";
11
11
  /** The text a property name spells, or `undefined` for a computed name. */
12
12
  function nameText(node) {
13
13
  if (node === undefined) {
@@ -151,7 +151,7 @@ function sourceOf(target) {
151
151
  * read once per type for the whole project, since one type is destructured in many
152
152
  * places and a table answers every name of it.
153
153
  */
154
- export function destructuring(project, cap) {
154
+ export function destructuring(project, cap, properties = propertyTables(project.queries)) {
155
155
  const queries = project.queries;
156
156
  const tables = new Map();
157
157
  let patternBatches = 0;
@@ -163,10 +163,10 @@ export function destructuring(project, cap) {
163
163
  }
164
164
  let table = new Map();
165
165
  if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
166
- patternLookups += 1;
167
- const properties = queries.propertiesOf(type);
168
- table = isAnswered(properties)
169
- ? new Map(properties.map((property) => [property.name, property]))
166
+ const read = properties.of(type);
167
+ patternLookups += read.asked ? 1 : 0;
168
+ table = isAnswered(read.properties)
169
+ ? new Map(read.properties.map((property) => [property.name, property]))
170
170
  : UNANSWERED;
171
171
  }
172
172
  tables.set(type.id, table);
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
14
14
  import { SymbolFlags } from "@typescript/native/unstable/sync";
15
- import { configurationFiles, filesNamedBy, moduleStrings } from "./configuration-files.js";
15
+ import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
16
16
  import { globExpression } from "./glob.js";
17
17
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
18
18
  import { UNANSWERED } from "./query.js";
@@ -169,8 +169,8 @@ function isBrowserTestConfig(name) {
169
169
  * with what it exports, and each own file a relative string of a configuration module
170
170
  * or a JSON configuration file names, read against that file's directory.
171
171
  */
172
- function configurationEntries(host, targetRoot, files) {
173
- const { modules, documents } = configurationFiles(host, targetRoot, files);
172
+ function configurationEntries(host, targetRoot, files, parse) {
173
+ const { modules, documents, outside } = configurationFiles(host, targetRoot, files, parse);
174
174
  const found = [];
175
175
  const strings = (dir, texts) => {
176
176
  for (const text of texts) {
@@ -186,6 +186,9 @@ function configurationEntries(host, targetRoot, files) {
186
186
  for (const document of documents) {
187
187
  strings(dirnamePath(document.path), document.strings);
188
188
  }
189
+ for (const { file } of outside) {
190
+ strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
191
+ }
189
192
  return found;
190
193
  }
191
194
  /**
@@ -381,8 +384,8 @@ function workersOf(project, file, files) {
381
384
  * file's path below the target root; a file one of them matches is a test file the
382
385
  * runner executes.
383
386
  */
384
- export function entryPoints(project, files, testFiles, host, targetRoot) {
385
- const found = [...configurationEntries(host, targetRoot, files)];
387
+ export function entryPoints(project, files, testFiles, host, targetRoot, parse) {
388
+ const found = [...configurationEntries(host, targetRoot, files, parse)];
386
389
  const tests = testFiles.map((pattern) => ({ pattern, expression: globExpression(pattern) }));
387
390
  for (const [path, file] of files.byPath) {
388
391
  const name = basename(path);
@@ -26,7 +26,12 @@ export const EXEMPTION_CLASSES = [
26
26
  confidence: "certain",
27
27
  typescriptVisibility: { private: false, privateName: false },
28
28
  },
29
- { class: "generated-file", languages: ["go"], confidence: "certain" },
29
+ {
30
+ class: "generated-file",
31
+ languages: ["go", "ts"],
32
+ confidence: "certain",
33
+ typescriptVisibility: { private: true, privateName: false },
34
+ },
30
35
  { class: "linkname-cgo-asm-plugin", languages: ["go"], confidence: "certain" },
31
36
  {
32
37
  class: "template-field",