@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.db7b338
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 +22 -1
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/build.doc.mjs +1 -0
- package/api/component/component.doc.mjs +1 -0
- package/api/discover/discover.doc.mjs +1 -0
- package/api/docs/_adapter.d.mts +3 -8
- package/api/docs/_adapter.mjs +13 -105
- package/api/docs/detail/section/section.mjs +9 -15
- package/api/docs/detail/section/section.test.mjs +15 -6
- package/api/docs/docs.doc.mjs +1 -0
- package/api/docs/list/list.mjs +3 -7
- package/api/doctor/doctor.d.mts +25 -3
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +186 -14
- package/api/doctor/doctor.test.mjs +332 -7
- package/api/gap-report/gap-report.doc.mjs +1 -0
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +1 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/hook/list/list.mjs +69 -17
- package/api/init/init.doc.mjs +1 -0
- package/api/integration/add-contribution.mjs +7 -7
- package/api/integration/add-contribution.test.mjs +37 -3
- package/api/integration/add-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.test.mjs +8 -4
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +1 -0
- package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
- package/api/integration/integrationAddCodemod.doc.mjs +1 -0
- package/api/integration/integrationAddComponent.doc.mjs +1 -0
- package/api/integration/integrationAddDoc.doc.mjs +1 -0
- package/api/integration/integrationAddTemplate.doc.mjs +1 -0
- package/api/integration/integrationAddTheme.doc.mjs +6 -5
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
- package/api/integration/integrationDocConflicts.doc.mjs +1 -0
- package/api/integration/integrationPackCheck.doc.mjs +1 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
- package/api/integration/pack-check.test.mjs +17 -47
- package/api/integration/summarizeIssues.doc.mjs +1 -0
- package/api/integration/validate-integration-fixes.test.mjs +1389 -0
- package/api/integration/validate-integration.mjs +50 -102
- package/api/integration/validate-integration.test.mjs +85 -23
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +1 -0
- package/api/json/assertResponse.doc.mjs +1 -0
- package/api/json/isError.doc.mjs +1 -0
- package/api/json/parseResponse.doc.mjs +1 -0
- package/api/layout/layoutCheck.doc.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -0
- package/api/layout/layoutGrammar.doc.mjs +1 -0
- package/api/search/search.doc.mjs +1 -0
- package/api/search/search.mjs +16 -6
- package/api/swizzle/swizzle.doc.mjs +1 -0
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.doc.mjs +1 -0
- package/api/theme/_adapter.d.mts +2 -3
- package/api/theme/_adapter.mjs +4 -5
- package/api/theme/add/add.binary.test.mjs +10 -17
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -0
- package/api/theme/integration-themes.test.mjs +39 -28
- package/api/theme/list/list.test.mjs +19 -20
- package/api/theme/listThemes.doc.mjs +6 -5
- package/api/theme/themeAdd.doc.mjs +4 -3
- package/api/theme/themeBuild.doc.mjs +1 -0
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +5 -3
- package/api/theme/themePaletteGenerate.doc.mjs +1 -0
- package/api/theme/themeTargets.doc.mjs +1 -0
- package/api/theme/themeTemplate.doc.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +32 -5
- package/api/upgrade/_adapter.mjs +97 -69
- package/api/upgrade/provider-agreement.test.mjs +152 -0
- package/api/upgrade/run/run.mjs +356 -59
- package/api/upgrade/upgrade.doc.mjs +6 -1
- package/api/upgrade/upgrade.type.d.mts +34 -0
- package/api/upgrade/upgrade.type.mjs +15 -0
- package/assets/codemods/__tests__/runner.test.mjs +330 -8
- package/assets/codemods/integration-discovery.mjs +8 -2
- package/assets/codemods/integration-discovery.test.mjs +15 -0
- package/assets/codemods/integration-runner.mjs +56 -4
- package/assets/codemods/integration-runner.protection.test.mjs +153 -0
- package/assets/codemods/run-codemod.mjs +177 -34
- package/assets/codemods/runner.mjs +350 -102
- package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
- package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
- package/assets/codemods/transforms/next/index.mjs +19 -1
- package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
- package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
- package/assets/docs/cli-integrations.doc.mjs +30 -30
- package/assets/docs/cli.doc.mjs +15 -0
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
- package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
- package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
- package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
- package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
- package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
- package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
- package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
- package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
- package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
- package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
- package/authoring/codemod/codemod.doc.mjs +1 -1
- package/authoring/codemod/type.ts +12 -0
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/doctypes/_schema.d.mts +71 -141
- package/authoring/doctypes/_schema.mjs +29 -2
- package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/type.ts +2 -2
- package/authoring/doctypes/doctypes-new.test.mjs +48 -6
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +1 -1
- package/authoring/doctypes/function/type.ts +1 -1
- package/authoring/doctypes/hook/type.ts +2 -2
- package/authoring/doctypes/load-contract.test.mjs +3 -2
- package/authoring/doctypes/namespace/namespace.doc.mjs +2 -2
- package/authoring/doctypes/namespace/parse.test.mjs +23 -25
- package/authoring/doctypes/namespace/type.ts +5 -2
- package/authoring/doctypes/parse.d.mts +4 -2
- package/authoring/doctypes/parse.mjs +10 -5
- package/authoring/doctypes/reference/reference.doc.mjs +6 -4
- package/authoring/doctypes/reference/type.ts +9 -10
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -2
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/theme/parse.d.mts +35 -0
- package/authoring/doctypes/theme/parse.mjs +76 -0
- package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
- package/authoring/doctypes/theme/theme.doc.mjs +79 -0
- package/authoring/doctypes/theme/type.ts +42 -0
- package/authoring/doctypes/types.ts +2 -1
- package/authoring/gap-report/gap-report.doc.d.mts +12 -0
- package/authoring/gap-report/gap-report.doc.mjs +183 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +7 -4
- package/authoring/index.mjs +2 -1
- package/authoring/integration/integration.doc.mjs +2 -2
- package/authoring/integration/type.ts +3 -9
- package/clients/cli/__tests__/cliManifest.test.ts +27 -29
- package/clients/cli/commands/blog.doc.mjs +1 -1
- package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
- package/clients/cli/commands/build-theme.mjs +10 -44
- package/clients/cli/commands/build.doc.mjs +1 -1
- package/clients/cli/commands/component.doc.mjs +1 -1
- package/clients/cli/commands/discover.doc.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +15 -58
- package/clients/cli/commands/docs.test.mjs +11 -14
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.test.mjs +15 -7
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/gap-report.doc.mjs +1 -1
- package/clients/cli/commands/hook.doc.mjs +1 -1
- package/clients/cli/commands/init.doc.mjs +1 -1
- package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
- package/clients/cli/commands/integration-add.doc.mjs +1 -1
- package/clients/cli/commands/integration-pack.doc.mjs +1 -1
- package/clients/cli/commands/integration-real-world.test.mjs +3 -9
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/layout-check.doc.mjs +1 -1
- package/clients/cli/commands/layout-expand.doc.mjs +1 -1
- package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +1 -1
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/text-json-parity.test.mjs +718 -0
- package/clients/cli/commands/theme-add.doc.mjs +2 -2
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/clients/cli/commands/theme-list.doc.mjs +2 -2
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
- package/clients/cli/commands/theme-palette.doc.mjs +1 -1
- package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
- package/clients/cli/commands/theme-targets.doc.mjs +1 -1
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/theme.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +3 -2
- package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
- package/clients/cli/commands/upgrade.mjs +29 -7
- package/clients/cli/formatters/index.mjs +2 -0
- package/clients/cli/formatters/index.test.mjs +6 -0
- package/clients/cli/lib/hook-format.mjs +14 -5
- package/foundation/config/project-themes.test.mjs +11 -19
- package/foundation/config/project.d.mts +8 -0
- package/foundation/config/project.mjs +50 -21
- package/foundation/config/project.test.mjs +3 -16
- package/foundation/discovery/authoring-self-docs.d.mts +6 -0
- package/foundation/discovery/authoring-self-docs.mjs +17 -7
- package/foundation/discovery/authoring-self-docs.test.mjs +3 -2
- package/foundation/discovery/authoring-surface.d.mts +74 -0
- package/foundation/discovery/authoring-surface.mjs +525 -0
- package/foundation/discovery/authoring-surface.test.mjs +392 -0
- package/foundation/discovery/cli-self-docs.d.mts +113 -0
- package/foundation/discovery/cli-self-docs.mjs +514 -0
- package/foundation/discovery/cli-self-docs.test.mjs +437 -0
- package/foundation/discovery/component-loader.d.mts +35 -38
- package/foundation/discovery/component-loader.mjs +53 -222
- package/foundation/discovery/docs-discovery.d.mts +3 -2
- package/foundation/discovery/docs-discovery.mjs +14 -17
- package/foundation/discovery/docs-discovery.test.mjs +15 -15
- package/foundation/discovery/docs-section-key.d.mts +19 -8
- package/foundation/discovery/docs-section-key.mjs +119 -32
- package/foundation/discovery/docs-section-key.test.mjs +41 -19
- package/foundation/discovery/template-adapter.d.mts +7 -0
- package/foundation/discovery/template-adapter.mjs +27 -40
- package/foundation/discovery/template-adapter.test.mjs +15 -0
- package/foundation/discovery/theme-discovery.d.mts +67 -7
- package/foundation/discovery/theme-discovery.mjs +916 -186
- package/foundation/discovery/theme-discovery.test.mjs +613 -219
- package/foundation/doc-compiler/bundle.d.mts +47 -0
- package/foundation/doc-compiler/bundle.mjs +218 -0
- package/foundation/doc-compiler/bundle.test.mjs +255 -0
- package/foundation/doc-compiler/compile.d.mts +200 -0
- package/foundation/doc-compiler/compile.mjs +259 -9
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +277 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
- package/foundation/doc-compiler/import.d.mts +24 -0
- package/foundation/doc-compiler/import.mjs +59 -0
- package/foundation/doc-compiler/inputs.d.mts +102 -0
- package/foundation/doc-compiler/inputs.mjs +286 -0
- package/foundation/doc-compiler/inputs.test.mjs +299 -0
- package/foundation/doc-compiler/ir.d.mts +13 -0
- package/foundation/doc-compiler/ir.mjs +189 -12
- package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
- package/foundation/doc-compiler/overlays.d.mts +37 -0
- package/foundation/doc-compiler/overlays.mjs +206 -0
- package/foundation/doc-compiler/parse-readable.d.mts +9 -0
- package/foundation/doc-compiler/parse-readable.mjs +29 -0
- package/foundation/doc-compiler/read.d.mts +126 -0
- package/foundation/doc-compiler/read.mjs +320 -0
- package/foundation/doc-compiler/read.test.mjs +313 -0
- package/foundation/doc-compiler/source.d.mts +33 -0
- package/foundation/doc-compiler/source.mjs +128 -0
- package/foundation/fs/file-protection.d.mts +33 -0
- package/foundation/fs/file-protection.mjs +825 -0
- package/foundation/fs/file-protection.test.mjs +250 -0
- package/foundation/integrations/autolink.d.mts +58 -1
- package/foundation/integrations/autolink.mjs +143 -57
- package/foundation/integrations/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +1 -1
- package/foundation/integrations/contribution-inventory.mjs +17 -20
- package/foundation/integrations/contribution-inventory.test.mjs +67 -27
- package/foundation/integrations/integrations.d.mts +3 -0
- package/foundation/integrations/integrations.mjs +11 -97
- package/foundation/integrations/provider-ledger.test.mjs +275 -0
- package/foundation/integrations/provider-resolution.d.mts +152 -0
- package/foundation/integrations/provider-resolution.mjs +576 -0
- package/foundation/integrations/provider-resolution.test.mjs +369 -0
- package/foundation/integrations/theme-descriptor.d.mts +8 -0
- package/foundation/integrations/theme-descriptor.mjs +44 -0
- package/foundation/integrations/validate-contributions.mjs +104 -10
- package/foundation/response/error-codes.d.mts +3 -1
- package/foundation/response/error-codes.d.ts +2 -0
- package/foundation/response/error-codes.doc.mjs +12 -2
- package/foundation/response/error-codes.mjs +7 -1
- package/foundation/response/error-codes.test.mjs +55 -11
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/response/response.doc.mjs +1 -1
- package/foundation/text/string-utils.mjs +22 -10
- package/package.json +10 -9
- package/assets/templates/themes/manifest.json +0 -95
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Every authored descriptor becomes exactly one compiler input.
|
|
5
|
+
*
|
|
6
|
+
* The repo check is the completeness oracle for what Core and the CLI ship:
|
|
7
|
+
* every `.doc.mjs` under a descriptor root is read once, and nothing else is.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import {Project} from '../config/project.mjs';
|
|
15
|
+
import {CLI_ROOT} from '../fs/paths.mjs';
|
|
16
|
+
import {collectDocInputs, packageSource} from './inputs.mjs';
|
|
17
|
+
|
|
18
|
+
const REPO_ROOT = path.resolve(CLI_ROOT, '..', '..');
|
|
19
|
+
const SKIP = new Set(['__tests__', '__fixtures__', 'fixtures', 'node_modules']);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Every `.doc.mjs` under a directory, by real path.
|
|
23
|
+
* @param {string} dir
|
|
24
|
+
* @param {Set<string>} into
|
|
25
|
+
*/
|
|
26
|
+
function docFiles(dir, into) {
|
|
27
|
+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
|
|
28
|
+
const full = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
if (!SKIP.has(entry.name)) docFiles(full, into);
|
|
31
|
+
} else if (entry.name.endsWith('.doc.mjs')) {
|
|
32
|
+
into.add(fs.realpathSync(full));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return into;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let tmpDir;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// Inside the repo, so the project finds this checkout's Core.
|
|
42
|
+
tmpDir = fs.mkdtempSync(path.join(REPO_ROOT, '.astryx-doc-inputs-'));
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
path.join(tmpDir, 'package.json'),
|
|
45
|
+
JSON.stringify({name: 'consumer', version: '1.0.0'}),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('collectDocInputs over what this repo ships', () => {
|
|
54
|
+
it('reads every descriptor Core and the CLI ship exactly once', async () => {
|
|
55
|
+
const {inputs, problems} = await collectDocInputs(
|
|
56
|
+
await Project.load(tmpDir),
|
|
57
|
+
);
|
|
58
|
+
expect(problems).toEqual([]);
|
|
59
|
+
|
|
60
|
+
// What ships comes from the packages themselves, not from the module under
|
|
61
|
+
// test: Core's source, and every directory the CLI's package.json ships.
|
|
62
|
+
const shipped = new Set();
|
|
63
|
+
docFiles(path.join(REPO_ROOT, 'packages', 'core', 'src'), shipped);
|
|
64
|
+
const {files} = JSON.parse(
|
|
65
|
+
fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf-8'),
|
|
66
|
+
);
|
|
67
|
+
for (const entry of files) {
|
|
68
|
+
const full = path.join(CLI_ROOT, entry);
|
|
69
|
+
if (fs.statSync(full).isDirectory()) docFiles(full, shipped);
|
|
70
|
+
}
|
|
71
|
+
const read = inputs.map(input => input.file);
|
|
72
|
+
const rel = (/** @type {string} */ file) => path.relative(REPO_ROOT, file);
|
|
73
|
+
expect(read.filter(file => !shipped.has(file)).map(rel)).toEqual([]);
|
|
74
|
+
expect([...shipped].filter(file => !read.includes(file)).map(rel)).toEqual(
|
|
75
|
+
[],
|
|
76
|
+
);
|
|
77
|
+
expect(new Set(read).size).toBe(read.length);
|
|
78
|
+
expect(new Set(inputs.map(input => input.id)).size).toBe(inputs.length);
|
|
79
|
+
}, 120_000);
|
|
80
|
+
|
|
81
|
+
it('pins the core docs that `component <Name>` reads but the list leaves out', async () => {
|
|
82
|
+
const {inputs} = await collectDocInputs(await Project.load(tmpDir));
|
|
83
|
+
// Each is readable by name; listing them would change `component --list`,
|
|
84
|
+
// so a new entry here is a decision, not a silent drift.
|
|
85
|
+
expect(
|
|
86
|
+
inputs
|
|
87
|
+
.filter(input => input.listed === false)
|
|
88
|
+
.map(input => input.name)
|
|
89
|
+
.sort(),
|
|
90
|
+
).toEqual([
|
|
91
|
+
'Chat',
|
|
92
|
+
'ChatComposerTokenElement',
|
|
93
|
+
'Code',
|
|
94
|
+
'ContextMenuItem',
|
|
95
|
+
'Heading',
|
|
96
|
+
'Indicator',
|
|
97
|
+
'Layer',
|
|
98
|
+
'NavMenu',
|
|
99
|
+
'Resizable',
|
|
100
|
+
'SyntaxTheme',
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('keys templates by type and directory, not by their display title', async () => {
|
|
105
|
+
const {inputs} = await collectDocInputs(await Project.load(tmpDir));
|
|
106
|
+
const byName = new Map(
|
|
107
|
+
inputs
|
|
108
|
+
.filter(input => input.root === 'templates')
|
|
109
|
+
.map(input => [input.name, input]),
|
|
110
|
+
);
|
|
111
|
+
// A showcase shares its title with the block it shows; both stay inputs.
|
|
112
|
+
expect(byName.get('block/BannerStatuses')?.source).toBe(
|
|
113
|
+
'@astryxdesign/cli/assets/templates/blocks/components/Banner/BannerStatuses.doc.mjs',
|
|
114
|
+
);
|
|
115
|
+
expect(byName.get('block/BannerShowcase')?.source).toBe(
|
|
116
|
+
'@astryxdesign/cli/assets/templates/blocks/components/Banner/BannerShowcase.doc.mjs',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('collectDocInputs over an integration', () => {
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} rel
|
|
124
|
+
* @param {string} text
|
|
125
|
+
*/
|
|
126
|
+
function write(rel, text) {
|
|
127
|
+
const file = path.join(tmpDir, 'node_modules', '@acme', 'kit', rel);
|
|
128
|
+
fs.mkdirSync(path.dirname(file), {recursive: true});
|
|
129
|
+
fs.writeFileSync(file, text);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
it('lists each descriptor once, owned by the package, with where it ships', async () => {
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(tmpDir, 'package.json'),
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
name: 'consumer',
|
|
137
|
+
version: '1.0.0',
|
|
138
|
+
dependencies: {'@acme/kit': '^1.0.0'},
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
write(
|
|
142
|
+
'package.json',
|
|
143
|
+
JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
|
|
144
|
+
);
|
|
145
|
+
write(
|
|
146
|
+
'astryx.integration.mjs',
|
|
147
|
+
"export default {components: './components', templates: './templates', docs: './docs', themes: './themes'};\n",
|
|
148
|
+
);
|
|
149
|
+
write(
|
|
150
|
+
'components/AcmeCard.tsx',
|
|
151
|
+
'export function AcmeCard() { return null; }\n',
|
|
152
|
+
);
|
|
153
|
+
write(
|
|
154
|
+
'components/AcmeCard.doc.mjs',
|
|
155
|
+
"export default {type: 'component', name: 'AcmeCard', displayName: 'Acme Card', usage: {description: 'A card.'}, props: []};\n",
|
|
156
|
+
);
|
|
157
|
+
write(
|
|
158
|
+
'templates/hero.tsx',
|
|
159
|
+
'export default function Hero() { return null; }\n',
|
|
160
|
+
);
|
|
161
|
+
write(
|
|
162
|
+
'templates/hero.doc.mjs',
|
|
163
|
+
"export default {type: 'block', name: 'Hero block', description: 'A hero.'};\n",
|
|
164
|
+
);
|
|
165
|
+
const topic = (/** @type {object} */ fields) =>
|
|
166
|
+
`export default ${JSON.stringify({
|
|
167
|
+
type: 'generic',
|
|
168
|
+
title: 'Acme',
|
|
169
|
+
description: 'Acme notes.',
|
|
170
|
+
sections: [{title: 'Acme', content: [{type: 'prose', text: 'Hi.'}]}],
|
|
171
|
+
...fields,
|
|
172
|
+
})};\n`;
|
|
173
|
+
write('docs/acme-guide.doc.mjs', topic({name: 'acme-guide'}));
|
|
174
|
+
write(
|
|
175
|
+
'docs/theme-notes.doc.mjs',
|
|
176
|
+
topic({name: 'theme-notes', extends: 'theme'}),
|
|
177
|
+
);
|
|
178
|
+
write('themes/ocean/oceanTheme.ts', 'export const oceanTheme = {};\n');
|
|
179
|
+
write(
|
|
180
|
+
'themes/ocean/oceanTheme.doc.mjs',
|
|
181
|
+
"/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */\nexport default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Blue.', maintained: true};\n",
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const project = await Project.load(tmpDir);
|
|
185
|
+
expect(await project.issues()).toEqual([]);
|
|
186
|
+
const {inputs, problems} = await collectDocInputs(project);
|
|
187
|
+
expect(problems).toEqual([]);
|
|
188
|
+
expect(
|
|
189
|
+
inputs
|
|
190
|
+
.filter(input => input.owner === '@acme/kit')
|
|
191
|
+
.map(({id, source, role}) => ({id, source, role})),
|
|
192
|
+
).toEqual([
|
|
193
|
+
{
|
|
194
|
+
id: '@acme/kit:components:AcmeCard',
|
|
195
|
+
source: '@acme/kit/components/AcmeCard.doc.mjs',
|
|
196
|
+
role: undefined,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: '@acme/kit:templates:block/hero',
|
|
200
|
+
source: '@acme/kit/templates/hero.doc.mjs',
|
|
201
|
+
role: undefined,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: '@acme/kit:themes:ocean',
|
|
205
|
+
source: '@acme/kit/themes/ocean/oceanTheme.doc.mjs',
|
|
206
|
+
role: undefined,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: '@acme/kit:docs:theme+theme-notes',
|
|
210
|
+
source: '@acme/kit/docs/theme-notes.doc.mjs',
|
|
211
|
+
role: 'extension',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: '@acme/kit:docs:acme-guide',
|
|
215
|
+
source: '@acme/kit/docs/acme-guide.doc.mjs',
|
|
216
|
+
role: 'base',
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('collectDocInputs problems', () => {
|
|
223
|
+
/** @param {object} overrides */
|
|
224
|
+
const project = overrides =>
|
|
225
|
+
/** @type {any} */ ({
|
|
226
|
+
cwd: os.tmpdir(),
|
|
227
|
+
components: async () => [],
|
|
228
|
+
templates: async () => [],
|
|
229
|
+
themes: async () => [],
|
|
230
|
+
docs: async () => ({entries: () => []}),
|
|
231
|
+
...overrides,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('reports a file two roots both claim, and reads it once', async () => {
|
|
235
|
+
const file = path.join(tmpDir, 'Shared.doc.mjs');
|
|
236
|
+
fs.writeFileSync(file, 'export default {};\n');
|
|
237
|
+
const {inputs, problems} = await collectDocInputs(
|
|
238
|
+
project({
|
|
239
|
+
components: async () => [
|
|
240
|
+
{name: 'Shared', package: '@acme/kit', docPath: file},
|
|
241
|
+
],
|
|
242
|
+
templates: async () => [
|
|
243
|
+
{
|
|
244
|
+
type: 'block',
|
|
245
|
+
dirName: 'shared',
|
|
246
|
+
package: '@acme/kit',
|
|
247
|
+
docPath: file,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
expect(
|
|
253
|
+
inputs.filter(input => input.file === fs.realpathSync(file)),
|
|
254
|
+
).toHaveLength(1);
|
|
255
|
+
expect(problems).toEqual([
|
|
256
|
+
{
|
|
257
|
+
code: 'duplicate_file',
|
|
258
|
+
provider: '@acme/kit',
|
|
259
|
+
source: expect.stringMatching(/\/Shared\.doc\.mjs$/),
|
|
260
|
+
message: expect.stringContaining(
|
|
261
|
+
'is read as templates "block/shared" and as components "Shared"',
|
|
262
|
+
),
|
|
263
|
+
},
|
|
264
|
+
]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('reports two files under one input id', async () => {
|
|
268
|
+
const one = path.join(tmpDir, 'a', 'Card.doc.mjs');
|
|
269
|
+
const two = path.join(tmpDir, 'b', 'Card.doc.mjs');
|
|
270
|
+
for (const file of [one, two]) {
|
|
271
|
+
fs.mkdirSync(path.dirname(file), {recursive: true});
|
|
272
|
+
fs.writeFileSync(file, 'export default {};\n');
|
|
273
|
+
}
|
|
274
|
+
const {problems} = await collectDocInputs(
|
|
275
|
+
project({
|
|
276
|
+
components: async () => [
|
|
277
|
+
{name: 'Card', package: '@acme/kit', docPath: one},
|
|
278
|
+
{name: 'Card', package: '@acme/kit', docPath: two},
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
expect(problems).toEqual([
|
|
283
|
+
{
|
|
284
|
+
code: 'duplicate_id',
|
|
285
|
+
provider: '@acme/kit',
|
|
286
|
+
source: expect.stringMatching(/\/Card\.doc\.mjs$/),
|
|
287
|
+
message: expect.stringContaining('both read as components "Card"'),
|
|
288
|
+
},
|
|
289
|
+
]);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('packageSource', () => {
|
|
294
|
+
it('names the package a file ships in and its path inside it', () => {
|
|
295
|
+
expect(
|
|
296
|
+
packageSource(path.join(CLI_ROOT, 'api', 'docs', 'docs.doc.mjs')),
|
|
297
|
+
).toBe('@astryxdesign/cli/api/docs/docs.doc.mjs');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
@@ -7,3 +7,16 @@
|
|
|
7
7
|
* @returns {import('./compile.mjs').CompiledReferenceNode}
|
|
8
8
|
*/
|
|
9
9
|
export function parseCompiledReferenceNode(value: unknown): import("./compile.mjs").CompiledReferenceNode;
|
|
10
|
+
/**
|
|
11
|
+
* Validate a compiled node of any kind.
|
|
12
|
+
* @param {unknown} value
|
|
13
|
+
* @returns {import('./compile.mjs').CompiledDocNode | import('./compile.mjs').CompiledReferenceNode}
|
|
14
|
+
*/
|
|
15
|
+
export function parseCompiledDocNode(value: unknown): import("./compile.mjs").CompiledDocNode | import("./compile.mjs").CompiledReferenceNode;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a compiled docs bundle: every node, every diagnostic, and one node
|
|
18
|
+
* per id.
|
|
19
|
+
* @param {unknown} value
|
|
20
|
+
* @returns {import('./bundle.mjs').CompiledDocsBundle}
|
|
21
|
+
*/
|
|
22
|
+
export function parseCompiledDocsBundle(value: unknown): import("./bundle.mjs").CompiledDocsBundle;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file The compiled-
|
|
4
|
+
* @file The compiled-doc contract and its sealed parsers.
|
|
5
5
|
*
|
|
6
|
-
* @input Any value that claims to be a compiled
|
|
7
|
-
* read back from JSON.
|
|
6
|
+
* @input Any value that claims to be a compiled node or a compiled docs bundle
|
|
7
|
+
* — typically one read back from JSON.
|
|
8
8
|
* @output The same value once it validates; a thrown Error naming the problems
|
|
9
|
-
* otherwise
|
|
10
|
-
*
|
|
9
|
+
* otherwise, carrying a compiler diagnostic (`unsupported_schema` or
|
|
10
|
+
* `invalid_bundle`). An unsupported schema version fails with its own message
|
|
11
|
+
* before anything else is checked.
|
|
11
12
|
* @position The load boundary for compiled nodes that did not come straight
|
|
12
13
|
* from ./compile.mjs in this process. The value is returned as given, not
|
|
13
14
|
* rebuilt, so key order (which response JSON follows) survives. A node is
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
import {SECTION_KEY_RE} from '../discovery/docs-section-key.mjs';
|
|
18
|
-
import {COMPILED_DOC_SCHEMA_VERSION} from './compile.mjs';
|
|
19
|
+
import {COMPILED_DOC_KINDS, COMPILED_DOC_SCHEMA_VERSION} from './compile.mjs';
|
|
20
|
+
import {diagnostic, diagnosticProblem} from './diagnostics.mjs';
|
|
19
21
|
|
|
20
22
|
const NODE_FIELDS = new Set([
|
|
21
23
|
'schemaVersion',
|
|
@@ -65,15 +67,13 @@ const isPackageName = value =>
|
|
|
65
67
|
*/
|
|
66
68
|
export function parseCompiledReferenceNode(value) {
|
|
67
69
|
const node = /** @type {any} */ (value);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`Compiled doc schema version ${JSON.stringify(node?.schemaVersion)} is not supported; this CLI reads version ${COMPILED_DOC_SCHEMA_VERSION}. Compile the docs again with this CLI.`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
70
|
+
const skew = schemaVersionProblem(node);
|
|
71
|
+
if (skew) throw compiledDocError('unsupported_schema', skew);
|
|
73
72
|
const problems = jsonProblems(node, 'node');
|
|
74
73
|
if (problems.length === 0) problems.push(...structureProblems(node));
|
|
75
74
|
if (problems.length > 0) {
|
|
76
|
-
throw
|
|
75
|
+
throw compiledDocError(
|
|
76
|
+
'invalid_bundle',
|
|
77
77
|
`Invalid compiled doc node: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
|
|
78
78
|
);
|
|
79
79
|
}
|
|
@@ -285,3 +285,180 @@ function resolutionProblem(resolved) {
|
|
|
285
285
|
return `status: expected resolved, unknown-topic or unknown-section, got ${JSON.stringify(resolved.status)}`;
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
|
+
|
|
289
|
+
const DOC_NODE_FIELDS = new Set([
|
|
290
|
+
'schemaVersion',
|
|
291
|
+
'kind',
|
|
292
|
+
'stage',
|
|
293
|
+
'id',
|
|
294
|
+
'lang',
|
|
295
|
+
'provenance',
|
|
296
|
+
'doc',
|
|
297
|
+
]);
|
|
298
|
+
const BUNDLE_FIELDS = new Set([
|
|
299
|
+
'schemaVersion',
|
|
300
|
+
'lang',
|
|
301
|
+
'nodes',
|
|
302
|
+
'diagnostics',
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* A package-relative source, never a location on one machine.
|
|
307
|
+
* @param {unknown} value
|
|
308
|
+
* @returns {boolean}
|
|
309
|
+
*/
|
|
310
|
+
const isPackageSource = value =>
|
|
311
|
+
isPackageName(value) &&
|
|
312
|
+
!/(^|\/)\.\.(\/|$)/.test(/** @type {string} */ (value));
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* An Error for a value that is not a compiled doc, carrying the diagnostic a
|
|
316
|
+
* reader reports.
|
|
317
|
+
* @param {'unsupported_schema' | 'invalid_bundle'} code
|
|
318
|
+
* @param {string} message
|
|
319
|
+
* @returns {Error & {diagnostic: import('./diagnostics.mjs').CompilerDiagnostic}}
|
|
320
|
+
*/
|
|
321
|
+
function compiledDocError(code, message) {
|
|
322
|
+
const error = /** @type {Error & {diagnostic: any}} */ (new Error(message));
|
|
323
|
+
error.diagnostic = diagnostic(code, {message});
|
|
324
|
+
return error;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @param {unknown} value
|
|
329
|
+
*/
|
|
330
|
+
function schemaVersionProblem(value) {
|
|
331
|
+
const version = /** @type {any} */ (value)?.schemaVersion;
|
|
332
|
+
return version === COMPILED_DOC_SCHEMA_VERSION
|
|
333
|
+
? null
|
|
334
|
+
: `Compiled doc schema version ${JSON.stringify(version)} is not supported; this CLI reads version ${COMPILED_DOC_SCHEMA_VERSION}. Compile the docs again with this CLI.`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Validate a compiled node of any kind.
|
|
339
|
+
* @param {unknown} value
|
|
340
|
+
* @returns {import('./compile.mjs').CompiledDocNode | import('./compile.mjs').CompiledReferenceNode}
|
|
341
|
+
*/
|
|
342
|
+
export function parseCompiledDocNode(value) {
|
|
343
|
+
const skew = schemaVersionProblem(value);
|
|
344
|
+
if (skew) throw compiledDocError('unsupported_schema', skew);
|
|
345
|
+
const problems = nodeProblems(value, 'node');
|
|
346
|
+
if (problems.length > 0) {
|
|
347
|
+
throw compiledDocError(
|
|
348
|
+
'invalid_bundle',
|
|
349
|
+
`Invalid compiled doc node: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return /** @type {any} */ (value);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Validate a compiled docs bundle: every node, every diagnostic, and one node
|
|
357
|
+
* per id.
|
|
358
|
+
* @param {unknown} value
|
|
359
|
+
* @returns {import('./bundle.mjs').CompiledDocsBundle}
|
|
360
|
+
*/
|
|
361
|
+
export function parseCompiledDocsBundle(value) {
|
|
362
|
+
const skew = schemaVersionProblem(value);
|
|
363
|
+
if (skew) throw compiledDocError('unsupported_schema', skew);
|
|
364
|
+
const bundle = /** @type {any} */ (value);
|
|
365
|
+
/** @type {string[]} */
|
|
366
|
+
const problems = jsonProblems(bundle, 'bundle');
|
|
367
|
+
if (problems.length === 0) {
|
|
368
|
+
const unknown = Object.keys(bundle).filter(key => !BUNDLE_FIELDS.has(key));
|
|
369
|
+
if (unknown.length > 0)
|
|
370
|
+
problems.push(`unknown fields: ${unknown.join(', ')}`);
|
|
371
|
+
if (bundle.lang !== null && !isText(bundle.lang)) {
|
|
372
|
+
problems.push('lang: expected a language or null');
|
|
373
|
+
}
|
|
374
|
+
if (!Array.isArray(bundle.nodes)) {
|
|
375
|
+
problems.push('nodes: expected an array');
|
|
376
|
+
} else {
|
|
377
|
+
const ids = new Set();
|
|
378
|
+
bundle.nodes.forEach(
|
|
379
|
+
(/** @type {unknown} */ node, /** @type {number} */ index) => {
|
|
380
|
+
for (const problem of nodeProblems(node, `nodes[${index}]`)) {
|
|
381
|
+
problems.push(problem);
|
|
382
|
+
}
|
|
383
|
+
const id = /** @type {any} */ (node)?.id;
|
|
384
|
+
if (ids.has(id))
|
|
385
|
+
problems.push(`nodes[${index}].id: "${id}" appears twice`);
|
|
386
|
+
ids.add(id);
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
if (!Array.isArray(bundle.diagnostics)) {
|
|
391
|
+
problems.push('diagnostics: expected an array');
|
|
392
|
+
} else {
|
|
393
|
+
bundle.diagnostics.forEach(
|
|
394
|
+
(/** @type {unknown} */ d, /** @type {number} */ index) => {
|
|
395
|
+
const problem = diagnosticProblem(d);
|
|
396
|
+
if (problem) problems.push(`diagnostics[${index}]: ${problem}`);
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (problems.length > 0) {
|
|
402
|
+
throw compiledDocError(
|
|
403
|
+
'invalid_bundle',
|
|
404
|
+
`Invalid compiled docs bundle: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return bundle;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Problems with one node of any kind, each prefixed with where it is.
|
|
412
|
+
* @param {unknown} value
|
|
413
|
+
* @param {string} at
|
|
414
|
+
* @returns {string[]}
|
|
415
|
+
*/
|
|
416
|
+
function nodeProblems(value, at) {
|
|
417
|
+
const node = /** @type {any} */ (value);
|
|
418
|
+
if (node?.schemaVersion !== COMPILED_DOC_SCHEMA_VERSION) {
|
|
419
|
+
return [`${at}.schemaVersion: expected ${COMPILED_DOC_SCHEMA_VERSION}`];
|
|
420
|
+
}
|
|
421
|
+
const json = jsonProblems(node, at);
|
|
422
|
+
if (json.length > 0) return json;
|
|
423
|
+
if (node.kind === 'reference') {
|
|
424
|
+
return structureProblems(node).map(problem => `${at}: ${problem}`);
|
|
425
|
+
}
|
|
426
|
+
/** @type {string[]} */
|
|
427
|
+
const problems = [];
|
|
428
|
+
const unknown = Object.keys(node).filter(key => !DOC_NODE_FIELDS.has(key));
|
|
429
|
+
if (unknown.length > 0)
|
|
430
|
+
problems.push(`${at}: unknown fields: ${unknown.join(', ')}`);
|
|
431
|
+
if (!COMPILED_DOC_KINDS.includes(node.kind)) {
|
|
432
|
+
problems.push(
|
|
433
|
+
`${at}.kind: ${JSON.stringify(node.kind)} is not a compiled doc kind`,
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (node.stage !== 'lowered')
|
|
437
|
+
problems.push(`${at}.stage: expected "lowered"`);
|
|
438
|
+
if (!isText(node.id)) problems.push(`${at}.id: expected an id`);
|
|
439
|
+
if (node.lang !== null && !isText(node.lang)) {
|
|
440
|
+
problems.push(`${at}.lang: expected a language or null`);
|
|
441
|
+
}
|
|
442
|
+
const provenance = node.provenance;
|
|
443
|
+
if (
|
|
444
|
+
!isRecord(provenance) ||
|
|
445
|
+
Object.keys(provenance).length !== 2 ||
|
|
446
|
+
!isPackageName(provenance.provider) ||
|
|
447
|
+
!isPackageSource(provenance.source)
|
|
448
|
+
) {
|
|
449
|
+
problems.push(
|
|
450
|
+
`${at}.provenance: expected {provider, source} naming a package and a path inside it, not a machine path`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (!isRecord(node.doc) || !isText(node.doc.name)) {
|
|
454
|
+
problems.push(`${at}.doc: expected a doc with a name`);
|
|
455
|
+
} else if (
|
|
456
|
+
(node.kind === 'page' || node.kind === 'block' || node.kind === 'theme') &&
|
|
457
|
+
node.doc.type !== node.kind
|
|
458
|
+
) {
|
|
459
|
+
problems.push(
|
|
460
|
+
`${at}.doc.type: a ${node.kind} node holds a ${node.kind} doc`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return problems;
|
|
464
|
+
}
|