@lushly-dev/fast-element 3.0.1-preview.0 → 3.0.1-preview.2
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/CHANGELOG.md +12 -0
- package/dist/command/command.api.json +7857 -0
- package/dist/context/context.api.json +1084 -0
- package/dist/declarative/declarative.api.json +8130 -0
- package/dist/definition-schema/definition-schema.api.json +2689 -0
- package/dist/di/di.api.json +4945 -0
- package/dist/dts/arrays.d.ts +2 -0
- package/dist/dts/binding/binding.d.ts +59 -0
- package/dist/dts/binding/exports.d.ts +9 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/binding/signal.d.ts +36 -0
- package/dist/dts/binding/two-way.d.ts +44 -0
- package/dist/dts/command/behavior.d.ts +94 -0
- package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
- package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
- package/dist/dts/command/command.d.ts +79 -0
- package/dist/dts/command/component-registry.d.ts +50 -0
- package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
- package/dist/dts/command/index.d.ts +21 -0
- package/dist/dts/command/registry.d.ts +201 -0
- package/dist/dts/command/schema-validation.d.ts +45 -0
- package/dist/dts/command/types.d.ts +332 -0
- package/dist/dts/command/validation.d.ts +116 -0
- package/dist/dts/command/weak-ref.d.ts +11 -0
- package/dist/dts/components/attributes.d.ts +153 -0
- package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
- package/dist/dts/components/element-controller.d.ts +349 -0
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/enable-hydration.d.ts +66 -0
- package/dist/dts/components/fast-definitions.d.ts +211 -0
- package/dist/dts/components/fast-element-registry.d.ts +23 -0
- package/dist/dts/components/fast-element.d.ts +91 -0
- package/dist/dts/components/hydration-tracker.d.ts +110 -0
- package/dist/dts/components/hydration.d.ts +52 -0
- package/dist/dts/components/schema.d.ts +205 -0
- package/dist/dts/context.d.ts +171 -0
- package/dist/dts/debug.d.ts +5 -0
- package/dist/dts/declarative/attribute-map.d.ts +58 -0
- package/dist/dts/declarative/debug.d.ts +4 -0
- package/dist/dts/declarative/index.d.ts +14 -0
- package/dist/dts/declarative/interfaces.d.ts +8 -0
- package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
- package/dist/dts/declarative/observer-map.d.ts +89 -0
- package/dist/dts/declarative/runtime.d.ts +5 -0
- package/dist/dts/declarative/syntax.d.ts +21 -0
- package/dist/dts/declarative/template-bridge.d.ts +33 -0
- package/dist/dts/declarative/template-parser.d.ts +98 -0
- package/dist/dts/declarative/template.d.ts +9 -0
- package/dist/dts/declarative/utilities.d.ts +358 -0
- package/dist/dts/definition-schema/api.d.ts +42 -0
- package/dist/dts/definition-schema/cem.d.ts +78 -0
- package/dist/dts/definition-schema/extension.d.ts +18 -0
- package/dist/dts/definition-schema/extract.d.ts +38 -0
- package/dist/dts/definition-schema/index.d.ts +9 -0
- package/dist/dts/definition-schema/storage.d.ts +35 -0
- package/dist/dts/definition-schema/types.d.ts +176 -0
- package/dist/dts/dev-mode.d.ts +13 -0
- package/dist/dts/di/di.d.ts +894 -0
- package/dist/dts/dom-policy.d.ts +101 -0
- package/dist/dts/dom.d.ts +88 -0
- package/dist/dts/hydration/diagnostics.d.ts +93 -0
- package/dist/dts/hydration/exports.d.ts +18 -0
- package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
- package/dist/dts/hydration/messages.d.ts +62 -0
- package/dist/dts/hydration/runtime.d.ts +7 -0
- package/dist/dts/hydration/target-builder.d.ts +107 -0
- package/dist/dts/index.d.ts +51 -0
- package/dist/dts/index.debug.d.ts +1 -0
- package/dist/dts/index.rollup.d.ts +1 -0
- package/dist/dts/index.rollup.debug.d.ts +1 -0
- package/dist/dts/interfaces.d.ts +106 -0
- package/dist/dts/metadata.d.ts +26 -0
- package/dist/dts/observation/arrays.d.ts +248 -0
- package/dist/dts/observation/notifier.d.ts +127 -0
- package/dist/dts/observation/observable.d.ts +276 -0
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/observation/volatile.d.ts +9 -0
- package/dist/dts/platform.d.ts +87 -0
- package/dist/dts/registry.d.ts +2 -0
- package/dist/dts/render.d.ts +7 -0
- package/dist/dts/schema.d.ts +1 -0
- package/dist/dts/scoped/detection.d.ts +55 -0
- package/dist/dts/scoped/index.d.ts +68 -0
- package/dist/dts/scoped/mixin.d.ts +118 -0
- package/dist/dts/scoped/registry.d.ts +76 -0
- package/dist/dts/scoped/types.d.ts +48 -0
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/css-directive.d.ts +50 -0
- package/dist/dts/styles/css.d.ts +34 -0
- package/dist/dts/styles/element-styles.d.ts +61 -0
- package/dist/dts/styles/host.d.ts +65 -0
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/children.d.ts +62 -0
- package/dist/dts/templating/compiler.d.ts +54 -0
- package/dist/dts/templating/html-binding-directive.d.ts +155 -0
- package/dist/dts/templating/html-directive.d.ts +218 -0
- package/dist/dts/templating/html.d.ts +1 -0
- package/dist/dts/templating/hydration-view.d.ts +130 -0
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +99 -0
- package/dist/dts/templating/ref.d.ts +23 -0
- package/dist/dts/templating/render.d.ts +318 -0
- package/dist/dts/templating/repeat.d.ts +131 -0
- package/dist/dts/templating/slotted.d.ts +38 -0
- package/dist/dts/templating/template.d.ts +221 -0
- package/dist/dts/templating/view.d.ts +268 -0
- package/dist/dts/templating/when.d.ts +12 -0
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +16 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/models.d.ts +20 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/dts/trust/index.d.ts +8 -0
- package/dist/dts/trust/types.d.ts +48 -0
- package/dist/dts/trust/utils.d.ts +26 -0
- package/dist/dts/tsdoc-metadata.json +11 -0
- package/dist/dts/utilities.d.ts +76 -0
- package/dist/esm/arrays.js +1 -0
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/exports.js +6 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/binding/signal.js +99 -0
- package/dist/esm/binding/two-way.js +109 -0
- package/dist/esm/command/behavior.js +155 -0
- package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
- package/dist/esm/command/behaviors/undo-behavior.js +313 -0
- package/dist/esm/command/command.js +104 -0
- package/dist/esm/command/component-registry.js +107 -0
- package/dist/esm/command/components/confirm-dialog.js +370 -0
- package/dist/esm/command/index.js +22 -0
- package/dist/esm/command/registry.js +755 -0
- package/dist/esm/command/schema-validation.js +194 -0
- package/dist/esm/command/types.js +86 -0
- package/dist/esm/command/validation.js +508 -0
- package/dist/esm/command/weak-ref.js +5 -0
- package/dist/esm/components/attributes.js +219 -0
- package/dist/esm/components/definition-schema-transforms.js +23 -0
- package/dist/esm/components/element-controller.js +840 -0
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/enable-hydration.js +123 -0
- package/dist/esm/components/fast-definitions.js +294 -0
- package/dist/esm/components/fast-element-registry.js +67 -0
- package/dist/esm/components/fast-element.js +82 -0
- package/dist/esm/components/hydration-tracker.js +211 -0
- package/dist/esm/components/hydration.js +187 -0
- package/dist/esm/components/schema.js +253 -0
- package/dist/esm/context.js +185 -0
- package/dist/esm/debug.js +71 -0
- package/dist/esm/declarative/attribute-map.js +122 -0
- package/dist/esm/declarative/debug.js +4 -0
- package/dist/esm/declarative/index.js +10 -0
- package/dist/esm/declarative/interfaces.js +9 -0
- package/dist/esm/declarative/observer-map-utilities.js +565 -0
- package/dist/esm/declarative/observer-map.js +216 -0
- package/dist/esm/declarative/runtime.js +14 -0
- package/dist/esm/declarative/syntax.js +36 -0
- package/dist/esm/declarative/template-bridge.js +160 -0
- package/dist/esm/declarative/template-parser.js +306 -0
- package/dist/esm/declarative/template.js +116 -0
- package/dist/esm/declarative/utilities.js +1069 -0
- package/dist/esm/definition-schema/api.js +129 -0
- package/dist/esm/definition-schema/cem.js +89 -0
- package/dist/esm/definition-schema/extension.js +53 -0
- package/dist/esm/definition-schema/extract.js +167 -0
- package/dist/esm/definition-schema/index.js +8 -0
- package/dist/esm/definition-schema/storage.js +58 -0
- package/dist/esm/definition-schema/types.js +8 -0
- package/dist/esm/dev-mode.js +22 -0
- package/dist/esm/di/di.js +1441 -0
- package/dist/esm/dom-policy.js +374 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/diagnostics.js +50 -0
- package/dist/esm/hydration/exports.js +6 -0
- package/dist/esm/hydration/hydration-debugger.js +112 -0
- package/dist/esm/hydration/messages.js +84 -0
- package/dist/esm/hydration/runtime.js +33 -0
- package/dist/esm/hydration/target-builder.js +264 -0
- package/dist/esm/index.debug.js +3 -0
- package/dist/esm/index.js +49 -0
- package/dist/esm/index.rollup.debug.js +6 -0
- package/dist/esm/index.rollup.js +4 -0
- package/dist/esm/interfaces.js +55 -0
- package/dist/esm/metadata.js +57 -0
- package/dist/esm/observation/arrays.js +661 -0
- package/dist/esm/observation/notifier.js +162 -0
- package/dist/esm/observation/observable.js +324 -0
- package/dist/esm/observation/update-queue.js +56 -0
- package/dist/esm/observation/volatile.js +16 -0
- package/dist/esm/platform.js +98 -0
- package/dist/esm/registry.js +2 -0
- package/dist/esm/render.js +1 -0
- package/dist/esm/schema.js +1 -0
- package/dist/esm/scoped/detection.js +102 -0
- package/dist/esm/scoped/index.js +69 -0
- package/dist/esm/scoped/mixin.js +127 -0
- package/dist/esm/scoped/registry.js +125 -0
- package/dist/esm/scoped/types.js +4 -0
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/css-directive.js +36 -0
- package/dist/esm/styles/css.js +56 -0
- package/dist/esm/styles/element-styles.js +144 -0
- package/dist/esm/styles/host.js +1 -0
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +67 -0
- package/dist/esm/templating/compiler.js +359 -0
- package/dist/esm/templating/html-binding-directive.js +288 -0
- package/dist/esm/templating/html-directive.js +113 -0
- package/dist/esm/templating/html.js +1 -0
- package/dist/esm/templating/hydration-view.js +228 -0
- package/dist/esm/templating/markup.js +83 -0
- package/dist/esm/templating/node-observation.js +84 -0
- package/dist/esm/templating/ref.js +21 -0
- package/dist/esm/templating/render.js +468 -0
- package/dist/esm/templating/repeat.js +434 -0
- package/dist/esm/templating/slotted.js +49 -0
- package/dist/esm/templating/template.js +206 -0
- package/dist/esm/templating/view.js +298 -0
- package/dist/esm/templating/when.js +22 -0
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +107 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/models.js +58 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/trust/index.js +7 -0
- package/dist/esm/trust/types.js +6 -0
- package/dist/esm/trust/utils.js +126 -0
- package/dist/esm/utilities.js +140 -0
- package/dist/fast-element.api.json +26798 -0
- package/dist/fast-element.d.ts +3985 -0
- package/dist/fast-element.debug.js +7604 -0
- package/dist/fast-element.debug.min.js +3 -0
- package/dist/fast-element.js +7603 -0
- package/dist/fast-element.min.js +3 -0
- package/dist/fast-element.untrimmed.d.ts +4103 -0
- package/dist/observation/arrays/arrays.api.json +2621 -0
- package/dist/scoped/scoped.api.json +891 -0
- package/dist/trust/trust.api.json +629 -0
- package/package.json +21 -23
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation commands for FAST elements.
|
|
3
|
+
* Provides linting and accessibility auditing capabilities.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Task 10: Implements fast.lint.check for code validation against FAST best practices.
|
|
7
|
+
* Task 11: Implements fast.a11y.audit for accessibility auditing.
|
|
8
|
+
*/
|
|
9
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
10
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
11
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
12
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
13
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
14
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
15
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
import { CommandRegistry } from "./registry.js";
|
|
19
|
+
import { failure } from "./types.js";
|
|
20
|
+
import { createWeakReference } from "./weak-ref.js";
|
|
21
|
+
/**
|
|
22
|
+
* Default lint rules applied when no rules are specified.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_LINT_RULES = [
|
|
26
|
+
"fast/no-inline-styles",
|
|
27
|
+
"fast/prefer-design-tokens",
|
|
28
|
+
"fast/proper-element-naming",
|
|
29
|
+
"fast/require-slot-names",
|
|
30
|
+
"fast/attribute-reflection",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Built-in linter for FAST best practices.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
const linter = {
|
|
37
|
+
/**
|
|
38
|
+
* Check code against FAST best practices.
|
|
39
|
+
* @param code - The code to lint
|
|
40
|
+
* @param rules - Optional array of rule IDs to apply
|
|
41
|
+
* @returns Array of lint issues found
|
|
42
|
+
*/
|
|
43
|
+
check(code, rules) {
|
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
const activeRules = rules !== null && rules !== void 0 ? rules : DEFAULT_LINT_RULES;
|
|
46
|
+
const issues = [];
|
|
47
|
+
const lines = code.split("\n");
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
const line = lines[i];
|
|
50
|
+
const lineNum = i + 1;
|
|
51
|
+
// Rule: fast/no-inline-styles
|
|
52
|
+
if (activeRules.includes("fast/no-inline-styles")) {
|
|
53
|
+
const styleMatch = line.match(/style\s*=\s*["']/);
|
|
54
|
+
if (styleMatch) {
|
|
55
|
+
issues.push({
|
|
56
|
+
rule: "fast/no-inline-styles",
|
|
57
|
+
severity: "warning",
|
|
58
|
+
message: "Avoid inline styles. Use CSS classes or design tokens instead.",
|
|
59
|
+
line: lineNum,
|
|
60
|
+
column: styleMatch.index + 1,
|
|
61
|
+
fix: "Extract inline styles to a CSS class or use design tokens",
|
|
62
|
+
fixConfidence: 0.7,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Rule: fast/prefer-design-tokens
|
|
67
|
+
if (activeRules.includes("fast/prefer-design-tokens")) {
|
|
68
|
+
const hexMatch = line.match(/#[0-9a-fA-F]{3,8}\b/);
|
|
69
|
+
if (hexMatch) {
|
|
70
|
+
issues.push({
|
|
71
|
+
rule: "fast/prefer-design-tokens",
|
|
72
|
+
severity: "info",
|
|
73
|
+
message: `Prefer design tokens over hardcoded color values: ${hexMatch[0]}`,
|
|
74
|
+
line: lineNum,
|
|
75
|
+
column: hexMatch.index + 1,
|
|
76
|
+
fix: "Replace with a design token (e.g., --accent-foreground-rest)",
|
|
77
|
+
fixConfidence: 0.6,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Rule: fast/proper-element-naming
|
|
82
|
+
if (activeRules.includes("fast/proper-element-naming")) {
|
|
83
|
+
const tagMatch = line.match(/<([a-z]+)(?:\s|>)/);
|
|
84
|
+
if (tagMatch &&
|
|
85
|
+
!tagMatch[1].includes("-") &&
|
|
86
|
+
![
|
|
87
|
+
"div",
|
|
88
|
+
"span",
|
|
89
|
+
"p",
|
|
90
|
+
"a",
|
|
91
|
+
"ul",
|
|
92
|
+
"li",
|
|
93
|
+
"h1",
|
|
94
|
+
"h2",
|
|
95
|
+
"h3",
|
|
96
|
+
"h4",
|
|
97
|
+
"h5",
|
|
98
|
+
"h6",
|
|
99
|
+
"template",
|
|
100
|
+
"slot",
|
|
101
|
+
"style",
|
|
102
|
+
"script",
|
|
103
|
+
].includes(tagMatch[1])) {
|
|
104
|
+
// This is a native element, which is fine - skip
|
|
105
|
+
}
|
|
106
|
+
// Check for custom elements without proper prefix
|
|
107
|
+
const customElementMatch = line.match(/customElements\.define\s*\(\s*["']([^"']+)["']/);
|
|
108
|
+
if (customElementMatch &&
|
|
109
|
+
!customElementMatch[1].startsWith("fast-") &&
|
|
110
|
+
!customElementMatch[1].startsWith("fluent-")) {
|
|
111
|
+
issues.push({
|
|
112
|
+
rule: "fast/proper-element-naming",
|
|
113
|
+
severity: "warning",
|
|
114
|
+
message: `Custom element "${customElementMatch[1]}" should use a namespace prefix (e.g., fast-, fluent-, app-)`,
|
|
115
|
+
line: lineNum,
|
|
116
|
+
column: customElementMatch.index + 1,
|
|
117
|
+
fix: `Rename to include a namespace prefix: "app-${customElementMatch[1]}" or similar`,
|
|
118
|
+
fixConfidence: 0.8,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Rule: fast/require-slot-names
|
|
123
|
+
if (activeRules.includes("fast/require-slot-names")) {
|
|
124
|
+
const slotMatch = line.match(/<slot\s*>/);
|
|
125
|
+
if (slotMatch) {
|
|
126
|
+
issues.push({
|
|
127
|
+
rule: "fast/require-slot-names",
|
|
128
|
+
severity: "info",
|
|
129
|
+
message: "Consider using named slots for better API clarity",
|
|
130
|
+
line: lineNum,
|
|
131
|
+
column: slotMatch.index + 1,
|
|
132
|
+
fix: 'Add a name attribute to the slot: <slot name="content">',
|
|
133
|
+
fixConfidence: 0.5,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Rule: fast/attribute-reflection
|
|
138
|
+
if (activeRules.includes("fast/attribute-reflection")) {
|
|
139
|
+
const attrMatch = line.match(/@attr\s*(?:\([^)]*\))?\s*\n?\s*(?:public\s+)?(\w+)/);
|
|
140
|
+
if (attrMatch) {
|
|
141
|
+
// Check if there's a corresponding attribute definition without reflection option
|
|
142
|
+
const noReflectMatch = line.match(/@attr\s*\(\s*\)\s*/) || line.match(/@attr\s*(?!\()/);
|
|
143
|
+
if (noReflectMatch) {
|
|
144
|
+
issues.push({
|
|
145
|
+
rule: "fast/attribute-reflection",
|
|
146
|
+
severity: "info",
|
|
147
|
+
message: "Consider explicit reflection mode for @attr decorator",
|
|
148
|
+
line: lineNum,
|
|
149
|
+
column: noReflectMatch.index + 1,
|
|
150
|
+
fix: "Specify reflection: @attr({ mode: 'reflect' }) or @attr({ mode: 'boolean' })",
|
|
151
|
+
fixConfidence: 0.6,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return issues;
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* WCAG rules by conformance level.
|
|
163
|
+
* Rules are cumulative: AA includes A, AAA includes AA.
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
const WCAG_RULES = {
|
|
167
|
+
A: [
|
|
168
|
+
"aria-required-attr",
|
|
169
|
+
"aria-valid-attr",
|
|
170
|
+
"button-name",
|
|
171
|
+
"image-alt",
|
|
172
|
+
"input-label",
|
|
173
|
+
"link-name",
|
|
174
|
+
"list-structure",
|
|
175
|
+
"tabindex",
|
|
176
|
+
],
|
|
177
|
+
AA: [
|
|
178
|
+
"color-contrast",
|
|
179
|
+
"focus-visible",
|
|
180
|
+
"heading-order",
|
|
181
|
+
"landmark-unique",
|
|
182
|
+
"meta-viewport",
|
|
183
|
+
"target-size",
|
|
184
|
+
],
|
|
185
|
+
AAA: [
|
|
186
|
+
"color-contrast-enhanced",
|
|
187
|
+
"focus-visible-enhanced",
|
|
188
|
+
"link-purpose",
|
|
189
|
+
"reading-level",
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Get all applicable rules for a WCAG level (cumulative).
|
|
194
|
+
* @internal
|
|
195
|
+
*/
|
|
196
|
+
function getRulesForLevel(level) {
|
|
197
|
+
const rules = [...WCAG_RULES.A];
|
|
198
|
+
if (level === "AA" || level === "AAA") {
|
|
199
|
+
rules.push(...WCAG_RULES.AA);
|
|
200
|
+
}
|
|
201
|
+
if (level === "AAA") {
|
|
202
|
+
rules.push(...WCAG_RULES.AAA);
|
|
203
|
+
}
|
|
204
|
+
return rules;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Built-in accessibility auditor.
|
|
208
|
+
* Performs runtime DOM analysis for WCAG violations.
|
|
209
|
+
* @internal
|
|
210
|
+
*/
|
|
211
|
+
const a11yAuditor = {
|
|
212
|
+
/**
|
|
213
|
+
* Audit an element for accessibility violations.
|
|
214
|
+
* @param target - CSS selector for the target element
|
|
215
|
+
* @param level - WCAG conformance level to check against
|
|
216
|
+
* @returns Array of accessibility violations found
|
|
217
|
+
*/
|
|
218
|
+
audit(target, level) {
|
|
219
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
220
|
+
// Guard for non-browser environments
|
|
221
|
+
if (typeof document === "undefined") {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const violations = [];
|
|
225
|
+
const element = document.querySelector(target);
|
|
226
|
+
if (!element) {
|
|
227
|
+
return violations;
|
|
228
|
+
}
|
|
229
|
+
const applicableRules = getRulesForLevel(level);
|
|
230
|
+
// Check: Images without alt text (WCAG 1.1.1 - Level A)
|
|
231
|
+
if (applicableRules.includes("image-alt")) {
|
|
232
|
+
const images = element.querySelectorAll("img:not([alt])");
|
|
233
|
+
images.forEach((img, index) => {
|
|
234
|
+
violations.push({
|
|
235
|
+
rule: "image-alt",
|
|
236
|
+
impact: "critical",
|
|
237
|
+
selector: `${target} img:nth-of-type(${index + 1})`,
|
|
238
|
+
wcagLevel: "A",
|
|
239
|
+
message: "Images must have alternate text",
|
|
240
|
+
wcagCriterion: "1.1.1",
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Check: Buttons without accessible name (WCAG 4.1.2 - Level A)
|
|
245
|
+
if (applicableRules.includes("button-name")) {
|
|
246
|
+
const buttons = element.querySelectorAll("button");
|
|
247
|
+
buttons.forEach((btn, index) => {
|
|
248
|
+
var _a;
|
|
249
|
+
const hasText = (_a = btn.textContent) === null || _a === void 0 ? void 0 : _a.trim();
|
|
250
|
+
const hasAriaLabel = btn.getAttribute("aria-label");
|
|
251
|
+
const hasAriaLabelledBy = btn.getAttribute("aria-labelledby");
|
|
252
|
+
if (!hasText && !hasAriaLabel && !hasAriaLabelledBy) {
|
|
253
|
+
violations.push({
|
|
254
|
+
rule: "button-name",
|
|
255
|
+
impact: "critical",
|
|
256
|
+
selector: `${target} button:nth-of-type(${index + 1})`,
|
|
257
|
+
wcagLevel: "A",
|
|
258
|
+
message: "Buttons must have discernible text",
|
|
259
|
+
wcagCriterion: "4.1.2",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Check: Links without accessible name (WCAG 2.4.4 - Level A)
|
|
265
|
+
if (applicableRules.includes("link-name")) {
|
|
266
|
+
const links = element.querySelectorAll("a[href]");
|
|
267
|
+
links.forEach((link, index) => {
|
|
268
|
+
var _a;
|
|
269
|
+
const hasText = (_a = link.textContent) === null || _a === void 0 ? void 0 : _a.trim();
|
|
270
|
+
const hasAriaLabel = link.getAttribute("aria-label");
|
|
271
|
+
const hasAriaLabelledBy = link.getAttribute("aria-labelledby");
|
|
272
|
+
if (!hasText && !hasAriaLabel && !hasAriaLabelledBy) {
|
|
273
|
+
violations.push({
|
|
274
|
+
rule: "link-name",
|
|
275
|
+
impact: "serious",
|
|
276
|
+
selector: `${target} a:nth-of-type(${index + 1})`,
|
|
277
|
+
wcagLevel: "A",
|
|
278
|
+
message: "Links must have discernible text",
|
|
279
|
+
wcagCriterion: "2.4.4",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// Check: Form inputs without labels (WCAG 1.3.1 - Level A)
|
|
285
|
+
if (applicableRules.includes("input-label")) {
|
|
286
|
+
const inputs = element.querySelectorAll("input, select, textarea");
|
|
287
|
+
inputs.forEach((input, index) => {
|
|
288
|
+
const id = input.getAttribute("id");
|
|
289
|
+
const hasLabel = id && element.querySelector(`label[for="${id}"]`);
|
|
290
|
+
const hasAriaLabel = input.getAttribute("aria-label");
|
|
291
|
+
const hasAriaLabelledBy = input.getAttribute("aria-labelledby");
|
|
292
|
+
const isHidden = input.getAttribute("type") === "hidden";
|
|
293
|
+
if (!isHidden && !hasLabel && !hasAriaLabel && !hasAriaLabelledBy) {
|
|
294
|
+
violations.push({
|
|
295
|
+
rule: "input-label",
|
|
296
|
+
impact: "critical",
|
|
297
|
+
selector: `${target} input:nth-of-type(${index + 1})`,
|
|
298
|
+
wcagLevel: "A",
|
|
299
|
+
message: "Form elements must have labels",
|
|
300
|
+
wcagCriterion: "1.3.1",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// Check: Invalid tabindex values (WCAG 2.4.3 - Level A)
|
|
306
|
+
if (applicableRules.includes("tabindex")) {
|
|
307
|
+
const tabindexElements = element.querySelectorAll("[tabindex]");
|
|
308
|
+
tabindexElements.forEach((el, index) => {
|
|
309
|
+
const tabindex = parseInt(el.getAttribute("tabindex") || "0", 10);
|
|
310
|
+
if (tabindex > 0) {
|
|
311
|
+
violations.push({
|
|
312
|
+
rule: "tabindex",
|
|
313
|
+
impact: "moderate",
|
|
314
|
+
selector: `${target} [tabindex]:nth-of-type(${index + 1})`,
|
|
315
|
+
wcagLevel: "A",
|
|
316
|
+
message: "Avoid positive tabindex values; use 0 or -1",
|
|
317
|
+
wcagCriterion: "2.4.3",
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// Check: Invalid ARIA attributes (WCAG 4.1.2 - Level A)
|
|
323
|
+
if (applicableRules.includes("aria-valid-attr")) {
|
|
324
|
+
const ariaElements = element.querySelectorAll("[aria-hidden='true'][tabindex]:not([tabindex='-1'])");
|
|
325
|
+
ariaElements.forEach((el, index) => {
|
|
326
|
+
violations.push({
|
|
327
|
+
rule: "aria-valid-attr",
|
|
328
|
+
impact: "serious",
|
|
329
|
+
selector: `${target} [aria-hidden='true'][tabindex]:nth-of-type(${index + 1})`,
|
|
330
|
+
wcagLevel: "A",
|
|
331
|
+
message: "Elements with aria-hidden='true' should not be focusable",
|
|
332
|
+
wcagCriterion: "4.1.2",
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Check: Required ARIA attributes (WCAG 4.1.2 - Level A)
|
|
337
|
+
if (applicableRules.includes("aria-required-attr")) {
|
|
338
|
+
// Check role="checkbox" requires aria-checked
|
|
339
|
+
const checkboxes = element.querySelectorAll("[role='checkbox']:not([aria-checked])");
|
|
340
|
+
checkboxes.forEach((el, index) => {
|
|
341
|
+
violations.push({
|
|
342
|
+
rule: "aria-required-attr",
|
|
343
|
+
impact: "critical",
|
|
344
|
+
selector: `${target} [role='checkbox']:nth-of-type(${index + 1})`,
|
|
345
|
+
wcagLevel: "A",
|
|
346
|
+
message: "Elements with role='checkbox' must have aria-checked",
|
|
347
|
+
wcagCriterion: "4.1.2",
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Check: Heading order (WCAG 1.3.1 - Level AA)
|
|
352
|
+
if (applicableRules.includes("heading-order")) {
|
|
353
|
+
const headings = element.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
|
354
|
+
let lastLevel = 0;
|
|
355
|
+
headings.forEach((heading, index) => {
|
|
356
|
+
const level = parseInt(heading.tagName[1], 10);
|
|
357
|
+
if (lastLevel > 0 && level > lastLevel + 1) {
|
|
358
|
+
violations.push({
|
|
359
|
+
rule: "heading-order",
|
|
360
|
+
impact: "moderate",
|
|
361
|
+
selector: `${target} ${heading.tagName.toLowerCase()}:nth-of-type(${index + 1})`,
|
|
362
|
+
wcagLevel: "AA",
|
|
363
|
+
message: `Heading levels should only increase by one. Found h${level} after h${lastLevel}`,
|
|
364
|
+
wcagCriterion: "1.3.1",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
lastLevel = level;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return violations;
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Register fast.lint.* and fast.a11y.* commands with the CommandRegistry.
|
|
376
|
+
* Called during fast-element initialization.
|
|
377
|
+
*
|
|
378
|
+
* @remarks
|
|
379
|
+
* These commands provide code validation and accessibility auditing:
|
|
380
|
+
* - fast.lint.check: Validates code against FAST best practices
|
|
381
|
+
* - fast.a11y.audit: Runs WCAG accessibility audit on DOM elements
|
|
382
|
+
*
|
|
383
|
+
* @public
|
|
384
|
+
*/
|
|
385
|
+
export function registerValidationCommands() {
|
|
386
|
+
// fast.lint.check
|
|
387
|
+
CommandRegistry.register("fast.lint.check", {
|
|
388
|
+
id: "fast.lint.check",
|
|
389
|
+
label: "Validate code against FAST best practices",
|
|
390
|
+
description: "Analyzes code for FAST best practice violations including inline styles, " +
|
|
391
|
+
"design token usage, element naming, slot names, and attribute reflection.",
|
|
392
|
+
category: "FAST",
|
|
393
|
+
execute: (args) => __awaiter(this, void 0, void 0, function* () {
|
|
394
|
+
const { code, rules } = args !== null && args !== void 0 ? args : {};
|
|
395
|
+
if (!code) {
|
|
396
|
+
return failure({
|
|
397
|
+
code: "VALIDATION_ERROR",
|
|
398
|
+
message: "Code is required for linting",
|
|
399
|
+
suggestion: "Provide a code string to analyze",
|
|
400
|
+
retryable: false,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const issues = yield linter.check(code, rules);
|
|
405
|
+
// Calculate counts by severity
|
|
406
|
+
const counts = {
|
|
407
|
+
error: issues.filter(i => i.severity === "error").length,
|
|
408
|
+
warning: issues.filter(i => i.severity === "warning").length,
|
|
409
|
+
info: issues.filter(i => i.severity === "info").length,
|
|
410
|
+
};
|
|
411
|
+
const hasErrors = counts.error > 0;
|
|
412
|
+
// Build plan array with fix suggestions
|
|
413
|
+
const plan = issues
|
|
414
|
+
.filter(i => i.fix)
|
|
415
|
+
.map((issue, idx) => { var _a; return ({
|
|
416
|
+
step: idx + 1,
|
|
417
|
+
action: issue.fix,
|
|
418
|
+
confidence: (_a = issue.fixConfidence) !== null && _a !== void 0 ? _a : 0.8,
|
|
419
|
+
}); });
|
|
420
|
+
if (hasErrors) {
|
|
421
|
+
return failure({
|
|
422
|
+
code: "LINT_ERRORS",
|
|
423
|
+
message: `Found ${counts.error} error(s)`,
|
|
424
|
+
suggestion: "Fix the reported errors and re-run",
|
|
425
|
+
retryable: false,
|
|
426
|
+
details: { issues, counts, plan },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
success: true,
|
|
431
|
+
data: { issues, counts },
|
|
432
|
+
confidence: 0.95,
|
|
433
|
+
plan,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
return failure({
|
|
438
|
+
code: "VALIDATION_ERROR",
|
|
439
|
+
message: error instanceof Error ? error.message : "Linting failed",
|
|
440
|
+
suggestion: "Check that the code is valid and try again",
|
|
441
|
+
retryable: true,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}),
|
|
445
|
+
canExecute: true,
|
|
446
|
+
when: true,
|
|
447
|
+
expose: { palette: true, agent: true, mcp: true, cli: true },
|
|
448
|
+
source: typeof document !== "undefined"
|
|
449
|
+
? createWeakReference(document.body)
|
|
450
|
+
: createWeakReference({}),
|
|
451
|
+
});
|
|
452
|
+
// fast.a11y.audit
|
|
453
|
+
CommandRegistry.register("fast.a11y.audit", {
|
|
454
|
+
id: "fast.a11y.audit",
|
|
455
|
+
label: "Run accessibility audit",
|
|
456
|
+
description: "Audits DOM elements for WCAG accessibility violations. " +
|
|
457
|
+
"Checks for missing alt text, unlabeled form controls, heading order, " +
|
|
458
|
+
"ARIA issues, and more based on the specified conformance level.",
|
|
459
|
+
category: "FAST",
|
|
460
|
+
execute: (args) => __awaiter(this, void 0, void 0, function* () {
|
|
461
|
+
var _a, _b;
|
|
462
|
+
const target = (_a = args === null || args === void 0 ? void 0 : args.element) !== null && _a !== void 0 ? _a : "body";
|
|
463
|
+
const level = (_b = args === null || args === void 0 ? void 0 : args.level) !== null && _b !== void 0 ? _b : "AA";
|
|
464
|
+
// Validate WCAG level
|
|
465
|
+
if (!["A", "AA", "AAA"].includes(level)) {
|
|
466
|
+
return failure({
|
|
467
|
+
code: "INVALID_LEVEL",
|
|
468
|
+
message: `Invalid WCAG level: ${level}. Must be A, AA, or AAA`,
|
|
469
|
+
suggestion: "Use level 'A', 'AA', or 'AAA'",
|
|
470
|
+
retryable: false,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const violations = yield a11yAuditor.audit(target, level);
|
|
475
|
+
if (violations.length > 0) {
|
|
476
|
+
return failure({
|
|
477
|
+
code: "A11Y_VIOLATIONS",
|
|
478
|
+
message: `Found ${violations.length} accessibility violation(s)`,
|
|
479
|
+
suggestion: "Fix the reported violations and re-audit",
|
|
480
|
+
retryable: false,
|
|
481
|
+
details: { violations, level, target },
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
success: true,
|
|
486
|
+
data: { violations, level, target },
|
|
487
|
+
confidence: 0.9,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
return failure({
|
|
492
|
+
code: "AUDIT_ERROR",
|
|
493
|
+
message: error instanceof Error
|
|
494
|
+
? error.message
|
|
495
|
+
: "Accessibility audit failed",
|
|
496
|
+
suggestion: "Check that the target selector is valid",
|
|
497
|
+
retryable: true,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}),
|
|
501
|
+
canExecute: true,
|
|
502
|
+
when: true,
|
|
503
|
+
expose: { palette: true, agent: true, mcp: true, cli: true },
|
|
504
|
+
source: typeof document !== "undefined"
|
|
505
|
+
? createWeakReference(document.body)
|
|
506
|
+
: createWeakReference({}),
|
|
507
|
+
});
|
|
508
|
+
}
|