@craft-ts/dev-tools 0.7.0-beta.13
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/LICENSE +21 -0
- package/README.md +154 -0
- package/generators.json +26 -0
- package/package.json +119 -0
- package/src/bin/craft-architecture-check.d.ts +2 -0
- package/src/bin/craft-architecture-check.js +51 -0
- package/src/bin/craft-architecture-check.js.map +1 -0
- package/src/bin/craft-brand.d.ts +2 -0
- package/src/bin/craft-brand.js +9 -0
- package/src/bin/craft-brand.js.map +1 -0
- package/src/bin/craft-graph.d.ts +2 -0
- package/src/bin/craft-graph.js +72 -0
- package/src/bin/craft-graph.js.map +1 -0
- package/src/bin/craft-migrate-architecture.d.ts +2 -0
- package/src/bin/craft-migrate-architecture.js +64 -0
- package/src/bin/craft-migrate-architecture.js.map +1 -0
- package/src/bin/craft-migrate-components.d.ts +2 -0
- package/src/bin/craft-migrate-components.js +67 -0
- package/src/bin/craft-migrate-components.js.map +1 -0
- package/src/bin/craft-migrate-primitives.d.ts +2 -0
- package/src/bin/craft-migrate-primitives.js +71 -0
- package/src/bin/craft-migrate-primitives.js.map +1 -0
- package/src/bin/craft-migrate-routes.d.ts +2 -0
- package/src/bin/craft-migrate-routes.js +77 -0
- package/src/bin/craft-migrate-routes.js.map +1 -0
- package/src/bin/craft-migrate-services.d.ts +2 -0
- package/src/bin/craft-migrate-services.js +75 -0
- package/src/bin/craft-migrate-services.js.map +1 -0
- package/src/bin/craft-migrate-template.d.ts +2 -0
- package/src/bin/craft-migrate-template.js +68 -0
- package/src/bin/craft-migrate-template.js.map +1 -0
- package/src/bin/craft-migrate.d.ts +2 -0
- package/src/bin/craft-migrate.js +91 -0
- package/src/bin/craft-migrate.js.map +1 -0
- package/src/bin/craft.d.ts +2 -0
- package/src/bin/craft.js +318 -0
- package/src/bin/craft.js.map +1 -0
- package/src/eslint-rules/anchor-has-href.cjs +35 -0
- package/src/eslint-rules/app-start-registry-match.cjs +355 -0
- package/src/eslint-rules/button-has-type.cjs +50 -0
- package/src/eslint-rules/control-has-accessible-name.cjs +56 -0
- package/src/eslint-rules/craft-component-name-match.cjs +8 -0
- package/src/eslint-rules/craft-computed-name-match.cjs +63 -0
- package/src/eslint-rules/craft-css-token-registry.cjs +17 -0
- package/src/eslint-rules/craft-css-var-naming.cjs +18 -0
- package/src/eslint-rules/craft-css-vars-contract.cjs +34 -0
- package/src/eslint-rules/craft-directive-name-match.cjs +8 -0
- package/src/eslint-rules/craft-method-name-match.cjs +8 -0
- package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
- package/src/eslint-rules/craft-primitive-context.cjs +46 -0
- package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-styles-scope-safe.cjs +25 -0
- package/src/eslint-rules/css-rule-utils.cjs +109 -0
- package/src/eslint-rules/global-exception-registry-match.cjs +562 -0
- package/src/eslint-rules/heading-has-content.cjs +31 -0
- package/src/eslint-rules/html-helpers.cjs +115 -0
- package/src/eslint-rules/hyperscript-walk.cjs +249 -0
- package/src/eslint-rules/iframe-has-title.cjs +26 -0
- package/src/eslint-rules/img-has-alt.cjs +28 -0
- package/src/eslint-rules/index.cjs +209 -0
- package/src/eslint-rules/index.d.cts +5 -0
- package/src/eslint-rules/label-has-associated-control.cjs +67 -0
- package/src/eslint-rules/no-async-await.cjs +46 -0
- package/src/eslint-rules/no-craft-computed-side-effects.cjs +301 -0
- package/src/eslint-rules/no-craft-use-in-template.cjs +89 -0
- package/src/eslint-rules/no-craft-use.cjs +42 -0
- package/src/eslint-rules/no-direct-temporal-globals.cjs +73 -0
- package/src/eslint-rules/no-effect-in-params.cjs +265 -0
- package/src/eslint-rules/no-effect-outside-loaders.cjs +3 -0
- package/src/eslint-rules/no-ephemeral-template-form-state.cjs +131 -0
- package/src/eslint-rules/no-hardcoded-design-values.cjs +15 -0
- package/src/eslint-rules/no-heading-level-skip.cjs +101 -0
- package/src/eslint-rules/no-imperative-craft-resource-trigger.cjs +291 -0
- package/src/eslint-rules/no-important-in-component-styles.cjs +12 -0
- package/src/eslint-rules/no-injection-token.cjs +173 -0
- package/src/eslint-rules/no-invalid-insertion-pipe.cjs +162 -0
- package/src/eslint-rules/no-manual-route-provider-list.cjs +85 -0
- package/src/eslint-rules/no-noninteractive-element-interactions.cjs +66 -0
- package/src/eslint-rules/no-positive-tabindex.cjs +44 -0
- package/src/eslint-rules/no-redundant-primitive-insertion.cjs +361 -0
- package/src/eslint-rules/no-render-writes.cjs +106 -0
- package/src/eslint-rules/no-throw.cjs +132 -0
- package/src/eslint-rules/no-transition-actions.cjs +133 -0
- package/src/eslint-rules/no-type-assertions-in-template.cjs +211 -0
- package/src/eslint-rules/no-widened-route-provider-context.cjs +39 -0
- package/src/eslint-rules/prefer-browser-boundaries.cjs +92 -0
- package/src/eslint-rules/prefer-craft-http-transport.cjs +103 -0
- package/src/eslint-rules/prefer-craft-template-blocks.cjs +492 -0
- package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +105 -0
- package/src/eslint-rules/prefer-named-html-helpers.cjs +33 -0
- package/src/eslint-rules/prefer-query-method-over-state-trigger.cjs +282 -0
- package/src/eslint-rules/prefer-relative-heading.cjs +43 -0
- package/src/eslint-rules/provide-host-name-match-component.cjs +455 -0
- package/src/eslint-rules/recommended-config.cjs +77 -0
- package/src/eslint-rules/require-assert-exhaustive-route-exceptions.cjs +259 -0
- package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
- package/src/eslint-rules/require-child-route-mount-check.cjs +224 -0
- package/src/eslint-rules/require-component-monitoring.cjs +212 -0
- package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
- package/src/eslint-rules/require-craft-method-for-yieldable-callback.cjs +236 -0
- package/src/eslint-rules/require-craft-resource-trigger-yield.cjs +209 -0
- package/src/eslint-rules/require-effect-adapters.cjs +86 -0
- package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
- package/src/eslint-rules/require-focus-visible.cjs +36 -0
- package/src/eslint-rules/require-interactive-local-name.cjs +105 -0
- package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
- package/src/eslint-rules/require-outlet-heading-section.cjs +70 -0
- package/src/eslint-rules/require-pending-component-di-check.cjs +490 -0
- package/src/eslint-rules/require-primitive-context.cjs +156 -0
- package/src/eslint-rules/require-primitive-derived-property.cjs +598 -0
- package/src/eslint-rules/require-primitive-generator-unwrap.cjs +274 -0
- package/src/eslint-rules/require-reactive-template-bindings.cjs +305 -0
- package/src/eslint-rules/require-reduced-motion.cjs +38 -0
- package/src/eslint-rules/require-route-heading-outline.cjs +166 -0
- package/src/eslint-rules/require-yieldable-insertion-write.cjs +98 -0
- package/src/eslint-rules/require-yieldable-reactive-read.cjs +102 -0
- package/src/eslint-rules/require-yieldable-template-method.cjs +424 -0
- package/src/eslint-rules/role-has-required-aria.cjs +38 -0
- package/src/eslint-rules/server-function-client-match.cjs +283 -0
- package/src/eslint-rules/target-blank-noopener.cjs +31 -0
- package/src/eslint-rules/template-element-name-unique.cjs +124 -0
- package/src/eslint-rules/valid-aria.cjs +38 -0
- package/src/generators/route/compat.d.ts +3 -0
- package/src/generators/route/compat.js +6 -0
- package/src/generators/route/compat.js.map +1 -0
- package/src/generators/route/generator.d.ts +31 -0
- package/src/generators/route/generator.js +402 -0
- package/src/generators/route/generator.js.map +1 -0
- package/src/generators/route/schema.json +50 -0
- package/src/generators/route/split-schema.json +19 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +23 -0
- package/src/index.js.map +1 -0
- package/src/scripts/angular-brand-codemod.d.ts +139 -0
- package/src/scripts/angular-brand-codemod.js +2318 -0
- package/src/scripts/angular-brand-codemod.js.map +1 -0
- package/src/scripts/angular-brand-codemod.ts +3985 -0
- package/src/scripts/architecture/migrate-architecture.d.ts +14 -0
- package/src/scripts/architecture/migrate-architecture.js +534 -0
- package/src/scripts/architecture/migrate-architecture.js.map +1 -0
- package/src/scripts/architecture/migrate-architecture.ts +653 -0
- package/src/scripts/architecture-graph.d.ts +368 -0
- package/src/scripts/architecture-graph.js +2191 -0
- package/src/scripts/architecture-graph.js.map +1 -0
- package/src/scripts/architecture-graph.ts +3136 -0
- package/src/scripts/components/migrate-components.d.ts +20 -0
- package/src/scripts/components/migrate-components.js +163 -0
- package/src/scripts/components/migrate-components.js.map +1 -0
- package/src/scripts/components/migrate-components.ts +227 -0
- package/src/scripts/components/migration-diagnostic.d.ts +7 -0
- package/src/scripts/components/migration-diagnostic.js +2 -0
- package/src/scripts/components/migration-diagnostic.js.map +1 -0
- package/src/scripts/components/migration-diagnostic.ts +8 -0
- package/src/scripts/create/create-project.d.ts +20 -0
- package/src/scripts/create/create-project.js +783 -0
- package/src/scripts/create/create-project.js.map +1 -0
- package/src/scripts/create/create-project.ts +849 -0
- package/src/scripts/data-flow-graph.d.ts +36 -0
- package/src/scripts/data-flow-graph.js +186 -0
- package/src/scripts/data-flow-graph.js.map +1 -0
- package/src/scripts/data-flow-graph.ts +240 -0
- package/src/scripts/dependency-graph.d.ts +152 -0
- package/src/scripts/dependency-graph.js +4456 -0
- package/src/scripts/dependency-graph.js.map +1 -0
- package/src/scripts/dependency-graph.ts +5908 -0
- package/src/scripts/effect-dependency-graph.d.ts +89 -0
- package/src/scripts/effect-dependency-graph.js +487 -0
- package/src/scripts/effect-dependency-graph.js.map +1 -0
- package/src/scripts/effect-dependency-graph.ts +630 -0
- package/src/scripts/migrate.d.ts +37 -0
- package/src/scripts/migrate.js +98 -0
- package/src/scripts/migrate.js.map +1 -0
- package/src/scripts/migrate.ts +162 -0
- package/src/scripts/migration-workspace.d.ts +2 -0
- package/src/scripts/migration-workspace.js +67 -0
- package/src/scripts/migration-workspace.js.map +1 -0
- package/src/scripts/migration-workspace.ts +93 -0
- package/src/scripts/primitives/migrate-named-primitives.d.ts +50 -0
- package/src/scripts/primitives/migrate-named-primitives.js +487 -0
- package/src/scripts/primitives/migrate-named-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-named-primitives.ts +627 -0
- package/src/scripts/primitives/migrate-primitive-generators.d.ts +30 -0
- package/src/scripts/primitives/migrate-primitive-generators.js +309 -0
- package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
- package/src/scripts/primitives/migrate-primitive-generators.ts +410 -0
- package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
- package/src/scripts/primitives/migrate-primitives.js +427 -0
- package/src/scripts/primitives/migrate-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-primitives.ts +655 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.d.ts +23 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js +382 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js.map +1 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +499 -0
- package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
- package/src/scripts/primitives/migration-diagnostic.js +2 -0
- package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
- package/src/scripts/primitives/migration-diagnostic.ts +15 -0
- package/src/scripts/routes/migrate-routes.d.ts +34 -0
- package/src/scripts/routes/migrate-routes.js +657 -0
- package/src/scripts/routes/migrate-routes.js.map +1 -0
- package/src/scripts/routes/migrate-routes.ts +895 -0
- package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
- package/src/scripts/routes/migration-diagnostic.js +2 -0
- package/src/scripts/routes/migration-diagnostic.js.map +1 -0
- package/src/scripts/routes/migration-diagnostic.ts +25 -0
- package/src/scripts/routes/route-command.d.ts +75 -0
- package/src/scripts/routes/route-command.js +1169 -0
- package/src/scripts/routes/route-command.js.map +1 -0
- package/src/scripts/routes/route-command.ts +1807 -0
- package/src/scripts/routes/verify-routes.d.ts +60 -0
- package/src/scripts/routes/verify-routes.js +1121 -0
- package/src/scripts/routes/verify-routes.js.map +1 -0
- package/src/scripts/routes/verify-routes.ts +1511 -0
- package/src/scripts/services/config.d.ts +2 -0
- package/src/scripts/services/config.js +38 -0
- package/src/scripts/services/config.js.map +1 -0
- package/src/scripts/services/config.ts +60 -0
- package/src/scripts/services/migrate-services.d.ts +29 -0
- package/src/scripts/services/migrate-services.js +924 -0
- package/src/scripts/services/migrate-services.js.map +1 -0
- package/src/scripts/services/migrate-services.ts +1348 -0
- package/src/scripts/services/migration-diagnostic.d.ts +8 -0
- package/src/scripts/services/migration-diagnostic.js +2 -0
- package/src/scripts/services/migration-diagnostic.js.map +1 -0
- package/src/scripts/services/migration-diagnostic.ts +28 -0
- package/src/scripts/template/migrate-template.d.ts +16 -0
- package/src/scripts/template/migrate-template.js +349 -0
- package/src/scripts/template/migrate-template.js.map +1 -0
- package/src/scripts/template/migrate-template.ts +418 -0
- package/src/template-migration.d.ts +1 -0
- package/src/template-migration.js +2 -0
- package/src/template-migration.js.map +1 -0
- package/src/testing.d.ts +19 -0
- package/src/testing.js +2 -0
- package/src/testing.js.map +1 -0
- package/src/tsconfig.codemod.json +13 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const {
|
|
4
|
+
IndentationText,
|
|
5
|
+
Node,
|
|
6
|
+
Project,
|
|
7
|
+
QuoteKind,
|
|
8
|
+
SyntaxKind,
|
|
9
|
+
} = require('ts-morph');
|
|
10
|
+
|
|
11
|
+
const projectCache = new Map();
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: {
|
|
17
|
+
description:
|
|
18
|
+
'Ensure route exceptions delegated to the global error component (via globalError()) are registered in CraftGlobalExceptionRegistry.',
|
|
19
|
+
},
|
|
20
|
+
fixable: 'code',
|
|
21
|
+
schema: [],
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
'Program:exit'() {
|
|
26
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
27
|
+
const filePath = getFilePath(context);
|
|
28
|
+
if (!filePath || !filePath.endsWith('.ts')) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const text = sourceCode.getText();
|
|
33
|
+
// Cheap pre-filter: either a delegated outcome (`globalError()`) that must
|
|
34
|
+
// be registered, or an existing registry that may now hold orphaned entries.
|
|
35
|
+
if (
|
|
36
|
+
!text.includes('globalError') &&
|
|
37
|
+
!text.includes('CraftGlobalExceptionRegistry')
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sourceFile = getProjectSourceFile(
|
|
43
|
+
getProject(getCwd(context)),
|
|
44
|
+
filePath,
|
|
45
|
+
text,
|
|
46
|
+
);
|
|
47
|
+
const requiredRegistrations = collectRequiredRegistrations(sourceFile);
|
|
48
|
+
// Nothing to register AND no registry to prune → nothing to do.
|
|
49
|
+
if (
|
|
50
|
+
requiredRegistrations.length === 0 &&
|
|
51
|
+
!getGlobalRegistryInterface(sourceFile)
|
|
52
|
+
) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const registryState = analyzeRegistryState(
|
|
57
|
+
sourceFile,
|
|
58
|
+
requiredRegistrations,
|
|
59
|
+
);
|
|
60
|
+
if (
|
|
61
|
+
registryState.missing.length === 0 &&
|
|
62
|
+
registryState.outOfDate.length === 0 &&
|
|
63
|
+
registryState.orphaned.length === 0
|
|
64
|
+
) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Capture the report location BEFORE mutating: purging an orphaned entry
|
|
69
|
+
// removes its node, so `reportNode.getStart()` would throw afterwards.
|
|
70
|
+
const reportLoc = getNodeLoc(sourceCode, registryState.reportNode);
|
|
71
|
+
|
|
72
|
+
ensureRegistryEntries(sourceFile, requiredRegistrations);
|
|
73
|
+
removeOrphanedRegistryEntries(sourceFile, requiredRegistrations);
|
|
74
|
+
const fixedText = sourceFile.getFullText();
|
|
75
|
+
if (fixedText === text) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
context.report({
|
|
80
|
+
loc: reportLoc,
|
|
81
|
+
message: formatRegistryMessage(registryState),
|
|
82
|
+
fix(fixer) {
|
|
83
|
+
return fixer.replaceTextRange([0, text.length], fixedText);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function getProject(cwd) {
|
|
92
|
+
let project = projectCache.get(cwd);
|
|
93
|
+
if (project) {
|
|
94
|
+
return project;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const manipulationSettings = {
|
|
98
|
+
indentationText: IndentationText.TwoSpaces,
|
|
99
|
+
quoteKind: QuoteKind.Single,
|
|
100
|
+
};
|
|
101
|
+
const tsConfigFilePath = path.join(cwd, 'tsconfig.json');
|
|
102
|
+
project = fs.existsSync(tsConfigFilePath)
|
|
103
|
+
? new Project({ tsConfigFilePath, manipulationSettings })
|
|
104
|
+
: new Project({
|
|
105
|
+
compilerOptions: { experimentalDecorators: true, target: 9 },
|
|
106
|
+
manipulationSettings,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
projectCache.set(cwd, project);
|
|
110
|
+
return project;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getProjectSourceFile(project, filePath, text) {
|
|
114
|
+
const normalizedPath = path.resolve(filePath);
|
|
115
|
+
const existingSourceFile = project.getSourceFile(normalizedPath);
|
|
116
|
+
if (existingSourceFile) {
|
|
117
|
+
existingSourceFile.replaceWithText(text);
|
|
118
|
+
return existingSourceFile;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const sourceFile = project.addSourceFileAtPathIfExists(normalizedPath);
|
|
122
|
+
if (sourceFile) {
|
|
123
|
+
sourceFile.replaceWithText(text);
|
|
124
|
+
return sourceFile;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return project.createSourceFile(normalizedPath, text, { overwrite: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getFilePath(context) {
|
|
131
|
+
const filePath = context.filename ?? context.getFilename();
|
|
132
|
+
if (!filePath || filePath === '<input>') {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return filePath;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getCwd(context) {
|
|
139
|
+
return context.cwd ?? process.cwd();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- collection: which (path, code) pairs must be registered ---
|
|
143
|
+
|
|
144
|
+
function collectRequiredRegistrations(sourceFile) {
|
|
145
|
+
// Keyed by `${path}::${code}` to dedupe; carries the collection variable.
|
|
146
|
+
const registrations = new Map();
|
|
147
|
+
|
|
148
|
+
for (const call of sourceFile.getDescendantsOfKind(
|
|
149
|
+
SyntaxKind.CallExpression,
|
|
150
|
+
)) {
|
|
151
|
+
if (getCallExpressionName(call) !== 'craftRoutes') {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const collectionVar = resolveCollectionVar(call);
|
|
156
|
+
if (!collectionVar) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const routesArray = call.getArguments()[1];
|
|
161
|
+
if (!routesArray || !Node.isArrayLiteralExpression(routesArray)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const element of routesArray.getElements()) {
|
|
166
|
+
const routeDef = extractRouteDefinition(element);
|
|
167
|
+
if (!routeDef) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const code of collectGlobalErrorCodes(routeDef.handleExceptions)) {
|
|
172
|
+
const key = `${routeDef.path}::${code}`;
|
|
173
|
+
if (!registrations.has(key)) {
|
|
174
|
+
registrations.set(key, {
|
|
175
|
+
path: routeDef.path,
|
|
176
|
+
code,
|
|
177
|
+
collectionVar,
|
|
178
|
+
reportNode: routeDef.reportNode,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [...registrations.values()];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getCallExpressionName(callExpression) {
|
|
189
|
+
const expression = callExpression.getExpression();
|
|
190
|
+
if (Node.isIdentifier(expression)) {
|
|
191
|
+
return expression.getText();
|
|
192
|
+
}
|
|
193
|
+
// `craftRoute(...).withProviders` etc. — the leftmost call's callee.
|
|
194
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
195
|
+
return expression.getName();
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// The variable the craftRoutes result is destructured into ends in `Routes`
|
|
201
|
+
// (e.g. `const { demoRoutes, … } = craftRoutes('demo', [...])`).
|
|
202
|
+
function resolveCollectionVar(craftRoutesCall) {
|
|
203
|
+
const declaration = craftRoutesCall.getFirstAncestorByKind(
|
|
204
|
+
SyntaxKind.VariableDeclaration,
|
|
205
|
+
);
|
|
206
|
+
if (!declaration) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nameNode = declaration.getNameNode();
|
|
211
|
+
if (Node.isIdentifier(nameNode)) {
|
|
212
|
+
return nameNode.getText();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (Node.isObjectBindingPattern(nameNode)) {
|
|
216
|
+
const element = nameNode
|
|
217
|
+
.getElements()
|
|
218
|
+
.find((el) => el.getNameNode().getText().endsWith('Routes'));
|
|
219
|
+
return element?.getNameNode().getText();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// A route array element is either `craftRoute('path', { … }, { … })` (optionally
|
|
226
|
+
// followed by `.withProviders(…)`) or a plain `{ path: '…', handleExceptions: { … } }`.
|
|
227
|
+
function extractRouteDefinition(element) {
|
|
228
|
+
if (Node.isObjectLiteralExpression(element)) {
|
|
229
|
+
const pathValue = readStringProperty(element, 'path');
|
|
230
|
+
const handleExceptions = readObjectProperty(element, 'handleExceptions');
|
|
231
|
+
if (!pathValue || !handleExceptions) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
return { path: pathValue, handleExceptions, reportNode: element };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (Node.isCallExpression(element)) {
|
|
238
|
+
const routeCall = findRouteCall(element);
|
|
239
|
+
if (!routeCall) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const [pathArg, , handlersArg] = routeCall.getArguments();
|
|
243
|
+
if (
|
|
244
|
+
!pathArg ||
|
|
245
|
+
!handlersArg ||
|
|
246
|
+
!Node.isObjectLiteralExpression(handlersArg) ||
|
|
247
|
+
(!Node.isStringLiteral(pathArg) &&
|
|
248
|
+
!Node.isNoSubstitutionTemplateLiteral(pathArg))
|
|
249
|
+
) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
path: pathArg.getLiteralText(),
|
|
254
|
+
handleExceptions: handlersArg,
|
|
255
|
+
reportNode: routeCall,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Walks `craftRoute(...).withProviders(...)` (any depth) down to the `craftRoute(` call.
|
|
263
|
+
function findRouteCall(node) {
|
|
264
|
+
let current = node;
|
|
265
|
+
while (Node.isCallExpression(current)) {
|
|
266
|
+
if (getCallExpressionName(current) === 'craftRoute') {
|
|
267
|
+
return current;
|
|
268
|
+
}
|
|
269
|
+
const expression = current.getExpression();
|
|
270
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
271
|
+
current = expression.getExpression();
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function readStringProperty(objectLiteral, propertyName) {
|
|
280
|
+
const property = objectLiteral
|
|
281
|
+
.getProperties()
|
|
282
|
+
.find(
|
|
283
|
+
(prop) =>
|
|
284
|
+
Node.isPropertyAssignment(prop) && prop.getName() === propertyName,
|
|
285
|
+
);
|
|
286
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
const initializer = property.getInitializer();
|
|
290
|
+
if (
|
|
291
|
+
!initializer ||
|
|
292
|
+
(!Node.isStringLiteral(initializer) &&
|
|
293
|
+
!Node.isNoSubstitutionTemplateLiteral(initializer))
|
|
294
|
+
) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
return initializer.getLiteralText();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function readObjectProperty(objectLiteral, propertyName) {
|
|
301
|
+
const property = objectLiteral
|
|
302
|
+
.getProperties()
|
|
303
|
+
.find(
|
|
304
|
+
(prop) =>
|
|
305
|
+
Node.isPropertyAssignment(prop) && prop.getName() === propertyName,
|
|
306
|
+
);
|
|
307
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
const initializer = property.getInitializer();
|
|
311
|
+
return initializer && Node.isObjectLiteralExpression(initializer)
|
|
312
|
+
? initializer
|
|
313
|
+
: undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The codes whose handler calls `globalError()` (function or generator handler).
|
|
317
|
+
function collectGlobalErrorCodes(handleExceptions) {
|
|
318
|
+
const codes = [];
|
|
319
|
+
for (const property of handleExceptions.getProperties()) {
|
|
320
|
+
if (!Node.isPropertyAssignment(property)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const handler = property.getInitializer();
|
|
324
|
+
if (!handler || !handlerCallsGlobalError(handler)) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
codes.push(readPropertyAssignmentName(property));
|
|
328
|
+
}
|
|
329
|
+
return codes;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function handlerCallsGlobalError(handlerNode) {
|
|
333
|
+
return handlerNode
|
|
334
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
335
|
+
.some((call) => {
|
|
336
|
+
const expression = call.getExpression();
|
|
337
|
+
return Node.isIdentifier(expression) && expression.getText() === 'globalError';
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function readPropertyAssignmentName(property) {
|
|
342
|
+
const nameNode = property.getNameNode();
|
|
343
|
+
if (Node.isStringLiteral(nameNode) || Node.isNumericLiteral(nameNode)) {
|
|
344
|
+
return nameNode.getLiteralText();
|
|
345
|
+
}
|
|
346
|
+
return property.getName();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// --- registry analysis + maintenance ---
|
|
350
|
+
|
|
351
|
+
function expectedTypeFor(registration) {
|
|
352
|
+
return `CraftRouteExceptionType<typeof ${registration.collectionVar}, '${registration.path}', '${registration.code}'>`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function groupByPath(registrations) {
|
|
356
|
+
const byPath = new Map();
|
|
357
|
+
for (const registration of registrations) {
|
|
358
|
+
const entry = byPath.get(registration.path) ?? [];
|
|
359
|
+
entry.push(registration);
|
|
360
|
+
byPath.set(registration.path, entry);
|
|
361
|
+
}
|
|
362
|
+
return byPath;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function expectedPathType(entries) {
|
|
366
|
+
const members = entries
|
|
367
|
+
.map(
|
|
368
|
+
(registration) =>
|
|
369
|
+
`${formatPropertyName(registration.code)}: ${expectedTypeFor(registration)}`,
|
|
370
|
+
)
|
|
371
|
+
.join('; ');
|
|
372
|
+
return `{ ${members} }`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function analyzeRegistryState(sourceFile, requiredRegistrations) {
|
|
376
|
+
const registryInterface = getGlobalRegistryInterface(sourceFile);
|
|
377
|
+
const propertiesByName = new Map(
|
|
378
|
+
(registryInterface?.getProperties() ?? []).map((property) => [
|
|
379
|
+
readInterfacePropertyName(property),
|
|
380
|
+
property,
|
|
381
|
+
]),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const missing = [];
|
|
385
|
+
const outOfDate = [];
|
|
386
|
+
const requiredByPath = groupByPath(requiredRegistrations);
|
|
387
|
+
|
|
388
|
+
for (const [routePath, entries] of requiredByPath) {
|
|
389
|
+
const property = propertiesByName.get(routePath);
|
|
390
|
+
if (!property) {
|
|
391
|
+
missing.push(routePath);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const currentType = normalizeText(property.getTypeNode()?.getText() ?? '');
|
|
395
|
+
const expectedType = normalizeText(expectedPathType(entries));
|
|
396
|
+
if (currentType !== expectedType) {
|
|
397
|
+
outOfDate.push(routePath);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Orphaned: a registry entry whose route path no longer delegates any code to
|
|
402
|
+
// `globalError()` (the guard/handler was refactored, or the route removed).
|
|
403
|
+
// Such an entry resolves `CraftRouteExceptionType<…>` to `never`, silently
|
|
404
|
+
// collapsing `CraftGlobalHandledException` for every consumer.
|
|
405
|
+
const orphaned = [];
|
|
406
|
+
for (const routePath of propertiesByName.keys()) {
|
|
407
|
+
if (!requiredByPath.has(routePath)) {
|
|
408
|
+
orphaned.push(routePath);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// With no globalError() route left, the report anchors on the first orphaned
|
|
413
|
+
// registry property (falling back to the interface itself).
|
|
414
|
+
const reportNode =
|
|
415
|
+
requiredRegistrations[0]?.reportNode ??
|
|
416
|
+
(orphaned.length > 0
|
|
417
|
+
? propertiesByName.get(orphaned[0])
|
|
418
|
+
: registryInterface);
|
|
419
|
+
|
|
420
|
+
return { missing, outOfDate, orphaned, reportNode };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function removeOrphanedRegistryEntries(sourceFile, requiredRegistrations) {
|
|
424
|
+
const registryInterface = getGlobalRegistryInterface(sourceFile);
|
|
425
|
+
if (!registryInterface) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const requiredPaths = new Set(
|
|
430
|
+
requiredRegistrations.map((registration) => registration.path),
|
|
431
|
+
);
|
|
432
|
+
for (const property of registryInterface.getProperties()) {
|
|
433
|
+
if (!requiredPaths.has(readInterfacePropertyName(property))) {
|
|
434
|
+
property.remove();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Deliberately keep an emptied `interface CraftGlobalExceptionRegistry {}`
|
|
439
|
+
// rather than deleting the interface / `declare module` block: the block may
|
|
440
|
+
// carry sibling augmentations (e.g. CraftRouterRoutesRegistry) or leading
|
|
441
|
+
// documentation, and an empty registry is valid + stable (no re-report).
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// A file may have several `declare module '@craft-ts/core'` blocks (e.g. one for
|
|
445
|
+
// CraftRouterRoutesRegistry and one for CraftGlobalExceptionRegistry), so search
|
|
446
|
+
// all of them rather than the first.
|
|
447
|
+
function getCraftCoreModuleDeclarations(sourceFile) {
|
|
448
|
+
return sourceFile.getModules().filter((moduleDeclaration) => {
|
|
449
|
+
const nameNode = moduleDeclaration.getNameNode();
|
|
450
|
+
return (
|
|
451
|
+
Node.isStringLiteral(nameNode) &&
|
|
452
|
+
nameNode.getLiteralText() === '@craft-ts/core'
|
|
453
|
+
);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getGlobalRegistryInterface(sourceFile) {
|
|
458
|
+
for (const moduleDeclaration of getCraftCoreModuleDeclarations(sourceFile)) {
|
|
459
|
+
const registryInterface = moduleDeclaration.getInterface(
|
|
460
|
+
'CraftGlobalExceptionRegistry',
|
|
461
|
+
);
|
|
462
|
+
if (registryInterface) {
|
|
463
|
+
return registryInterface;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function readInterfacePropertyName(property) {
|
|
470
|
+
const nameNode = property.getNameNode();
|
|
471
|
+
if (Node.isStringLiteral(nameNode) || Node.isNumericLiteral(nameNode)) {
|
|
472
|
+
return nameNode.getLiteralText();
|
|
473
|
+
}
|
|
474
|
+
return property.getName();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function ensureRegistryEntries(sourceFile, requiredRegistrations) {
|
|
478
|
+
let registryInterface = getGlobalRegistryInterface(sourceFile);
|
|
479
|
+
if (!registryInterface) {
|
|
480
|
+
const moduleDeclaration =
|
|
481
|
+
getCraftCoreModuleDeclarations(sourceFile)[0] ??
|
|
482
|
+
sourceFile.addModule({
|
|
483
|
+
name: "'@craft-ts/core'",
|
|
484
|
+
hasDeclareKeyword: true,
|
|
485
|
+
});
|
|
486
|
+
registryInterface = moduleDeclaration.addInterface({
|
|
487
|
+
name: 'CraftGlobalExceptionRegistry',
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const propertiesByName = new Map(
|
|
492
|
+
registryInterface
|
|
493
|
+
.getProperties()
|
|
494
|
+
.map((property) => [readInterfacePropertyName(property), property]),
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
for (const [routePath, entries] of groupByPath(requiredRegistrations)) {
|
|
498
|
+
const expectedType = expectedPathType(entries);
|
|
499
|
+
const property = propertiesByName.get(routePath);
|
|
500
|
+
if (!property) {
|
|
501
|
+
registryInterface.addProperty({
|
|
502
|
+
name: formatPropertyName(routePath),
|
|
503
|
+
type: expectedType,
|
|
504
|
+
});
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
property.setType(expectedType);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function formatPropertyName(name) {
|
|
512
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : `'${name}'`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function getNodeLoc(sourceCode, node) {
|
|
516
|
+
return {
|
|
517
|
+
start: sourceCode.getLocFromIndex(node.getStart()),
|
|
518
|
+
end: sourceCode.getLocFromIndex(node.getEnd()),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function formatRegistryMessage({ missing, outOfDate, orphaned }) {
|
|
523
|
+
const segments = [];
|
|
524
|
+
if (missing.length > 0) {
|
|
525
|
+
segments.push(`missing ${formatNameList(missing)}`);
|
|
526
|
+
}
|
|
527
|
+
if (outOfDate.length > 0) {
|
|
528
|
+
segments.push(`out of date for ${formatNameList(outOfDate)}`);
|
|
529
|
+
}
|
|
530
|
+
if (orphaned.length > 0) {
|
|
531
|
+
segments.push(`orphaned for ${formatNameList(orphaned)}`);
|
|
532
|
+
}
|
|
533
|
+
return `CraftGlobalExceptionRegistry is ${segments.join(' and ')}. Run ESLint --fix on this file to register globalError() route exceptions.`;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function formatNameList(names) {
|
|
537
|
+
if (names.length === 1) {
|
|
538
|
+
return `'${names[0]}'`;
|
|
539
|
+
}
|
|
540
|
+
if (names.length === 2) {
|
|
541
|
+
return `'${names[0]}' and '${names[1]}'`;
|
|
542
|
+
}
|
|
543
|
+
return `${names
|
|
544
|
+
.slice(0, -1)
|
|
545
|
+
.map((name) => `'${name}'`)
|
|
546
|
+
.join(', ')}, and '${names[names.length - 1]}'`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Compares two type strings for equality up to Prettier's formatting freedom.
|
|
550
|
+
// The rule emits the type on one line, but Prettier re-wraps long entries across
|
|
551
|
+
// lines — which introduces spaces around `<`/`>`/`,` and a trailing `;` before the
|
|
552
|
+
// closing brace. Collapsing whitespace alone would leave those, so the comparison
|
|
553
|
+
// would flip to "out of date" and fight Prettier forever. We additionally strip
|
|
554
|
+
// spaces adjacent to punctuation (keeping token boundaries like `typeof demoRoutes`
|
|
555
|
+
// intact) and drop a member-terminating `;` right before a `}`.
|
|
556
|
+
function normalizeText(text) {
|
|
557
|
+
return text
|
|
558
|
+
.replace(/\s+/g, ' ')
|
|
559
|
+
.replace(/\s*([<>(),;])\s*/g, '$1')
|
|
560
|
+
.replace(/;(?=}|$)/g, '')
|
|
561
|
+
.trim();
|
|
562
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { HEADING_HELPERS } = require('./html-helpers.cjs');
|
|
4
|
+
const { parseHyperscriptCall, hasAccessibleName, staticTextContent } = require('./hyperscript-walk.cjs');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Require heading helpers and heading() to have accessible content.',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
empty: '<{{tag}}> must have text content, aria-label, or aria-labelledby.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
return {
|
|
19
|
+
CallExpression(node) {
|
|
20
|
+
const call = parseHyperscriptCall(node);
|
|
21
|
+
if (!call) return;
|
|
22
|
+
const tag = call.tag;
|
|
23
|
+
if (!tag) return;
|
|
24
|
+
if (!HEADING_HELPERS.has(tag) && tag !== 'heading') return;
|
|
25
|
+
if (hasAccessibleName(call)) return;
|
|
26
|
+
if (staticTextContent(call.children).trim().length > 0) return;
|
|
27
|
+
context.report({ node, messageId: 'empty', data: { tag } });
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Named HTML helpers exported from `@craft-ts/component` hyperscript. */
|
|
4
|
+
const NAMED_HTML_HELPERS = [
|
|
5
|
+
'a',
|
|
6
|
+
'area',
|
|
7
|
+
'article',
|
|
8
|
+
'aside',
|
|
9
|
+
'button',
|
|
10
|
+
'caption',
|
|
11
|
+
'dialog',
|
|
12
|
+
'div',
|
|
13
|
+
'fieldset',
|
|
14
|
+
'figcaption',
|
|
15
|
+
'figure',
|
|
16
|
+
'footer',
|
|
17
|
+
'form',
|
|
18
|
+
'h1',
|
|
19
|
+
'h2',
|
|
20
|
+
'h3',
|
|
21
|
+
'h4',
|
|
22
|
+
'h5',
|
|
23
|
+
'h6',
|
|
24
|
+
'header',
|
|
25
|
+
'iframe',
|
|
26
|
+
'img',
|
|
27
|
+
'input',
|
|
28
|
+
'label',
|
|
29
|
+
'legend',
|
|
30
|
+
'li',
|
|
31
|
+
'main',
|
|
32
|
+
'nav',
|
|
33
|
+
'ol',
|
|
34
|
+
'option',
|
|
35
|
+
'p',
|
|
36
|
+
'pre',
|
|
37
|
+
'section',
|
|
38
|
+
'select',
|
|
39
|
+
'small',
|
|
40
|
+
'span',
|
|
41
|
+
'strong',
|
|
42
|
+
'svg',
|
|
43
|
+
'table',
|
|
44
|
+
'tbody',
|
|
45
|
+
'td',
|
|
46
|
+
'textarea',
|
|
47
|
+
'th',
|
|
48
|
+
'thead',
|
|
49
|
+
'tr',
|
|
50
|
+
'ul',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const NAMED_HTML_HELPER_SET = new Set(NAMED_HTML_HELPERS);
|
|
54
|
+
|
|
55
|
+
const HEADING_HELPERS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
|
|
56
|
+
|
|
57
|
+
const INTERACTIVE_TAGS = new Set([
|
|
58
|
+
'a',
|
|
59
|
+
'button',
|
|
60
|
+
'input',
|
|
61
|
+
'select',
|
|
62
|
+
'textarea',
|
|
63
|
+
'iframe',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const NONINTERACTIVE_TAGS = new Set([
|
|
67
|
+
'div',
|
|
68
|
+
'span',
|
|
69
|
+
'p',
|
|
70
|
+
'li',
|
|
71
|
+
'ul',
|
|
72
|
+
'ol',
|
|
73
|
+
'section',
|
|
74
|
+
'article',
|
|
75
|
+
'header',
|
|
76
|
+
'footer',
|
|
77
|
+
'main',
|
|
78
|
+
'nav',
|
|
79
|
+
'aside',
|
|
80
|
+
'figure',
|
|
81
|
+
'figcaption',
|
|
82
|
+
'caption',
|
|
83
|
+
'td',
|
|
84
|
+
'th',
|
|
85
|
+
'tr',
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const INTERACTIVE_ROLES = new Set([
|
|
89
|
+
'button',
|
|
90
|
+
'link',
|
|
91
|
+
'menuitem',
|
|
92
|
+
'menuitemcheckbox',
|
|
93
|
+
'menuitemradio',
|
|
94
|
+
'option',
|
|
95
|
+
'radio',
|
|
96
|
+
'switch',
|
|
97
|
+
'tab',
|
|
98
|
+
'checkbox',
|
|
99
|
+
'combobox',
|
|
100
|
+
'listbox',
|
|
101
|
+
'searchbox',
|
|
102
|
+
'slider',
|
|
103
|
+
'spinbutton',
|
|
104
|
+
'textbox',
|
|
105
|
+
'menu',
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
NAMED_HTML_HELPERS,
|
|
110
|
+
NAMED_HTML_HELPER_SET,
|
|
111
|
+
HEADING_HELPERS,
|
|
112
|
+
INTERACTIVE_TAGS,
|
|
113
|
+
NONINTERACTIVE_TAGS,
|
|
114
|
+
INTERACTIVE_ROLES,
|
|
115
|
+
};
|