@astryxdesign/cli 0.7.2-canary.f16dc9f → 0.7.2-canary.f8f7cf1
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.
- package/README.md +9 -8
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/component.test.mjs +420 -167
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/component/list/list.mjs +42 -4
- package/api/docs/_adapter.d.mts +11 -0
- package/api/docs/_adapter.mjs +244 -1
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/doctor/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +67 -4
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/init/run/run.mjs +2 -2
- package/api/integration/_adapter.d.mts +12 -0
- package/api/integration/_adapter.mjs +54 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +8 -4
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/authoring-checks.mjs +11 -2
- package/api/integration/authoring-checks.test.mjs +39 -0
- package/api/integration/authoring-checks.type.d.mts +9 -0
- package/api/integration/authoring-checks.type.mjs +14 -1
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
- package/api/integration/pack-check.mjs +61 -107
- package/api/integration/pack-check.test.mjs +359 -0
- package/api/integration/validate-integration.mjs +8 -0
- package/api/integration/validate-integration.test.mjs +11 -0
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +12 -1
- package/api/integration/validateIntegration.doc.mjs +17 -3
- package/api/search/search.d.mts +9 -0
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +108 -16
- package/api/search/search.test.mjs +36 -0
- package/api/swizzle/copy/copy.mjs +28 -14
- package/api/template/template.test.mjs +120 -3
- package/api/theme/build/build.family.test.mjs +64 -0
- package/api/theme/build/build.mjs +180 -24
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +10 -0
- package/assets/docs/tree/block-template.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +44 -3
- package/assets/docs/tree/checks.doc.mjs +21 -13
- package/assets/docs/tree/component-family.doc.mjs +5 -1
- package/assets/docs/tree/component-imports.doc.mjs +8 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/links.doc.mjs +2 -2
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +3 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +1 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +2 -1
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +43 -4
- package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
- package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
- package/assets/templates/pages/detail-page/page.tsx +3 -3
- package/assets/templates/pages/table-inbox/page.tsx +10 -8
- package/assets/theme.template.ts +1 -1
- package/authoring/doctypes/base/type.ts +6 -0
- package/authoring/doctypes/component/component.doc.mjs +4 -4
- package/authoring/doctypes/component/type.ts +6 -3
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +8 -1
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
- package/clients/cli/commands/doctor-integration.test.mjs +837 -0
- package/clients/cli/commands/doctor.mjs +190 -0
- package/clients/cli/commands/integration-check.doc.mjs +85 -0
- package/clients/cli/commands/integration-check.test.mjs +267 -0
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +107 -1
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/clients/cli/lib/component-format.mjs +5 -1
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/component-discovery.d.mts +190 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +864 -56
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +40 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1224 -0
- package/foundation/discovery/doc-types.test.mjs +652 -0
- package/foundation/discovery/docs-discovery.d.mts +81 -0
- package/foundation/discovery/docs-discovery.mjs +57 -0
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/module-exports.d.mts +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +278 -0
- package/foundation/discovery/prop-drift.mjs +2263 -0
- package/foundation/discovery/prop-drift.test.mjs +2219 -0
- package/foundation/discovery/template-adapter.mjs +74 -5
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +11 -1
- package/foundation/doc-compiler/inputs.mjs +23 -0
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +7 -2
- package/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file unresolvedReference() and unresolvedGlobals(): each kind of
|
|
5
|
+
* reference the compiler cannot resolve, how far the walk follows, and which
|
|
6
|
+
* declaration files and type packages start the global walk.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {createRequire} from 'node:module';
|
|
13
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
14
|
+
import {readPackageTsconfig} from './package-tsconfig.mjs';
|
|
15
|
+
import {
|
|
16
|
+
unresolvedGlobals,
|
|
17
|
+
unresolvedReference,
|
|
18
|
+
} from './unresolved-references.mjs';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const ts = require('typescript');
|
|
22
|
+
|
|
23
|
+
/** @type {string} */
|
|
24
|
+
let root;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
root = fs.realpathSync(
|
|
28
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-unresolved-')),
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
fs.rmSync(root, {recursive: true, force: true});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} rel
|
|
38
|
+
* @param {string | object} content
|
|
39
|
+
*/
|
|
40
|
+
function write(rel, content) {
|
|
41
|
+
const file = path.join(root, rel);
|
|
42
|
+
fs.mkdirSync(path.dirname(file), {recursive: true});
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
file,
|
|
45
|
+
typeof content === 'string' ? content : JSON.stringify(content),
|
|
46
|
+
);
|
|
47
|
+
return file;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const OPTIONS = {
|
|
51
|
+
strict: true,
|
|
52
|
+
jsx: 'react-jsx',
|
|
53
|
+
module: 'esnext',
|
|
54
|
+
moduleResolution: 'bundler',
|
|
55
|
+
target: 'es2022',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A program over the given sources and the declaration files the package's
|
|
60
|
+
* config includes, as the props comparison builds it.
|
|
61
|
+
* @param {string} packageDir
|
|
62
|
+
* @param {string[]} sources
|
|
63
|
+
* @param {object} [extra] compiler options to add
|
|
64
|
+
*/
|
|
65
|
+
function load(packageDir, sources, extra = {}) {
|
|
66
|
+
const project = readPackageTsconfig(ts, packageDir, sources);
|
|
67
|
+
const options = {...project.options, ...extra};
|
|
68
|
+
const program = ts.createProgram({
|
|
69
|
+
rootNames: [...sources, ...project.declarations],
|
|
70
|
+
options,
|
|
71
|
+
});
|
|
72
|
+
/** @type {(file: string) => boolean} */
|
|
73
|
+
const isOwnFile = file => {
|
|
74
|
+
const rel = path.relative(packageDir, fs.realpathSync(file));
|
|
75
|
+
return (
|
|
76
|
+
!rel.startsWith('..') && !rel.split(path.sep).includes('node_modules')
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
project,
|
|
81
|
+
options,
|
|
82
|
+
program,
|
|
83
|
+
checker: program.getTypeChecker(),
|
|
84
|
+
isOwnFile,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {string} dir */
|
|
89
|
+
function dependency(dir, files) {
|
|
90
|
+
for (const [name, content] of Object.entries(files)) {
|
|
91
|
+
write(path.join(dir, name), content);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe('unresolvedReference', () => {
|
|
96
|
+
it('finds the first reference that does not resolve, and follows only where told', () => {
|
|
97
|
+
write('tsconfig.json', {compilerOptions: OPTIONS});
|
|
98
|
+
write('src/theme.ts', 'export interface Theme { tone?: string }\n');
|
|
99
|
+
write('src/x.ts', 'export const fmt = 1;\n');
|
|
100
|
+
write(
|
|
101
|
+
'src/defaults-src.ts',
|
|
102
|
+
'declare const theme: {colors: {}};\nexport const defaults = {...theme.sizes};\n',
|
|
103
|
+
);
|
|
104
|
+
write(
|
|
105
|
+
'src/x-values.ts',
|
|
106
|
+
'export interface Theme { tone?: string }\nexport const fmt = (s: string) => s;\n',
|
|
107
|
+
);
|
|
108
|
+
dependency('node_modules/untyped-dep', {
|
|
109
|
+
'package.json': {name: 'untyped-dep', main: 'index.js'},
|
|
110
|
+
'index.js': 'export const fmt = text => text;\n',
|
|
111
|
+
});
|
|
112
|
+
dependency('node_modules/@acme/dep', {
|
|
113
|
+
'package.json': {name: '@acme/dep', types: 'index.d.ts'},
|
|
114
|
+
'index.d.ts':
|
|
115
|
+
"import type {Peer} from '@acme/not-installed-peer';\nexport type Dep = Peer;\n",
|
|
116
|
+
});
|
|
117
|
+
const cases = {
|
|
118
|
+
'missing.ts':
|
|
119
|
+
"import type {X} from '@acme/not-installed';\nexport type Y = X;\n",
|
|
120
|
+
'unnamed.ts':
|
|
121
|
+
"import type {Gone} from './theme';\nexport type Y = Gone;\n",
|
|
122
|
+
'reexport.ts': "export {fmt, gone} from './x';\n",
|
|
123
|
+
'types.ts': '/// <reference types="@acme/missing-types" />\nexport {};\n',
|
|
124
|
+
'path.ts':
|
|
125
|
+
'/// <reference path="./generated/augment.d.ts" />\nexport {};\n',
|
|
126
|
+
'effect.ts': "import '@acme/not-installed-effect';\nexport {};\n",
|
|
127
|
+
'untyped.ts': "import {fmt} from 'untyped-dep';\nexport const y = fmt;\n",
|
|
128
|
+
'jsx.tsx': 'export const A = () => <div />;\n',
|
|
129
|
+
'dep.ts': "import type {Dep} from '@acme/dep';\nexport type Y = Dep;\n",
|
|
130
|
+
// Type names declared nowhere, which only `keyof`, a key or a query
|
|
131
|
+
// may reach; and names that resolve.
|
|
132
|
+
'keyed.ts':
|
|
133
|
+
'export type Y = Omit<{label: string; size?: number}, keyof NowhereProps>;\n',
|
|
134
|
+
'template.ts':
|
|
135
|
+
'export type Y = {[K in `on${NowhereKey}`]?: () => void};\n',
|
|
136
|
+
'query.ts': 'export type Y = Parameters<typeof nowhereHook>[0];\n',
|
|
137
|
+
// A value declared nowhere behind a binding, a property that doesn't
|
|
138
|
+
// exist, and a name declared nowhere in a JavaScript file's JSDoc.
|
|
139
|
+
'valued.ts':
|
|
140
|
+
'const d = missingValue;\nexport type Y = Omit<{label: string; size?: number}, keyof typeof d>;\n',
|
|
141
|
+
'indexed.ts':
|
|
142
|
+
"interface Theme { colors: {a: string} }\nexport type Y = Partial<Record<keyof Theme['sizes'], string>>;\n",
|
|
143
|
+
'jsdoc.js':
|
|
144
|
+
'/** @param {Omit<{label: string; size?: number}, keyof NowhereJs>} props */\nexport function J(props) { return null; }\n',
|
|
145
|
+
// Tags whose types the compiler never reads.
|
|
146
|
+
'jsdoctags.js':
|
|
147
|
+
'/**\n * @param {{label: string}} props\n * @throws {HttpError} when offline\n * @exception {NowhereError}\n * @see NowhereGuide\n * @see {@link NowhereLink}\n */\nexport function T(props) { return null; }\n',
|
|
148
|
+
// A class's base that names nothing, written or in `@augments`, and
|
|
149
|
+
// one that names a class.
|
|
150
|
+
'baseless.ts':
|
|
151
|
+
'export class X extends NowhereBase<{label?: string}> {}\n',
|
|
152
|
+
'baselessjs.js':
|
|
153
|
+
'class Real {}\n/** @augments {NowhereBase} */\nexport class X extends Real {}\n',
|
|
154
|
+
'augments.js':
|
|
155
|
+
'class Real {}\n/** @augments {Real} */\nexport class X extends Real {}\n',
|
|
156
|
+
// A class's base is a value, and a module found without types gives
|
|
157
|
+
// its names no types to resolve.
|
|
158
|
+
'classbase.ts':
|
|
159
|
+
'class Real<P> { props!: P }\nconst Base = Real;\nexport class X extends Base<{label?: string}> {}\nexport function mixin(B: typeof Real) { return class extends B {}; }\n',
|
|
160
|
+
// A value the compiler can't type, spread into an object, makes it
|
|
161
|
+
// `any`, so `keyof typeof` it takes any name.
|
|
162
|
+
'spread.ts':
|
|
163
|
+
'interface Theme { colors: {a: string} }\ndeclare const theme: Theme;\nconst defaults = {...theme.sizes};\nexport type Y = Omit<{label: string; size?: number}, keyof typeof defaults>;\n',
|
|
164
|
+
'spreadname.ts':
|
|
165
|
+
'const tones = {...undeclaredTones};\nexport type Y = Partial<Record<keyof typeof tones, string>>;\n',
|
|
166
|
+
// The same from another file of the package's, and a line that
|
|
167
|
+
// `@ts-expect-error` covers, which `tsc` accepts.
|
|
168
|
+
'crossfile.ts':
|
|
169
|
+
"import {defaults} from './defaults-src';\nexport type Y = keyof typeof defaults;\n",
|
|
170
|
+
'suppressed.ts':
|
|
171
|
+
'declare const theme: {colors: {}};\nconst defaults = {\n // @ts-expect-error -- older themes have sizes\n ...theme.sizes,\n};\nexport type Y = keyof typeof defaults;\n',
|
|
172
|
+
// What an own function returns is read too.
|
|
173
|
+
'called.ts':
|
|
174
|
+
'function makeSlots() { return {...undeclaredSlots}; }\nconst slots = makeSlots();\nexport type Y = keyof typeof slots;\n',
|
|
175
|
+
// `tsc` reports no error in a `@ts-nocheck` file, or on a later line
|
|
176
|
+
// `@ts-expect-error` covers, or (without `noImplicitAny`) for an index
|
|
177
|
+
// that misses.
|
|
178
|
+
'nocheck.ts':
|
|
179
|
+
'// @ts-nocheck\ndeclare const theme: {colors: {}};\nconst defaults = {...theme.sizes};\nexport type Y = keyof typeof defaults;\n',
|
|
180
|
+
'laterline.ts':
|
|
181
|
+
'declare const theme: {colors: {}};\nconst defaults = {\n ...theme\n // @ts-expect-error -- older themes have sizes\n .sizes,\n};\nexport type Y = keyof typeof defaults;\n',
|
|
182
|
+
'indexmiss.ts':
|
|
183
|
+
"const COLORS = {red: '#f00'};\nfunction Base(props: {tone: string}) { const color = COLORS[props.tone]; return color; }\nexport type P = Parameters<typeof Base>[0];\n",
|
|
184
|
+
// A function passed to another, and a class field.
|
|
185
|
+
'wrapped.ts':
|
|
186
|
+
'function lazy<T>(make: () => T): T { return make(); }\nfunction makeSlots() { return {...undeclaredSlots}; }\nconst slots = lazy(makeSlots);\nexport type Y = keyof typeof slots;\n',
|
|
187
|
+
'classfield.ts':
|
|
188
|
+
"class Store { slots = {...undeclaredSlots}; }\nexport type Y = keyof InstanceType<typeof Store>['slots'];\n",
|
|
189
|
+
// A written return type, overloads, and a call made for its effect
|
|
190
|
+
// decide nothing about the type, whatever the body holds.
|
|
191
|
+
'declaredreturn.ts':
|
|
192
|
+
'function makeSlots(): {a?: number} { const broken = undeclaredThing; return {}; }\nconst slots = makeSlots();\nexport type Y = keyof typeof slots;\n',
|
|
193
|
+
'overloaded.ts':
|
|
194
|
+
'function make(): {a?: number};\nfunction make(x?: number) { return {...undeclaredA}; }\nconst made = make();\nexport type Y = keyof typeof made;\n',
|
|
195
|
+
'statement.ts':
|
|
196
|
+
'function track() { undeclaredLogger.log(); }\nfunction Base(props: {tone?: string}) { track(); return null; }\nexport type P = Parameters<typeof Base>[0];\n',
|
|
197
|
+
// A class's instance type is inferred from its members too, wherever
|
|
198
|
+
// a type names the class.
|
|
199
|
+
'classbyname.ts':
|
|
200
|
+
"class Store { slots = {...undeclaredSlots}; }\nexport type Y = keyof Store['slots'];\n",
|
|
201
|
+
'classgetter.ts':
|
|
202
|
+
"class Store { get slots() { return {...undeclaredSlots}; } }\nexport type Y = keyof Store['slots'];\n",
|
|
203
|
+
'extendscall.ts':
|
|
204
|
+
"function mixin() { return class { slots = {...undeclaredSlots}; }; }\nclass Store extends mixin() {}\nexport type Y = keyof Store['slots'];\n",
|
|
205
|
+
'interfaceextends.ts':
|
|
206
|
+
"class Store { slots = {...undeclaredSlots}; }\ninterface Holder extends Store {}\nexport type Y = keyof Holder['slots'];\n",
|
|
207
|
+
// A class named only as a whole: its names are its written members
|
|
208
|
+
// and its base, so a broken member elsewhere in it decides nothing.
|
|
209
|
+
'classmember.ts':
|
|
210
|
+
'class Widget { state = {...undeclaredState}; }\nexport type P = {onReady?: (widget: Widget) => void};\n',
|
|
211
|
+
'componentbase.ts':
|
|
212
|
+
"declare class Component<P> { props: P }\nclass Base<P> extends Component<P> { broken = {...undeclaredBroken}; }\nexport class AcmeX extends Base<{tone?: string}> {}\nexport type P = AcmeX['props'];\n",
|
|
213
|
+
// A base built by a call gives the class names of its own.
|
|
214
|
+
'computedbase.ts':
|
|
215
|
+
'function makeBase() { return undeclaredBase; }\nclass Store extends makeBase() {}\nexport type Y = keyof Store;\n',
|
|
216
|
+
// A cast that writes the type decides it; `as const` does not.
|
|
217
|
+
'cast.ts':
|
|
218
|
+
'const slots = {...undeclaredSlots} as {a?: number};\nexport type Y = keyof typeof slots;\n',
|
|
219
|
+
'castconst.ts':
|
|
220
|
+
'const slots = {...undeclaredSlots} as const;\nexport type Y = keyof typeof slots;\n',
|
|
221
|
+
// An instantiation expression takes type arguments for a value.
|
|
222
|
+
'instantiation.ts':
|
|
223
|
+
'function select<T>(value: T) { return value; }\nexport const selectString = select<string>;\nexport type S = typeof selectString;\n',
|
|
224
|
+
'untypedq.ts':
|
|
225
|
+
"import {fmt} from 'untyped-dep';\nexport type F = typeof fmt;\nexport type G = typeof import('untyped-dep').fmt;\n",
|
|
226
|
+
// TypeScript reads no types from a `.ts` file's JSDoc.
|
|
227
|
+
'tsdoc.ts':
|
|
228
|
+
'/** @param {NowhereDoc} x */\nexport function g(x: string) { return x; }\n',
|
|
229
|
+
// Values that resolve, and names that are not read as values.
|
|
230
|
+
'values.ts':
|
|
231
|
+
"import {type Theme, fmt} from './x-values';\nconst base = {a: 1};\nexport const merged = {...base, b: [1].length, c: Math.max(1, 2), fmt};\nexport type V = Theme;\n",
|
|
232
|
+
'known.ts':
|
|
233
|
+
"import type {Theme} from './theme';\nexport type Y<T> = {[K in keyof T]: Theme} & Partial<Record<'a', string>>;\nexport const sizes = ['sm', 'md'] as const;\nexport type W = Window & typeof globalThis;\nexport type G = keyof typeof globalThis;\nexport type U = typeof undefined;\nexport function f() { type A = typeof arguments; return 1; }\nexport type T = Theme['tone'];\n",
|
|
234
|
+
};
|
|
235
|
+
const sources = Object.entries(cases).map(([name, text]) =>
|
|
236
|
+
write(path.join('src', name), text),
|
|
237
|
+
);
|
|
238
|
+
const {program, checker, isOwnFile} = load(root, sources, {allowJs: true});
|
|
239
|
+
/** @type {(name: string, follow?: (file: string) => boolean, mine?: (file: string) => boolean) => string | undefined} */
|
|
240
|
+
const first = (name, follow = () => true, mine = isOwnFile) =>
|
|
241
|
+
unresolvedReference(
|
|
242
|
+
ts,
|
|
243
|
+
program,
|
|
244
|
+
checker,
|
|
245
|
+
[program.getSourceFile(path.join(root, 'src', name))],
|
|
246
|
+
follow,
|
|
247
|
+
mine,
|
|
248
|
+
)?.name;
|
|
249
|
+
|
|
250
|
+
expect(first('missing.ts')).toBe('`@acme/not-installed`');
|
|
251
|
+
expect(first('unnamed.ts')).toBe('`Gone` from `./theme`');
|
|
252
|
+
expect(first('reexport.ts')).toBe('`gone` from `./x`');
|
|
253
|
+
expect(first('types.ts')).toBe('the types `@acme/missing-types`');
|
|
254
|
+
expect(first('path.ts')).toBe('`./generated/augment.d.ts`');
|
|
255
|
+
// TypeScript 6 checks a side-effect import by default.
|
|
256
|
+
expect(first('effect.ts')).toBe('`@acme/not-installed-effect`');
|
|
257
|
+
// A module found without types resolves, and so does the JSX runtime
|
|
258
|
+
// import the compiler adds itself.
|
|
259
|
+
expect(first('untyped.ts')).toBeUndefined();
|
|
260
|
+
expect(first('jsx.tsx')).toBeUndefined();
|
|
261
|
+
// A dependency's own missing import, where the walk follows it; where it
|
|
262
|
+
// doesn't, the type that import leaves untyped in the package's own file.
|
|
263
|
+
expect(first('dep.ts')).toBe('`@acme/not-installed-peer`');
|
|
264
|
+
expect(first('dep.ts', isOwnFile)).toBe('`Dep`');
|
|
265
|
+
expect(first('keyed.ts')).toBe('`NowhereProps`');
|
|
266
|
+
expect(first('template.ts')).toBe('`NowhereKey`');
|
|
267
|
+
expect(first('query.ts')).toBe('`typeof nowhereHook`');
|
|
268
|
+
expect(first('valued.ts')).toBe('`typeof d`');
|
|
269
|
+
expect(first('indexed.ts')).toBe("`Theme['sizes']`");
|
|
270
|
+
expect(first('jsdoc.js')).toBe('`NowhereJs`');
|
|
271
|
+
expect(first('jsdoctags.js')).toBeUndefined();
|
|
272
|
+
expect(first('tsdoc.ts')).toBeUndefined();
|
|
273
|
+
expect(first('classbase.ts')).toBeUndefined();
|
|
274
|
+
expect(first('baseless.ts')).toBe('`NowhereBase`');
|
|
275
|
+
expect(first('baselessjs.js')).toBe('`NowhereBase`');
|
|
276
|
+
expect(first('augments.js')).toBeUndefined();
|
|
277
|
+
expect(first('untypedq.ts')).toBeUndefined();
|
|
278
|
+
expect(first('instantiation.ts')).toBeUndefined();
|
|
279
|
+
expect(first('spread.ts')).toBe('`theme.sizes`');
|
|
280
|
+
expect(first('spreadname.ts')).toBe('`undeclaredTones`');
|
|
281
|
+
expect(first('values.ts')).toBeUndefined();
|
|
282
|
+
expect(first('crossfile.ts')).toBe('`theme.sizes`');
|
|
283
|
+
expect(first('suppressed.ts')).toBeUndefined();
|
|
284
|
+
expect(first('called.ts')).toBe('`undeclaredSlots`');
|
|
285
|
+
expect(first('wrapped.ts')).toBe('`undeclaredSlots`');
|
|
286
|
+
expect(first('classfield.ts')).toBe('`undeclaredSlots`');
|
|
287
|
+
expect(first('declaredreturn.ts')).toBeUndefined();
|
|
288
|
+
expect(first('overloaded.ts')).toBeUndefined();
|
|
289
|
+
expect(first('statement.ts')).toBeUndefined();
|
|
290
|
+
for (const file of [
|
|
291
|
+
'classbyname.ts',
|
|
292
|
+
'classgetter.ts',
|
|
293
|
+
'extendscall.ts',
|
|
294
|
+
'interfaceextends.ts',
|
|
295
|
+
'castconst.ts',
|
|
296
|
+
]) {
|
|
297
|
+
expect.soft(first(file), file).toBe('`undeclaredSlots`');
|
|
298
|
+
}
|
|
299
|
+
expect(first('cast.ts')).toBeUndefined();
|
|
300
|
+
expect(first('computedbase.ts')).toBe('`undeclaredBase`');
|
|
301
|
+
expect.soft(first('classmember.ts'), 'classmember.ts').toBeUndefined();
|
|
302
|
+
expect.soft(first('componentbase.ts'), 'componentbase.ts').toBeUndefined();
|
|
303
|
+
expect(first('nocheck.ts')).toBeUndefined();
|
|
304
|
+
expect(first('laterline.ts')).toBeUndefined();
|
|
305
|
+
// With `noImplicitAny` the index miss is an error `tsc` reports.
|
|
306
|
+
expect(first('indexmiss.ts')).toBe('`COLORS[props.tone]`');
|
|
307
|
+
const loose = load(root, sources, {strict: false, noImplicitAny: false});
|
|
308
|
+
expect(
|
|
309
|
+
unresolvedReference(
|
|
310
|
+
ts,
|
|
311
|
+
loose.program,
|
|
312
|
+
loose.checker,
|
|
313
|
+
[loose.program.getSourceFile(path.join(root, 'src', 'indexmiss.ts'))],
|
|
314
|
+
() => true,
|
|
315
|
+
loose.isOwnFile,
|
|
316
|
+
),
|
|
317
|
+
).toBeNull();
|
|
318
|
+
expect(first('known.ts')).toBeUndefined();
|
|
319
|
+
// Only in the files the caller checks names in.
|
|
320
|
+
expect(
|
|
321
|
+
first(
|
|
322
|
+
'keyed.ts',
|
|
323
|
+
() => true,
|
|
324
|
+
() => false,
|
|
325
|
+
),
|
|
326
|
+
).toBeUndefined();
|
|
327
|
+
|
|
328
|
+
const unchecked = load(root, sources, {
|
|
329
|
+
noUncheckedSideEffectImports: false,
|
|
330
|
+
});
|
|
331
|
+
expect(
|
|
332
|
+
unresolvedReference(
|
|
333
|
+
ts,
|
|
334
|
+
unchecked.program,
|
|
335
|
+
unchecked.checker,
|
|
336
|
+
[unchecked.program.getSourceFile(path.join(root, 'src', 'effect.ts'))],
|
|
337
|
+
() => true,
|
|
338
|
+
() => true,
|
|
339
|
+
),
|
|
340
|
+
).toBeNull();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('gives each root the same answer, whatever an earlier call on the same program read', () => {
|
|
344
|
+
write('tsconfig.json', {compilerOptions: OPTIONS});
|
|
345
|
+
const header = write(
|
|
346
|
+
'src/CardHeader.ts',
|
|
347
|
+
'declare const theme: {colors: {}};\nexport const defaults = {...theme.sizes};\nexport type HeaderKeys = keyof typeof defaults;\n',
|
|
348
|
+
);
|
|
349
|
+
const card = write(
|
|
350
|
+
'src/Card.ts',
|
|
351
|
+
"import {defaults} from './CardHeader';\nexport type CardKeys = keyof typeof defaults;\n",
|
|
352
|
+
);
|
|
353
|
+
for (const order of [
|
|
354
|
+
[card, header],
|
|
355
|
+
[header, card],
|
|
356
|
+
]) {
|
|
357
|
+
const {program, checker, isOwnFile} = load(root, [card, header]);
|
|
358
|
+
const answers = order.map(
|
|
359
|
+
file =>
|
|
360
|
+
unresolvedReference(
|
|
361
|
+
ts,
|
|
362
|
+
program,
|
|
363
|
+
checker,
|
|
364
|
+
[program.getSourceFile(file)],
|
|
365
|
+
() => true,
|
|
366
|
+
isOwnFile,
|
|
367
|
+
)?.name,
|
|
368
|
+
);
|
|
369
|
+
expect(answers).toEqual(['`theme.sizes`', '`theme.sizes`']);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('unresolvedGlobals', () => {
|
|
375
|
+
it("starts from each declaration file the package's own config includes, wherever it sits, and from its own type packages", () => {
|
|
376
|
+
const widgets = path.join(root, 'packages', 'widgets');
|
|
377
|
+
const source = write(
|
|
378
|
+
'packages/widgets/src/Badge.tsx',
|
|
379
|
+
'export function Badge(props: {label?: string}) { return null; }\n',
|
|
380
|
+
);
|
|
381
|
+
dependency('packages/widgets/node_modules/@types/dep-globals', {
|
|
382
|
+
'package.json': {name: '@types/dep-globals', types: 'index.d.ts'},
|
|
383
|
+
'index.d.ts':
|
|
384
|
+
'/// <reference types="@acme/not-installed-runner" />\ndeclare var depGlobal: string;\n',
|
|
385
|
+
});
|
|
386
|
+
/** @type {(config: object) => import('./unresolved-references.mjs').UnresolvedReference | null} */
|
|
387
|
+
const globals = config => {
|
|
388
|
+
write('packages/widgets/tsconfig.json', config);
|
|
389
|
+
const {project, options, program, checker, isOwnFile} = load(widgets, [
|
|
390
|
+
source,
|
|
391
|
+
]);
|
|
392
|
+
return unresolvedGlobals(
|
|
393
|
+
ts,
|
|
394
|
+
program,
|
|
395
|
+
checker,
|
|
396
|
+
project.declarations,
|
|
397
|
+
options,
|
|
398
|
+
isOwnFile,
|
|
399
|
+
);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// A declaration file outside the package that its own config includes.
|
|
403
|
+
write(
|
|
404
|
+
'shared/globals.d.ts',
|
|
405
|
+
'/// <reference types="@acme/not-installed-css" />\ndeclare var shared: string;\n',
|
|
406
|
+
);
|
|
407
|
+
expect(
|
|
408
|
+
globals({compilerOptions: OPTIONS, include: ['src', '../../shared']}),
|
|
409
|
+
).toMatchObject({name: 'the types `@acme/not-installed-css`'});
|
|
410
|
+
|
|
411
|
+
// The package's own type package, named in `types`.
|
|
412
|
+
write(
|
|
413
|
+
'packages/widgets/types/css-prop/index.d.ts',
|
|
414
|
+
'/// <reference types="@acme/not-installed-css" />\ndeclare var cssProp: string;\n',
|
|
415
|
+
);
|
|
416
|
+
const typeRoots = ['./types', './node_modules/@types'];
|
|
417
|
+
expect(
|
|
418
|
+
globals({
|
|
419
|
+
compilerOptions: {...OPTIONS, typeRoots, types: ['css-prop']},
|
|
420
|
+
include: ['src'],
|
|
421
|
+
}),
|
|
422
|
+
).toMatchObject({name: 'the types `@acme/not-installed-css`'});
|
|
423
|
+
// With `types` unset TypeScript 5 loads every package in `typeRoots`, as
|
|
424
|
+
// TypeScript 6 does for `types: ['*']`, so they are walked then too.
|
|
425
|
+
const ts5 = new Proxy(ts, {
|
|
426
|
+
get: (target, key) =>
|
|
427
|
+
key === 'getAutomaticTypeDirectiveNames'
|
|
428
|
+
? (/** @type {any} */ options, /** @type {any} */ host) =>
|
|
429
|
+
target.getAutomaticTypeDirectiveNames(
|
|
430
|
+
{...options, types: options.types ?? ['*']},
|
|
431
|
+
host,
|
|
432
|
+
)
|
|
433
|
+
: target[key],
|
|
434
|
+
});
|
|
435
|
+
write('packages/widgets/tsconfig.json', {
|
|
436
|
+
compilerOptions: {...OPTIONS, typeRoots},
|
|
437
|
+
include: ['src'],
|
|
438
|
+
});
|
|
439
|
+
const five = load(widgets, [source], {types: ['*']});
|
|
440
|
+
expect(
|
|
441
|
+
unresolvedGlobals(
|
|
442
|
+
ts5,
|
|
443
|
+
five.program,
|
|
444
|
+
five.checker,
|
|
445
|
+
five.project.declarations,
|
|
446
|
+
{...five.options, types: undefined},
|
|
447
|
+
five.isOwnFile,
|
|
448
|
+
),
|
|
449
|
+
).toMatchObject({name: 'the types `@acme/not-installed-css`'});
|
|
450
|
+
|
|
451
|
+
// A module declaration file no component imports declares no globals,
|
|
452
|
+
// so a package only it imports is not followed; one that augments a
|
|
453
|
+
// module, or imports one for its effects, is.
|
|
454
|
+
write(
|
|
455
|
+
'packages/widgets/src/api.d.ts',
|
|
456
|
+
"import type {Client} from '@acme/not-installed-client';\nexport type Api = Client;\n",
|
|
457
|
+
);
|
|
458
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toBeNull();
|
|
459
|
+
write(
|
|
460
|
+
'packages/widgets/src/api.d.ts',
|
|
461
|
+
"import type {Client} from '@acme/not-installed-client';\ndeclare module '@acme/ui' { interface Theme { client?: Client } }\nexport {};\n",
|
|
462
|
+
);
|
|
463
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toMatchObject(
|
|
464
|
+
{name: '`@acme/not-installed-client`'},
|
|
465
|
+
);
|
|
466
|
+
for (const loads of [
|
|
467
|
+
"import '@acme/not-installed-css-prop';",
|
|
468
|
+
"import {} from '@acme/not-installed-css-prop';",
|
|
469
|
+
"import type {} from '@acme/not-installed-css-prop';",
|
|
470
|
+
"export * from '@acme/not-installed-css-prop';",
|
|
471
|
+
]) {
|
|
472
|
+
write('packages/widgets/src/api.d.ts', `${loads}\nexport {};\n`);
|
|
473
|
+
expect(
|
|
474
|
+
globals({compilerOptions: OPTIONS, include: ['src']}),
|
|
475
|
+
).toMatchObject({name: '`@acme/not-installed-css-prop`'});
|
|
476
|
+
}
|
|
477
|
+
// `export * as` binds the module's names under one, as `import * as`
|
|
478
|
+
// does, so it doesn't act globally.
|
|
479
|
+
for (const binds of [
|
|
480
|
+
"import * as legacy from '@acme/removed-package';",
|
|
481
|
+
"export * as legacy from '@acme/removed-package';",
|
|
482
|
+
]) {
|
|
483
|
+
write('packages/widgets/src/api.d.ts', `${binds}\nexport {};\n`);
|
|
484
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toBeNull();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// A UMD declaration file declares a global namespace for its module.
|
|
488
|
+
write(
|
|
489
|
+
'packages/widgets/src/api.d.ts',
|
|
490
|
+
"import type {Theme} from '@acme/not-installed-theme';\nexport interface Props { theme?: Theme }\nexport as namespace AcmeLib;\n",
|
|
491
|
+
);
|
|
492
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toMatchObject(
|
|
493
|
+
{name: '`@acme/not-installed-theme`'},
|
|
494
|
+
);
|
|
495
|
+
fs.rmSync(path.join(widgets, 'src', 'api.d.ts'));
|
|
496
|
+
|
|
497
|
+
// A type name declared nowhere, in the package's own declaration file.
|
|
498
|
+
write(
|
|
499
|
+
'packages/widgets/src/names.d.ts',
|
|
500
|
+
'type AcmeOmitted = Omit<{label: string}, keyof NowhereProps>;\n',
|
|
501
|
+
);
|
|
502
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toMatchObject(
|
|
503
|
+
{name: '`NowhereProps`', how: 'used'},
|
|
504
|
+
);
|
|
505
|
+
fs.rmSync(path.join(widgets, 'src', 'names.d.ts'));
|
|
506
|
+
|
|
507
|
+
// A type package the config names that cannot be found.
|
|
508
|
+
expect(
|
|
509
|
+
globals({
|
|
510
|
+
compilerOptions: {...OPTIONS, types: ['@acme/not-installed-globals']},
|
|
511
|
+
include: ['src'],
|
|
512
|
+
}),
|
|
513
|
+
).toEqual({
|
|
514
|
+
name: 'the types `@acme/not-installed-globals`',
|
|
515
|
+
file: path.join(widgets, 'tsconfig.json'),
|
|
516
|
+
how: 'named',
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// A dependency's type package is not followed, named, under `*`, or
|
|
520
|
+
// referenced from the package's own declaration file.
|
|
521
|
+
for (const types of [['dep-globals'], ['*']]) {
|
|
522
|
+
expect(
|
|
523
|
+
globals({compilerOptions: {...OPTIONS, types}, include: ['src']}),
|
|
524
|
+
).toBeNull();
|
|
525
|
+
}
|
|
526
|
+
write(
|
|
527
|
+
'packages/widgets/src/env.d.ts',
|
|
528
|
+
'/// <reference types="dep-globals" />\ndeclare var env: string;\n',
|
|
529
|
+
);
|
|
530
|
+
expect(globals({compilerOptions: OPTIONS, include: ['src']})).toBeNull();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("starts only from the package's own declaration files for a config from above it", () => {
|
|
534
|
+
// A monorepo's root config includes every file in the repository.
|
|
535
|
+
write('tsconfig.json', {compilerOptions: OPTIONS});
|
|
536
|
+
write(
|
|
537
|
+
'types/global.d.ts',
|
|
538
|
+
"import type {Props} from '@acme/not-installed';\ndeclare global { type RepoProps = Props; }\n",
|
|
539
|
+
);
|
|
540
|
+
const widgets = path.join(root, 'packages', 'widgets');
|
|
541
|
+
const source = write(
|
|
542
|
+
'packages/widgets/src/Badge.tsx',
|
|
543
|
+
'export function Badge(props: {label?: string}) { return null; }\n',
|
|
544
|
+
);
|
|
545
|
+
const read = () => {
|
|
546
|
+
const {project, options, program, checker, isOwnFile} = load(widgets, [
|
|
547
|
+
source,
|
|
548
|
+
]);
|
|
549
|
+
return unresolvedGlobals(
|
|
550
|
+
ts,
|
|
551
|
+
program,
|
|
552
|
+
checker,
|
|
553
|
+
project.declarations,
|
|
554
|
+
options,
|
|
555
|
+
isOwnFile,
|
|
556
|
+
);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
expect(read()).toBeNull();
|
|
560
|
+
|
|
561
|
+
write(
|
|
562
|
+
'packages/widgets/src/env.d.ts',
|
|
563
|
+
"import type {Env} from '@acme/not-installed-env';\ndeclare global { type WidgetEnv = Env; }\n",
|
|
564
|
+
);
|
|
565
|
+
expect(read()).toMatchObject({name: '`@acme/not-installed-env`'});
|
|
566
|
+
});
|
|
567
|
+
});
|
|
@@ -195,8 +195,10 @@ const RUNNERS = {
|
|
|
195
195
|
runs: SELF_DOCS,
|
|
196
196
|
sites: [
|
|
197
197
|
'import ../../../api/integration/integrationAdd.doc.mjs',
|
|
198
|
+
'import ../../../api/integration/integrationCheck.doc.mjs',
|
|
198
199
|
'import ../../../api/integration/integrationPackCheck.doc.mjs',
|
|
199
200
|
'import ./integration-add.doc.mjs',
|
|
201
|
+
'import ./integration-check.doc.mjs',
|
|
200
202
|
'import ./integration-pack.doc.mjs',
|
|
201
203
|
'import ./integration-verify.doc.mjs',
|
|
202
204
|
'import ./integration.doc.mjs',
|
|
@@ -246,12 +248,20 @@ const RUNNERS = {
|
|
|
246
248
|
runs: 'astryx.config, through the config parser',
|
|
247
249
|
sites: ['loadModuleWithParser(..., parseConfig)'],
|
|
248
250
|
},
|
|
251
|
+
'foundation/discovery/component-discovery.mjs': {
|
|
252
|
+
runs: '@babel/parser, on first use, to read the members a family doc lists in its components without running the doc',
|
|
253
|
+
sites: ['require(@babel/parser)'],
|
|
254
|
+
},
|
|
249
255
|
'foundation/discovery/docs-discovery.mjs': {
|
|
250
256
|
runs: 'a contributed topic, for its catalog fields: name, title, description, category, and relationships',
|
|
251
257
|
sites: ['importDocModule'],
|
|
252
258
|
},
|
|
259
|
+
'foundation/discovery/project-typescript.mjs': {
|
|
260
|
+
runs: "the project's own typescript, for the author checks that read source and docs with it, and its package.json for the version of one they cannot use",
|
|
261
|
+
sites: ['require(typescript)', 'require(typescript/package.json)'],
|
|
262
|
+
},
|
|
253
263
|
'foundation/discovery/template-adapter.mjs': {
|
|
254
|
-
runs: '@babel/parser, to find the fixture references in template source; and a template doc that does not validate, to read the replacement it declares',
|
|
264
|
+
runs: '@babel/parser, to find the fixture references and page-local bindings in template source; and a template doc that does not validate, to read the replacement it declares',
|
|
255
265
|
sites: ['importDocModule', 'require(@babel/parser)'],
|
|
256
266
|
},
|
|
257
267
|
'foundation/discovery/theme-discovery.mjs': {
|
|
@@ -81,8 +81,31 @@ const NON_DESCRIPTOR_DIRS = new Set([
|
|
|
81
81
|
export async function collectDocInputs(project) {
|
|
82
82
|
const list = new InputList();
|
|
83
83
|
|
|
84
|
+
// A component documented as a member of its family's doc (HStack and VStack
|
|
85
|
+
// in Stack's doc) is read through the family's own entry: one descriptor,
|
|
86
|
+
// one reader. Own docs go first so the family keeps its name.
|
|
87
|
+
/** @type {Array<{name: string, package: string, docPath: string}>} */
|
|
88
|
+
const ownDocs = [];
|
|
89
|
+
/** @type {Array<{name: string, package: string, docPath: string}>} */
|
|
90
|
+
const familyDocs = [];
|
|
84
91
|
for (const record of await project.components()) {
|
|
85
92
|
if (record.docPath == null) continue;
|
|
93
|
+
const stem = path
|
|
94
|
+
.basename(record.docPath)
|
|
95
|
+
.replace(/\.doc\.[cm]?[jt]s$/u, '')
|
|
96
|
+
.replace(/^XDS/u, '');
|
|
97
|
+
const entry = {
|
|
98
|
+
name: record.name,
|
|
99
|
+
package: record.package,
|
|
100
|
+
docPath: record.docPath,
|
|
101
|
+
};
|
|
102
|
+
(stem === record.name ? ownDocs : familyDocs).push(entry);
|
|
103
|
+
}
|
|
104
|
+
for (const record of ownDocs) {
|
|
105
|
+
list.add('components', record.name, record.package, record.docPath);
|
|
106
|
+
}
|
|
107
|
+
for (const record of familyDocs) {
|
|
108
|
+
if (list.claims(record.docPath)) continue;
|
|
86
109
|
list.add('components', record.name, record.package, record.docPath);
|
|
87
110
|
}
|
|
88
111
|
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
* @typedef {object} DocLink
|
|
14
14
|
* @property {string} target the target as written: `[<provider>:]<kind>:<name>`
|
|
15
15
|
* @property {string} id the doc's identity (a DocId)
|
|
16
|
-
* @property {string} route what `astryx docs <route>` opens
|
|
16
|
+
* @property {string | null} route what `astryx docs <route>` opens; null for a
|
|
17
|
+
* component, page, or block doc, which no docs route opens
|
|
17
18
|
* @property {string} title
|
|
18
19
|
* @property {string} summary
|
|
19
|
-
* @property {string} command the command that opens it
|
|
20
|
+
* @property {string} command the command that opens it: `astryx docs <route>`,
|
|
21
|
+
* or `astryx component` or `astryx template` for a component, page, or block
|
|
20
22
|
*/
|
|
21
23
|
/**
|
|
22
24
|
* Finds the doc a target names, or says why none matches.
|
|
@@ -118,13 +120,15 @@ export type DocLink = {
|
|
|
118
120
|
*/
|
|
119
121
|
id: string;
|
|
120
122
|
/**
|
|
121
|
-
* what `astryx docs <route>` opens
|
|
123
|
+
* what `astryx docs <route>` opens; null for a
|
|
124
|
+
* component, page, or block doc, which no docs route opens
|
|
122
125
|
*/
|
|
123
|
-
route: string;
|
|
126
|
+
route: string | null;
|
|
124
127
|
title: string;
|
|
125
128
|
summary: string;
|
|
126
129
|
/**
|
|
127
|
-
* the command that opens it
|
|
130
|
+
* the command that opens it: `astryx docs <route>`,
|
|
131
|
+
* or `astryx component` or `astryx template` for a component, page, or block
|
|
128
132
|
*/
|
|
129
133
|
command: string;
|
|
130
134
|
};
|