@astryxdesign/cli 0.7.2-canary.fc12a18 → 0.7.2-canary.fdd384f

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 (174) hide show
  1. package/README.md +9 -8
  2. package/api/build/help/help.mjs +1 -1
  3. package/api/component/_adapter.d.mts +41 -1
  4. package/api/component/_adapter.mjs +93 -7
  5. package/api/component/component.mjs +22 -5
  6. package/api/component/component.test.mjs +420 -167
  7. package/api/component/integration-export-name.test.mjs +644 -0
  8. package/api/component/list/list.mjs +42 -4
  9. package/api/docs/_adapter.d.mts +11 -0
  10. package/api/docs/_adapter.mjs +244 -1
  11. package/api/docs/integrationDocs.test.mjs +124 -0
  12. package/api/doctor/theme-checks.mjs +1 -1
  13. package/api/gap-report/gap-report.mjs +67 -4
  14. package/api/index.d.mts +2 -0
  15. package/api/index.mjs +2 -0
  16. package/api/init/run/run.mjs +2 -2
  17. package/api/integration/_adapter.d.mts +12 -0
  18. package/api/integration/_adapter.mjs +54 -0
  19. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  20. package/api/integration/add-contribution.mjs +8 -4
  21. package/api/integration/add-contribution.test.mjs +4 -2
  22. package/api/integration/authoring-checks.mjs +11 -2
  23. package/api/integration/authoring-checks.test.mjs +39 -0
  24. package/api/integration/authoring-checks.type.d.mts +9 -0
  25. package/api/integration/authoring-checks.type.mjs +14 -1
  26. package/api/integration/check/check.d.mts +96 -0
  27. package/api/integration/check/check.mjs +357 -0
  28. package/api/integration/check/check.test.mjs +489 -0
  29. package/api/integration/integration-check.type.d.mts +118 -0
  30. package/api/integration/integration-check.type.mjs +84 -0
  31. package/api/integration/integrationCheck.doc.d.mts +10 -0
  32. package/api/integration/integrationCheck.doc.mjs +73 -0
  33. package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
  34. package/api/integration/pack-check.mjs +61 -107
  35. package/api/integration/pack-check.test.mjs +359 -0
  36. package/api/integration/validate-integration.mjs +8 -0
  37. package/api/integration/validate-integration.test.mjs +11 -0
  38. package/api/integration/validate-integration.type.d.mts +8 -0
  39. package/api/integration/validate-integration.type.mjs +12 -1
  40. package/api/integration/validateIntegration.doc.mjs +17 -3
  41. package/api/search/search.d.mts +9 -0
  42. package/api/search/search.integration-check.test.mjs +24 -0
  43. package/api/search/search.mjs +108 -16
  44. package/api/search/search.test.mjs +36 -0
  45. package/api/swizzle/copy/copy.mjs +28 -14
  46. package/api/template/template.test.mjs +120 -3
  47. package/api/theme/build/build.family.test.mjs +64 -0
  48. package/api/theme/build/build.mjs +180 -24
  49. package/api/theme/themeBuild.doc.mjs +4 -4
  50. package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  51. package/assets/codemods/transforms/next/index.mjs +11 -1
  52. package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
  53. package/assets/docs/README.md +10 -0
  54. package/assets/docs/author-a-theme.doc.mjs +1 -1
  55. package/assets/docs/getting-started.doc.mjs +3 -3
  56. package/assets/docs/theme.doc.dense.mjs +1 -1
  57. package/assets/docs/theme.doc.mjs +1 -1
  58. package/assets/docs/theme.doc.zh.mjs +1 -1
  59. package/assets/docs/tree/add-a-component.doc.mjs +10 -0
  60. package/assets/docs/tree/block-template.doc.mjs +2 -2
  61. package/assets/docs/tree/check-the-props.doc.mjs +196 -0
  62. package/assets/docs/tree/check-your-docs.doc.mjs +44 -3
  63. package/assets/docs/tree/checks.doc.mjs +21 -13
  64. package/assets/docs/tree/component-family.doc.mjs +5 -1
  65. package/assets/docs/tree/component-imports.doc.mjs +8 -0
  66. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  67. package/assets/docs/tree/links.doc.mjs +2 -2
  68. package/assets/docs/tree/page-template.doc.mjs +1 -1
  69. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  70. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  71. package/assets/docs/tree/single-component.doc.mjs +3 -2
  72. package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
  73. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  74. package/assets/docs/tree/versioning.doc.mjs +1 -0
  75. package/assets/docs/tree/write-the-template-file.doc.mjs +2 -1
  76. package/assets/docs/use-a-theme.doc.mjs +2 -2
  77. package/assets/docs/working-with-ai.doc.mjs +43 -4
  78. package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
  79. package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
  80. package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
  81. package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
  82. package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
  83. package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
  84. package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
  85. package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
  86. package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
  87. package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
  88. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
  89. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
  90. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
  91. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
  92. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
  93. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
  94. package/assets/templates/pages/detail-page/page.tsx +3 -3
  95. package/assets/templates/pages/table-inbox/page.tsx +10 -8
  96. package/assets/theme.template.ts +1 -1
  97. package/authoring/doctypes/base/type.ts +6 -0
  98. package/authoring/doctypes/component/component.doc.mjs +4 -4
  99. package/authoring/doctypes/component/type.ts +6 -3
  100. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  101. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  102. package/authoring/doctypes/template/parse.d.mts +6 -2
  103. package/authoring/doctypes/template/parse.mjs +3 -1
  104. package/authoring/doctypes/template/template.doc.mjs +1 -1
  105. package/authoring/doctypes/template/type.ts +22 -0
  106. package/authoring/index.d.ts +1 -0
  107. package/authoring/integration/integration.doc.mjs +8 -1
  108. package/authoring/integration/parse.test.mjs +10 -0
  109. package/authoring/integration/schema.d.mts +2 -0
  110. package/authoring/integration/schema.mjs +1 -0
  111. package/authoring/integration/type.ts +4 -0
  112. package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
  113. package/clients/cli/commands/build-theme.mjs +1 -1
  114. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  115. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  116. package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
  117. package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
  118. package/clients/cli/commands/doctor-integration.test.mjs +837 -0
  119. package/clients/cli/commands/doctor.mjs +190 -0
  120. package/clients/cli/commands/integration-check.doc.mjs +85 -0
  121. package/clients/cli/commands/integration-check.test.mjs +267 -0
  122. package/clients/cli/commands/integration.doc.mjs +6 -2
  123. package/clients/cli/commands/integration.mjs +107 -1
  124. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  125. package/clients/cli/commands/theme-build.doc.mjs +2 -2
  126. package/clients/cli/index.mjs +1 -0
  127. package/clients/cli/lib/component-format.mjs +5 -1
  128. package/foundation/config/project.mjs +82 -0
  129. package/foundation/discovery/authoring-self-docs.mjs +1 -0
  130. package/foundation/discovery/component-discovery.d.mts +190 -10
  131. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  132. package/foundation/discovery/component-discovery.mjs +864 -56
  133. package/foundation/discovery/component-discovery.test.mjs +96 -0
  134. package/foundation/discovery/component-replacement.mjs +18 -0
  135. package/foundation/discovery/component-replacement.test.mjs +40 -0
  136. package/foundation/discovery/doc-types.d.mts +157 -0
  137. package/foundation/discovery/doc-types.mjs +1224 -0
  138. package/foundation/discovery/doc-types.test.mjs +652 -0
  139. package/foundation/discovery/docs-discovery.d.mts +81 -0
  140. package/foundation/discovery/docs-discovery.mjs +57 -0
  141. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  142. package/foundation/discovery/integration-doc-files.mjs +231 -0
  143. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  144. package/foundation/discovery/module-exports.d.mts +27 -0
  145. package/foundation/discovery/module-exports.mjs +356 -31
  146. package/foundation/discovery/module-exports.test.mjs +172 -0
  147. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  148. package/foundation/discovery/package-tsconfig.mjs +228 -0
  149. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  150. package/foundation/discovery/project-typescript.d.mts +23 -0
  151. package/foundation/discovery/project-typescript.mjs +61 -0
  152. package/foundation/discovery/prop-drift.d.mts +278 -0
  153. package/foundation/discovery/prop-drift.mjs +2263 -0
  154. package/foundation/discovery/prop-drift.test.mjs +2219 -0
  155. package/foundation/discovery/template-adapter.mjs +74 -5
  156. package/foundation/discovery/type-match.d.mts +53 -0
  157. package/foundation/discovery/type-match.mjs +660 -0
  158. package/foundation/discovery/type-match.test.mjs +169 -0
  159. package/foundation/discovery/type-text.d.mts +68 -0
  160. package/foundation/discovery/type-text.mjs +658 -0
  161. package/foundation/discovery/type-text.test.mjs +196 -0
  162. package/foundation/discovery/unresolved-references.d.mts +62 -0
  163. package/foundation/discovery/unresolved-references.mjs +790 -0
  164. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  165. package/foundation/doc-compiler/doc-loads.test.mjs +11 -1
  166. package/foundation/doc-compiler/inputs.mjs +23 -0
  167. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  168. package/foundation/doc-compiler/links.d.mts +9 -5
  169. package/foundation/doc-compiler/links.mjs +4 -2
  170. package/foundation/integrations/contribution-inventory.mjs +6 -1
  171. package/foundation/response/response-types.doc.mjs +7 -2
  172. package/foundation/xle/registry.mjs +4 -0
  173. package/foundation/xle/registry.test.mjs +28 -0
  174. package/package.json +10 -10
@@ -0,0 +1,790 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Find the references in a package's TypeScript project that the
5
+ * compiler cannot resolve, so a check that reads the package's types knows
6
+ * when part of them is a guess: a dependency that is not installed can make
7
+ * any type built from it the compiler's error type, whatever the code looks
8
+ * like.
9
+ *
10
+ * - From given files: their imports, re-exports, `/// <reference>`
11
+ * directives and side-effect imports, and the files those reach in turn;
12
+ * and in the package's own files, each type the compiler can only give its
13
+ * error type.
14
+ * - From the program's global roots: the package's own declaration files
15
+ * that act globally and the type packages the program loads by name
16
+ * (`compilerOptions.types`).
17
+ *
18
+ * @input The project's own `typescript`, a program and its checker.
19
+ * @output The first reference that does not resolve, or null.
20
+ * @position Foundation discovery, used by the documented-props comparison.
21
+ */
22
+
23
+ import * as path from 'node:path';
24
+
25
+ /**
26
+ * What the walk learns about one program for one `mine`: each source file's
27
+ * references (the first that doesn't resolve, the first type it can't give,
28
+ * the files they reach), what each declaration's type is inferred from, and
29
+ * the declarations whose whole reach reads clean. Each is a fact about the
30
+ * program, so an answer never depends on what an earlier call read; the
31
+ * record goes when the program does.
32
+ * @type {WeakMap<any, WeakMap<Function, {files: Map<any, any>, local: Map<any, any>, clean: Set<any>, untypedNames: Map<any, Set<string>>, errors: Map<any, any[]>}>>}
33
+ */
34
+ const PROGRAMS = new WeakMap();
35
+
36
+ /**
37
+ * @typedef {object} UnresolvedReference
38
+ * @property {string} name the reference as written, quoted
39
+ * @property {string} file the file that writes it
40
+ * @property {'imported' | 'used' | 'named'} how a file imports it, a file
41
+ * uses it as a type name, or the config names it
42
+ */
43
+
44
+ /**
45
+ * The JSDoc tags whose types the compiler reads in a JavaScript file, in the
46
+ * TypeScript versions that have them. It never checks the others (`@throws`,
47
+ * `@exception`, `@see`, `@example` …), so a name declared nowhere in one
48
+ * decides no type.
49
+ */
50
+ const TYPED_JSDOC_TAGS = [
51
+ 'JSDocTypeTag',
52
+ 'JSDocParameterTag',
53
+ 'JSDocReturnTag',
54
+ 'JSDocTypedefTag',
55
+ 'JSDocCallbackTag',
56
+ 'JSDocTemplateTag',
57
+ 'JSDocPropertyTag',
58
+ 'JSDocThisTag',
59
+ 'JSDocEnumTag',
60
+ 'JSDocAugmentsTag',
61
+ 'JSDocImplementsTag',
62
+ 'JSDocSatisfiesTag',
63
+ 'JSDocOverloadTag',
64
+ 'JSDocImportTag',
65
+ ];
66
+
67
+ /**
68
+ * The first reference that does not resolve from the given files, the files
69
+ * they import, and so on, as far as `follow` allows. A reference fails when
70
+ * the program finds no module, no type package or no file for it, when a
71
+ * module does not export a name imported from it, or, in the files `mine`
72
+ * accepts, when a type name resolves to no declaration (TS2304). A module
73
+ * found without types (a JavaScript file) resolves: its names are `any`. A
74
+ * side-effect import counts where the compiler checks one
75
+ * (`noUncheckedSideEffectImports`, on by default from TypeScript 6).
76
+ * @param {any} ts the project's own `typescript`
77
+ * @param {any} program
78
+ * @param {any} checker
79
+ * @param {any[]} roots source files
80
+ * @param {(file: string) => boolean} follow which files it reads past them
81
+ * @param {(file: string) => boolean} mine whose type names it checks
82
+ * @returns {UnresolvedReference | null}
83
+ */
84
+ export function unresolvedReference(ts, program, checker, roots, follow, mine) {
85
+ const K = ts.SyntaxKind;
86
+ const typedTags = new Set(
87
+ TYPED_JSDOC_TAGS.map(name => K[name]).filter(kind => kind !== undefined),
88
+ );
89
+ const STARTS = new Set([
90
+ K.TypeReference,
91
+ K.TypeQuery,
92
+ K.IndexedAccessType,
93
+ K.ImportType,
94
+ K.ExpressionWithTypeArguments,
95
+ ]);
96
+ const options = program.getCompilerOptions();
97
+ const sideEffects =
98
+ options.noUncheckedSideEffectImports ??
99
+ Number.parseInt(ts.versionMajorMinor, 10) >= 6;
100
+ let byMine = PROGRAMS.get(program);
101
+ if (!byMine) {
102
+ byMine = new WeakMap();
103
+ PROGRAMS.set(program, byMine);
104
+ }
105
+ let state = byMine.get(mine);
106
+ if (!state) {
107
+ state = {
108
+ files: new Map(),
109
+ local: new Map(),
110
+ clean: new Set(),
111
+ untypedNames: new Map(),
112
+ errors: new Map(),
113
+ };
114
+ byMine.set(mine, state);
115
+ }
116
+ const record = state;
117
+ /**
118
+ * The names a file imports from modules found without types.
119
+ * @type {(file: any) => Set<string>}
120
+ */
121
+ const untypedNamesOf = file => {
122
+ let names = record.untypedNames.get(file);
123
+ if (names) return names;
124
+ names = new Set();
125
+ for (const literal of file.imports ?? []) {
126
+ if (literal.pos < 0 || checker.getSymbolAtLocation(literal)) continue;
127
+ if (
128
+ ts.resolveModuleName(literal.text, file.fileName, options, ts.sys)
129
+ .resolvedModule
130
+ ) {
131
+ for (const name of boundNames(ts, literal.parent)) names.add(name);
132
+ }
133
+ }
134
+ record.untypedNames.set(file, names);
135
+ return names;
136
+ };
137
+ /**
138
+ * Whether `tsc` reports an error inside a node. It leaves a line
139
+ * `@ts-expect-error` covers alone, and a `@ts-nocheck` file, and without
140
+ * `noImplicitAny` it gives some reads the error type with no diagnostic.
141
+ * @type {(file: any, node: any) => boolean}
142
+ */
143
+ const errorReported = (file, node) => {
144
+ const errors =
145
+ record.errors.get(file) ??
146
+ program
147
+ .getSemanticDiagnostics(file)
148
+ .filter(
149
+ (/** @type {any} */ d) => d.category === ts.DiagnosticCategory.Error,
150
+ );
151
+ record.errors.set(file, errors);
152
+ const start = node.getStart(file);
153
+ return errors.some(
154
+ (/** @type {any} */ d) =>
155
+ d.start !== undefined && d.start >= start && d.start < node.end,
156
+ );
157
+ };
158
+ /**
159
+ * Whether a function's type comes from overload signatures.
160
+ * @type {(fn: any) => boolean}
161
+ */
162
+ const overloaded = fn => {
163
+ const symbol = fn.name && checker.getSymbolAtLocation(fn.name);
164
+ return (symbol?.declarations ?? []).some(
165
+ (/** @type {any} */ d) => d !== fn && ts.isFunctionLike(d) && !d.body,
166
+ );
167
+ };
168
+ /**
169
+ * The expressions a declaration's type is inferred from: an initializer
170
+ * with no type written, what a function returns when its return type
171
+ * isn't written, and a parameter's default. A class's names come from
172
+ * its written members and its base; a member's type is read where a type
173
+ * indexes it. A type written down, or overloads, decide it alone.
174
+ * @type {(declaration: any) => any[]}
175
+ */
176
+ const typeSources = declaration => {
177
+ /** @type {any[]} */
178
+ const sources = [];
179
+ const fromFunction = (/** @type {any} */ fn) => {
180
+ if (overloaded(fn)) return;
181
+ for (const parameter of fn.parameters ?? []) {
182
+ if (!parameter.type && parameter.initializer) {
183
+ sources.push(parameter.initializer);
184
+ }
185
+ }
186
+ if (fn.type || !fn.body) return;
187
+ if (!ts.isBlock(fn.body)) {
188
+ sources.push(fn.body);
189
+ return;
190
+ }
191
+ // A function or class nested in the body returns for itself.
192
+ const visit = (/** @type {any} */ n) => {
193
+ if (ts.isFunctionLike(n) || ts.isClassLike(n)) return;
194
+ if (ts.isReturnStatement(n) && n.expression) sources.push(n.expression);
195
+ ts.forEachChild(n, visit);
196
+ };
197
+ ts.forEachChild(fn.body, visit);
198
+ };
199
+ if (ts.isFunctionLike(declaration)) {
200
+ fromFunction(declaration);
201
+ } else if (ts.isClassLike(declaration)) {
202
+ for (const clause of declaration.heritageClauses ?? []) {
203
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
204
+ for (const base of clause.types) sources.push(base.expression);
205
+ }
206
+ }
207
+ } else if (ts.isBindingElement(declaration)) {
208
+ if (declaration.initializer) sources.push(declaration.initializer);
209
+ let root = declaration.parent;
210
+ while (root && !ts.isVariableDeclaration(root) && !ts.isParameter(root)) {
211
+ root = root.parent;
212
+ }
213
+ if (root && !root.type && root.initializer) {
214
+ sources.push(root.initializer);
215
+ }
216
+ } else if (!declaration.type && declaration.initializer) {
217
+ sources.push(declaration.initializer);
218
+ }
219
+ return sources;
220
+ };
221
+ /**
222
+ * What one declaration's type is inferred from, read on its own: the
223
+ * first value read there that the compiler can only give its error type
224
+ * and `tsc` reports, and the declarations it names, whose types count
225
+ * too.
226
+ * @type {(declaration: any) => {found: {node: any, file: any} | null, names: any[]}}
227
+ */
228
+ const localScan = declaration => {
229
+ const known = record.local.get(declaration);
230
+ if (known) return known;
231
+ const file = declaration.getSourceFile();
232
+ const untypedHere = untypedNamesOf(file);
233
+ /** @type {any[]} */
234
+ const names = [];
235
+ /** @type {{node: any, file: any} | null} */
236
+ let found = null;
237
+ /** @type {(node: any) => void} */
238
+ const scan = node => {
239
+ if (found) return;
240
+ // A JSX element is a `JSX.Element`, whatever it holds.
241
+ if (
242
+ ts.isJsxElement(node) ||
243
+ ts.isJsxSelfClosingElement(node) ||
244
+ ts.isJsxFragment(node)
245
+ ) {
246
+ return;
247
+ }
248
+ // A cast that writes the type decides it, whatever the value.
249
+ if (
250
+ (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) &&
251
+ !ts.isConstTypeReference(node.type)
252
+ ) {
253
+ return;
254
+ }
255
+ if (ts.isFunctionLike(node) || ts.isClassLike(node)) {
256
+ for (const source of typeSources(node)) scan(source);
257
+ return;
258
+ }
259
+ ts.forEachChild(node, scan);
260
+ if (found) return;
261
+ if (
262
+ readsValue(ts, node) &&
263
+ !untypedHere.has(rootName(ts, node)) &&
264
+ isErrorValue(checker, node) &&
265
+ errorReported(file, node)
266
+ ) {
267
+ found = {node, file};
268
+ return;
269
+ }
270
+ // A name for another declaration: its type comes from there.
271
+ if (!ts.isIdentifier(node)) return;
272
+ let symbol =
273
+ ts.isShorthandPropertyAssignment(node.parent) &&
274
+ node.parent.name === node
275
+ ? checker.getShorthandAssignmentValueSymbol(node.parent)
276
+ : checker.getSymbolAtLocation(node);
277
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
278
+ symbol = checker.getAliasedSymbol(symbol);
279
+ }
280
+ for (const named of symbol?.declarations ?? []) {
281
+ if (!names.includes(named)) names.push(named);
282
+ }
283
+ };
284
+ for (const source of typeSources(declaration)) {
285
+ scan(source);
286
+ if (found) break;
287
+ }
288
+ const result = {found, names};
289
+ record.local.set(declaration, result);
290
+ return result;
291
+ };
292
+ /**
293
+ * The first such read in what the declarations' types are inferred from,
294
+ * in the package's own TypeScript files, following the declarations each
295
+ * names, depth first. A declaration whose whole reach read clean before
296
+ * is skipped, which changes no answer.
297
+ * @type {(declarations: readonly any[]) => {node: any, file: any} | null}
298
+ */
299
+ const scanDeclarations = declarations => {
300
+ const visited = new Set();
301
+ /** @type {(list: readonly any[]) => {node: any, file: any} | null} */
302
+ const walk = list => {
303
+ for (const declaration of list) {
304
+ const file = declaration.getSourceFile();
305
+ if (
306
+ visited.has(declaration) ||
307
+ record.clean.has(declaration) ||
308
+ file.isDeclarationFile ||
309
+ !/\.[cm]?tsx?$/.test(file.fileName) ||
310
+ !mine(file.fileName)
311
+ ) {
312
+ continue;
313
+ }
314
+ visited.add(declaration);
315
+ const {found, names} = localScan(declaration);
316
+ const reached = found ?? walk(names);
317
+ if (reached) return reached;
318
+ }
319
+ return null;
320
+ };
321
+ const found = walk(declarations);
322
+ if (!found)
323
+ for (const declaration of visited) record.clean.add(declaration);
324
+ return found;
325
+ };
326
+ /** @type {(sf: any) => {missing: UnresolvedReference | null, untyped: UnresolvedReference | null, next: any[]}} */
327
+ const read = sf => {
328
+ const cache = record.files;
329
+ if (cache.has(sf)) return cache.get(sf);
330
+ /** @type {UnresolvedReference | null} */
331
+ let missing = null;
332
+ /** @type {any[]} */
333
+ const next = [];
334
+ /** @type {(name: string, how?: UnresolvedReference['how']) => void} */
335
+ const fail = (name, how = 'imported') => {
336
+ missing ??= {name, file: sf.fileName, how};
337
+ };
338
+ // A module found without types gives its names no types: they are
339
+ // `any`, not missing.
340
+ /** @type {Set<string>} */
341
+ const untypedNames = new Set();
342
+ for (const literal of sf.imports ?? []) {
343
+ const parent = literal.parent;
344
+ // The JSX runtime import the compiler adds itself shapes no types.
345
+ if (!parent || missing || literal.pos < 0) continue;
346
+ if (
347
+ ts.isImportDeclaration(parent) &&
348
+ !parent.importClause &&
349
+ !sideEffects
350
+ ) {
351
+ continue;
352
+ }
353
+ const module = checker.getSymbolAtLocation(literal);
354
+ if (!module) {
355
+ const found = ts.resolveModuleName(
356
+ literal.text,
357
+ sf.fileName,
358
+ options,
359
+ ts.sys,
360
+ ).resolvedModule;
361
+ if (!found) fail(`\`${literal.text}\``);
362
+ else for (const name of boundNames(ts, parent)) untypedNames.add(name);
363
+ continue;
364
+ }
365
+ for (const d of module.declarations ?? []) next.push(d.getSourceFile());
366
+ // Each name an import or a re-export takes from the module. One it
367
+ // does not export resolves to the compiler's `unknown` symbol.
368
+ const clause = ts.isImportDeclaration(parent)
369
+ ? parent.importClause
370
+ : null;
371
+ const bound = clause?.namedBindings;
372
+ const exported = ts.isExportDeclaration(parent)
373
+ ? parent.exportClause
374
+ : null;
375
+ const names = [
376
+ ...(clause?.name ? [clause.name] : []),
377
+ ...(bound && ts.isNamedImports(bound) ? bound.elements : []),
378
+ ...(exported && ts.isNamedExports(exported) ? exported.elements : []),
379
+ ].map(/** @param {any} n */ n => n.name ?? n);
380
+ for (const name of names) {
381
+ const alias = checker.getSymbolAtLocation(name);
382
+ const resolved = alias && checker.getAliasedSymbol(alias);
383
+ if (
384
+ !resolved ||
385
+ (resolved.escapedName === 'unknown' && !resolved.declarations?.length)
386
+ ) {
387
+ fail(`\`${name.text}\` from \`${literal.text}\``);
388
+ }
389
+ }
390
+ }
391
+ for (const ref of sf.typeReferenceDirectives ?? []) {
392
+ const found = ts.resolveTypeReferenceDirective(
393
+ ref.fileName,
394
+ sf.fileName,
395
+ options,
396
+ ts.sys,
397
+ ).resolvedTypeReferenceDirective?.resolvedFileName;
398
+ if (!found) fail(`the types \`${ref.fileName}\``);
399
+ else if (program.getSourceFile(found)) {
400
+ next.push(program.getSourceFile(found));
401
+ }
402
+ }
403
+ for (const ref of sf.referencedFiles ?? []) {
404
+ const file = path.resolve(path.dirname(sf.fileName), ref.fileName);
405
+ const found = ['', '.ts', '.tsx', '.d.ts']
406
+ .map(ext => program.getSourceFile(file + ext))
407
+ .find(Boolean);
408
+ if (found) next.push(found);
409
+ else fail(`\`${ref.fileName}\``);
410
+ }
411
+ // A type the compiler can only give its error type: a name or value
412
+ // declared nowhere, a property that doesn't exist. `keyof` or a template
413
+ // literal key over one hides the error, so it is found where it starts.
414
+ // `as const` is a type reference named `const`. A JavaScript file's
415
+ // JSDoc types count too, in the tags the compiler reads types from;
416
+ // TypeScript reads no types from a `.ts` file's.
417
+ /** @type {UnresolvedReference | null} */
418
+ let untyped = null;
419
+ const js = /\.[cm]?jsx?$/.test(sf.fileName);
420
+ // A name, or an `import()` type, from a module found without types.
421
+ /** @type {(node: any) => boolean} */
422
+ const fromUntyped = node => {
423
+ if (ts.isImportTypeNode(node)) {
424
+ const literal = node.argument?.literal;
425
+ return (
426
+ !!literal &&
427
+ ts.isStringLiteral(literal) &&
428
+ !checker.getSymbolAtLocation(literal) &&
429
+ !!ts.resolveModuleName(literal.text, sf.fileName, options, ts.sys)
430
+ .resolvedModule
431
+ );
432
+ }
433
+ let name = ts.isTypeQueryNode(node)
434
+ ? node.exprName
435
+ : ts.isTypeReferenceNode(node)
436
+ ? node.typeName
437
+ : null;
438
+ while (name && ts.isQualifiedName(name)) name = name.left;
439
+ return !!name && ts.isIdentifier(name) && untypedNames.has(name.text);
440
+ };
441
+ // A value `typeof` reads, or a class a type names, in a TypeScript file
442
+ // of the package's own, whose type is inferred from a value the compiler
443
+ // can't type: a property that doesn't exist, spread into an object,
444
+ // turns it `any`, so `keyof typeof` it takes any name. Only an error
445
+ // `tsc` reports counts.
446
+ /** @type {(name: any, classes?: boolean) => boolean} */
447
+ const readsErrorValue = (name, classes = false) => {
448
+ let target = checker.getSymbolAtLocation(name);
449
+ if (target && target.flags & ts.SymbolFlags.Alias) {
450
+ target = checker.getAliasedSymbol(target);
451
+ }
452
+ const declarations = (target?.declarations ?? []).filter(
453
+ (/** @type {any} */ d) => !classes || ts.isClassLike(d),
454
+ );
455
+ const found = scanDeclarations(declarations);
456
+ if (found) {
457
+ untyped = {
458
+ name: `\`${oneLine(found.node.getText(found.file))}\``,
459
+ file: found.file.fileName,
460
+ how: 'used',
461
+ };
462
+ }
463
+ return false;
464
+ };
465
+ // The members a type indexes, such as `Store['slots']`: a member's
466
+ // type is inferred from its own initializer or body.
467
+ /** @type {(node: any) => boolean} */
468
+ const readsIndexedMembers = node => {
469
+ const object = checker.getTypeFromTypeNode(node.objectType);
470
+ const index = checker.getTypeFromTypeNode(node.indexType);
471
+ const keys = (index.isUnion() ? index.types : [index]).filter(
472
+ (/** @type {any} */ key) =>
473
+ key.isStringLiteral() || key.isNumberLiteral(),
474
+ );
475
+ const members = keys.map((/** @type {any} */ key) =>
476
+ checker.getPropertyOfType(object, String(key.value)),
477
+ );
478
+ const found = scanDeclarations(
479
+ members.flatMap(
480
+ (/** @type {any} */ member) => member?.declarations ?? [],
481
+ ),
482
+ );
483
+ if (found) {
484
+ untyped = {
485
+ name: `\`${oneLine(found.node.getText(found.file))}\``,
486
+ file: found.file.fileName,
487
+ how: 'used',
488
+ };
489
+ }
490
+ return false;
491
+ };
492
+ // A class's base, in its `extends` clause or a JavaScript file's
493
+ // `@augments` tag, that names nothing: the compiler can give the class no
494
+ // type, so it has no props to read. A base that resolves is read as a
495
+ // value below.
496
+ /** @type {(node: any) => boolean} */
497
+ const baseless = node =>
498
+ isValueWithTypeArguments(ts, node) &&
499
+ (ts.isHeritageClause(node.parent) ||
500
+ ts.isJSDocAugmentsTag(node.parent)) &&
501
+ ts.isEntityNameExpression(node.expression) &&
502
+ !checker.getSymbolAtLocation(node.expression);
503
+ // The innermost such type names it: its parts are read first.
504
+ /** @type {(node: any) => void} */
505
+ const visit = node => {
506
+ if (untyped) return;
507
+ for (const doc of js ? (node.jsDoc ?? []) : []) {
508
+ for (const tag of doc.tags ?? []) {
509
+ if (typedTags.has(tag.kind)) visit(tag);
510
+ }
511
+ }
512
+ ts.forEachChild(node, visit);
513
+ if (!untyped && baseless(node)) {
514
+ untyped = {
515
+ name: `\`${node.expression.getText(sf)}\``,
516
+ file: sf.fileName,
517
+ how: 'used',
518
+ };
519
+ }
520
+ if (
521
+ !untyped &&
522
+ ((STARTS.has(node.kind) &&
523
+ !(ts.isTypeReferenceNode(node) && ts.isConstTypeReference(node)) &&
524
+ !isValueWithTypeArguments(ts, node) &&
525
+ !fromUntyped(node) &&
526
+ isErrorType(checker, node)) ||
527
+ (ts.isTypeQueryNode(node) && readsErrorValue(node.exprName)) ||
528
+ (ts.isIndexedAccessTypeNode(node) && readsIndexedMembers(node)) ||
529
+ // A class a type names: its instance type is inferred too.
530
+ (ts.isTypeReferenceNode(node) &&
531
+ readsErrorValue(node.typeName, true)) ||
532
+ (ts.isExpressionWithTypeArguments(node) &&
533
+ readsErrorValue(node.expression, true)))
534
+ ) {
535
+ untyped = {
536
+ name: `\`${node.getText(sf)}\``,
537
+ file: sf.fileName,
538
+ how: 'used',
539
+ };
540
+ }
541
+ };
542
+ if (!missing && mine(sf.fileName)) visit(sf);
543
+ const entry = {missing, untyped, next};
544
+ cache.set(sf, entry);
545
+ return entry;
546
+ };
547
+ // A reference that doesn't resolve, anywhere the walk goes, names what to
548
+ // install; a type the compiler can't give is often only where it shows.
549
+ /** @type {UnresolvedReference | null} */
550
+ let firstUntyped = null;
551
+ const seen = new Set();
552
+ const queue = roots.filter(Boolean);
553
+ while (queue.length > 0) {
554
+ const sf = queue.shift();
555
+ if (seen.has(sf)) continue;
556
+ seen.add(sf);
557
+ const {missing, untyped, next} = read(sf);
558
+ if (missing) return missing;
559
+ firstUntyped ??= untyped;
560
+ queue.push(...next.filter(file => follow(file.fileName)));
561
+ }
562
+ return firstUntyped;
563
+ }
564
+
565
+ /**
566
+ * The local names an import binds: its default, its namespace, the names it
567
+ * lists, or an `import x = require()`.
568
+ * @param {any} ts
569
+ * @param {any} parent the import's declaration
570
+ * @returns {string[]}
571
+ */
572
+ function boundNames(ts, parent) {
573
+ if (ts.isExternalModuleReference(parent)) return [parent.parent.name.text];
574
+ const clause = ts.isImportDeclaration(parent) ? parent.importClause : null;
575
+ const bound = clause?.namedBindings;
576
+ return [
577
+ ...(clause?.name ? [clause.name.text] : []),
578
+ ...(bound && ts.isNamespaceImport(bound) ? [bound.name.text] : []),
579
+ ...(bound && ts.isNamedImports(bound)
580
+ ? bound.elements.map(/** @param {any} e */ e => e.name.text)
581
+ : []),
582
+ ];
583
+ }
584
+
585
+ /**
586
+ * Whether an import takes any names; one that takes none loads its module
587
+ * for what that does globally, as a side-effect import does.
588
+ * @param {any} ts
589
+ * @param {any} statement
590
+ */
591
+ function importsNames(ts, statement) {
592
+ const clause = statement.importClause;
593
+ const bound = clause?.namedBindings;
594
+ return (
595
+ !!clause &&
596
+ (!!clause.name ||
597
+ (!!bound && (!ts.isNamedImports(bound) || bound.elements.length > 0)))
598
+ );
599
+ }
600
+
601
+ /**
602
+ * Whether a node gives type arguments to a value, not a type name: the base
603
+ * a class extends, or an instantiation expression (`select<string>`).
604
+ * @param {any} ts
605
+ * @param {any} node
606
+ */
607
+ function isValueWithTypeArguments(ts, node) {
608
+ if (!ts.isExpressionWithTypeArguments(node)) return false;
609
+ const clause = node.parent;
610
+ if (!ts.isHeritageClause(clause)) return true;
611
+ return (
612
+ clause.token === ts.SyntaxKind.ExtendsKeyword &&
613
+ (ts.isClassDeclaration(clause.parent) ||
614
+ ts.isClassExpression(clause.parent))
615
+ );
616
+ }
617
+
618
+ /**
619
+ * Source text on one line, comments dropped, for a reason.
620
+ * @param {string} text
621
+ */
622
+ function oneLine(text) {
623
+ return text
624
+ .replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, '')
625
+ .replace(/\s+/g, ' ')
626
+ .trim();
627
+ }
628
+
629
+ /**
630
+ * Whether a node reads a value: a property or element access, a call, or a
631
+ * name spread, assigned, passed or returned. A name that only declares or
632
+ * imports something is not read here, and may stand for a type.
633
+ * @param {any} ts
634
+ * @param {any} node
635
+ */
636
+ function readsValue(ts, node) {
637
+ if (
638
+ ts.isPropertyAccessExpression(node) ||
639
+ ts.isElementAccessExpression(node) ||
640
+ ts.isCallExpression(node)
641
+ ) {
642
+ return true;
643
+ }
644
+ if (!ts.isIdentifier(node)) return false;
645
+ const parent = node.parent;
646
+ return (
647
+ ts.isSpreadAssignment(parent) ||
648
+ ts.isSpreadElement(parent) ||
649
+ ts.isJsxSpreadAttribute(parent) ||
650
+ ts.isShorthandPropertyAssignment(parent) ||
651
+ ((ts.isVariableDeclaration(parent) || ts.isPropertyAssignment(parent)) &&
652
+ parent.initializer === node) ||
653
+ (ts.isCallExpression(parent) && parent.arguments.includes(node)) ||
654
+ ts.isReturnStatement(parent) ||
655
+ ts.isArrayLiteralExpression(parent) ||
656
+ ts.isParenthesizedExpression(parent) ||
657
+ ts.isAsExpression(parent) ||
658
+ ts.isConditionalExpression(parent) ||
659
+ ts.isJsxExpression(parent) ||
660
+ ts.isExportAssignment(parent)
661
+ );
662
+ }
663
+
664
+ /**
665
+ * The name an access or call starts from: `fmt` in `fmt.parse(text)`.
666
+ * @param {any} ts
667
+ * @param {any} node
668
+ * @returns {string}
669
+ */
670
+ function rootName(ts, node) {
671
+ let root = node;
672
+ while (
673
+ ts.isPropertyAccessExpression(root) ||
674
+ ts.isElementAccessExpression(root) ||
675
+ ts.isCallExpression(root)
676
+ ) {
677
+ root = root.expression;
678
+ }
679
+ return ts.isIdentifier(root) ? root.text : '';
680
+ }
681
+
682
+ /**
683
+ * Whether the compiler can only give a value its error type.
684
+ * @param {any} checker
685
+ * @param {any} node
686
+ */
687
+ function isErrorValue(checker, node) {
688
+ try {
689
+ return checker.getTypeAtLocation(node).intrinsicName === 'error';
690
+ } catch {
691
+ return false;
692
+ }
693
+ }
694
+
695
+ /**
696
+ * Whether the compiler can only give a type node its error type.
697
+ * @param {any} checker
698
+ * @param {any} node
699
+ */
700
+ function isErrorType(checker, node) {
701
+ try {
702
+ return checker.getTypeFromTypeNode(node).intrinsicName === 'error';
703
+ } catch {
704
+ return false;
705
+ }
706
+ }
707
+
708
+ /**
709
+ * The first reference that does not resolve from the program's global roots,
710
+ * whose types every file sees: a type package the program loads by name
711
+ * (`compilerOptions.types`, `*` expanded as TypeScript does, or every
712
+ * package in `typeRoots` where TypeScript loads them all) that cannot be
713
+ * found, or a reference in a declaration file the package's own config
714
+ * includes, wherever it sits, or in the package's own type packages, or in a
715
+ * file they import. Of those, only a file that acts globally starts the walk:
716
+ * a script, `declare global`, `declare module`, `export as namespace`, an
717
+ * import that takes no names (a side-effect import, `import {} from`), a
718
+ * bare `export *`, or a `/// <reference>`. A config from above the package,
719
+ * such as a monorepo's root config, may include the whole repository, so
720
+ * only the package's own declaration files start the walk then. A
721
+ * dependency's declarations (below `node_modules`) are not followed: `tsc`
722
+ * skips their errors (`skipLibCheck`), and they reach a component only
723
+ * through the globals they declare.
724
+ * @param {any} ts the project's own `typescript`
725
+ * @param {any} program
726
+ * @param {any} checker
727
+ * @param {string[]} declarations the declaration files the config includes
728
+ * @param {any} options the program's compiler options
729
+ * @param {(file: string) => boolean} isOwnFile whether a file is the
730
+ * package's own
731
+ * @returns {UnresolvedReference | null}
732
+ */
733
+ export function unresolvedGlobals(
734
+ ts,
735
+ program,
736
+ checker,
737
+ declarations,
738
+ options,
739
+ isOwnFile,
740
+ ) {
741
+ const config =
742
+ options.configFilePath ??
743
+ path.join(ts.sys.getCurrentDirectory(), 'tsconfig.json');
744
+ /** @type {(file: string) => boolean} */
745
+ const follow = file =>
746
+ isOwnFile(file) || !/[\\/]node_modules[\\/]/.test(file);
747
+ const own = isOwnFile(config);
748
+ // A declaration file reaches a component through what it does globally: a
749
+ // script, `declare global`, `declare module`, `export as namespace`, or an
750
+ // import that takes no names, a bare `export *` or a `/// <reference>`, made
751
+ // for what those load. A module that does none of these counts only where a
752
+ // component imports it.
753
+ /** @type {(sf: any) => boolean} */
754
+ const global = sf =>
755
+ sf &&
756
+ (!ts.isExternalModule(sf) ||
757
+ sf.referencedFiles.length > 0 ||
758
+ sf.typeReferenceDirectives.length > 0 ||
759
+ sf.statements.some(
760
+ (/** @type {any} */ statement) =>
761
+ (ts.isModuleDeclaration(statement) &&
762
+ (ts.isGlobalScopeAugmentation(statement) ||
763
+ ts.isStringLiteral(statement.name))) ||
764
+ ts.isNamespaceExportDeclaration(statement) ||
765
+ (ts.isImportDeclaration(statement) && !importsNames(ts, statement)) ||
766
+ (ts.isExportDeclaration(statement) &&
767
+ !!statement.moduleSpecifier &&
768
+ !statement.exportClause),
769
+ ));
770
+ const roots = declarations
771
+ .filter(file => own || isOwnFile(file))
772
+ .map(file => program.getSourceFile(file))
773
+ .filter(global);
774
+ // With `types` unset TypeScript 5 loads every package in `typeRoots`, and
775
+ // TypeScript 6 loads none; this asks the project's own which it loads.
776
+ for (const name of ts.getAutomaticTypeDirectiveNames(options, ts.sys)) {
777
+ const found = ts.resolveTypeReferenceDirective(
778
+ name,
779
+ path.join(path.dirname(config), '__inferred type names__.ts'),
780
+ options,
781
+ ts.sys,
782
+ ).resolvedTypeReferenceDirective?.resolvedFileName;
783
+ if (!found) {
784
+ return {name: `the types \`${name}\``, file: config, how: 'named'};
785
+ }
786
+ const sf = follow(found) && program.getSourceFile(found);
787
+ if (global(sf)) roots.push(sf);
788
+ }
789
+ return unresolvedReference(ts, program, checker, roots, follow, follow);
790
+ }