@constela/compiler 0.15.21 → 0.16.0

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Program, ConstelaError, Expression, IslandStrategy, IslandStrategyOptions, StylePreset, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
1
+ import { Program, ConstelaError, Expression, TransitionDirective, IslandStrategy, IslandStrategyOptions, StylePreset, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
2
2
  export { createUndefinedVarError } from '@constela/core';
3
3
 
4
4
  /**
@@ -27,6 +27,7 @@ interface AnalyzePassSuccess {
27
27
  ok: true;
28
28
  ast: Program;
29
29
  context: AnalysisContext;
30
+ warnings: ConstelaError[];
30
31
  }
31
32
  interface AnalyzePassFailure {
32
33
  ok: false;
@@ -417,6 +418,7 @@ interface CompiledTextNode {
417
418
  interface CompiledIfNode {
418
419
  kind: 'if';
419
420
  condition: CompiledExpression;
421
+ transition?: TransitionDirective;
420
422
  then: CompiledNode;
421
423
  else?: CompiledNode;
422
424
  }
@@ -426,6 +428,7 @@ interface CompiledEachNode {
426
428
  as: string;
427
429
  index?: string;
428
430
  key?: CompiledExpression;
431
+ transition?: TransitionDirective;
429
432
  body: CompiledNode;
430
433
  }
431
434
  interface CompiledMarkdownNode {
@@ -581,6 +584,7 @@ declare function transformPass(ast: Program, _context: AnalysisContext, importDa
581
584
  interface CompileSuccess {
582
585
  ok: true;
583
586
  program: CompiledProgram;
587
+ warnings?: ConstelaError[];
584
588
  }
585
589
  interface CompileFailure {
586
590
  ok: false;
package/dist/index.js CHANGED
@@ -14,6 +14,151 @@ function validatePass(input) {
14
14
  };
15
15
  }
16
16
 
17
+ // src/passes/a11y-validate.ts
18
+ import {
19
+ createA11yImgNoAltError,
20
+ createA11yButtonNoLabelError,
21
+ createA11yAnchorNoLabelError,
22
+ createA11yInputNoLabelError,
23
+ createA11yHeadingSkipError,
24
+ createA11yPositiveTabindexError,
25
+ createA11yDuplicateIdError
26
+ } from "@constela/core";
27
+ var HEADING_RE = /^h([1-6])$/;
28
+ function getHeadingLevel(tag) {
29
+ const match = HEADING_RE.exec(tag);
30
+ return match ? Number(match[1]) : 0;
31
+ }
32
+ var FORM_INPUT_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select"]);
33
+ function hasTextChild(children) {
34
+ if (!children) return false;
35
+ return children.some((child) => child.kind === "text");
36
+ }
37
+ function validateElementNode(node, path, ctx) {
38
+ const { tag, props, children } = node;
39
+ if (tag === "img") {
40
+ if (!props || !("alt" in props)) {
41
+ ctx.warnings.push(createA11yImgNoAltError(path));
42
+ }
43
+ }
44
+ if (tag === "button") {
45
+ const hasAriaLabel = props != null && "aria-label" in props;
46
+ if (!hasAriaLabel && !hasTextChild(children)) {
47
+ ctx.warnings.push(createA11yButtonNoLabelError(path));
48
+ }
49
+ }
50
+ if (tag === "a") {
51
+ const hasAriaLabel = props != null && "aria-label" in props;
52
+ if (!hasAriaLabel && !hasTextChild(children)) {
53
+ ctx.warnings.push(createA11yAnchorNoLabelError(path));
54
+ }
55
+ }
56
+ if (FORM_INPUT_TAGS.has(tag)) {
57
+ const hasAriaLabel = props != null && "aria-label" in props;
58
+ const hasAriaLabelledby = props != null && "aria-labelledby" in props;
59
+ if (!hasAriaLabel && !hasAriaLabelledby) {
60
+ ctx.warnings.push(createA11yInputNoLabelError(tag, path));
61
+ }
62
+ }
63
+ const headingLevel = getHeadingLevel(tag);
64
+ if (headingLevel > 0) {
65
+ if (ctx.maxHeadingLevel > 0 && headingLevel > ctx.maxHeadingLevel + 1) {
66
+ ctx.warnings.push(
67
+ createA11yHeadingSkipError(headingLevel, ctx.maxHeadingLevel + 1, path)
68
+ );
69
+ }
70
+ ctx.maxHeadingLevel = Math.max(ctx.maxHeadingLevel, headingLevel);
71
+ }
72
+ if (props && "tabindex" in props) {
73
+ const tabindexExpr = props["tabindex"];
74
+ if (tabindexExpr && tabindexExpr.expr === "lit" && typeof tabindexExpr.value === "number" && tabindexExpr.value > 0) {
75
+ ctx.warnings.push(
76
+ createA11yPositiveTabindexError(tabindexExpr.value, path)
77
+ );
78
+ }
79
+ }
80
+ if (props && "id" in props) {
81
+ const idExpr = props["id"];
82
+ if (idExpr && idExpr.expr === "lit" && typeof idExpr.value === "string") {
83
+ if (ctx.seenIds.has(idExpr.value)) {
84
+ ctx.warnings.push(createA11yDuplicateIdError(idExpr.value, path));
85
+ } else {
86
+ ctx.seenIds.add(idExpr.value);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ function validateNode(node, path, ctx) {
92
+ switch (node.kind) {
93
+ case "element":
94
+ validateElementNode(node, path, ctx);
95
+ if (node.children) {
96
+ for (let i = 0; i < node.children.length; i++) {
97
+ const child = node.children[i];
98
+ if (child) {
99
+ validateNode(child, `${path}/children/${i}`, ctx);
100
+ }
101
+ }
102
+ }
103
+ break;
104
+ case "text":
105
+ break;
106
+ case "if":
107
+ validateNode(node.then, `${path}/then`, ctx);
108
+ if (node.else) {
109
+ validateNode(node.else, `${path}/else`, ctx);
110
+ }
111
+ break;
112
+ case "each":
113
+ validateNode(node.body, `${path}/body`, ctx);
114
+ break;
115
+ case "component":
116
+ if (node.children) {
117
+ for (let i = 0; i < node.children.length; i++) {
118
+ const child = node.children[i];
119
+ if (child) {
120
+ validateNode(child, `${path}/children/${i}`, ctx);
121
+ }
122
+ }
123
+ }
124
+ break;
125
+ case "slot":
126
+ case "markdown":
127
+ case "code":
128
+ break;
129
+ case "portal":
130
+ if (node.children) {
131
+ for (let i = 0; i < node.children.length; i++) {
132
+ const child = node.children[i];
133
+ if (child) {
134
+ validateNode(child, `${path}/children/${i}`, ctx);
135
+ }
136
+ }
137
+ }
138
+ break;
139
+ case "island":
140
+ validateNode(node.content, `${path}/content`, ctx);
141
+ break;
142
+ case "suspense":
143
+ validateNode(node.fallback, `${path}/fallback`, ctx);
144
+ validateNode(node.content, `${path}/content`, ctx);
145
+ break;
146
+ case "errorBoundary":
147
+ validateNode(node.fallback, `${path}/fallback`, ctx);
148
+ validateNode(node.content, `${path}/content`, ctx);
149
+ break;
150
+ }
151
+ }
152
+ function validateA11y(program) {
153
+ const ctx = {
154
+ warnings: [],
155
+ maxHeadingLevel: 0,
156
+ seenIds: /* @__PURE__ */ new Set()
157
+ };
158
+ validateNode(program.view, "/view", ctx);
159
+ return ctx.warnings;
160
+ }
161
+
17
162
  // src/passes/analyze.ts
18
163
  import {
19
164
  createUndefinedStateError,
@@ -1307,6 +1452,7 @@ function analyzePass(programAst) {
1307
1452
  insideLayout: isLayout
1308
1453
  })
1309
1454
  );
1455
+ const a11yWarnings = validateA11y(programAst);
1310
1456
  if (errors.length > 0) {
1311
1457
  return {
1312
1458
  ok: false,
@@ -1316,7 +1462,8 @@ function analyzePass(programAst) {
1316
1462
  return {
1317
1463
  ok: true,
1318
1464
  ast: programAst,
1319
- context
1465
+ context,
1466
+ warnings: a11yWarnings
1320
1467
  };
1321
1468
  }
1322
1469
 
@@ -1929,6 +2076,9 @@ function transformViewNode(node, ctx) {
1929
2076
  condition: transformExpression(node.condition, ctx),
1930
2077
  then: transformViewNode(node.then, ctx)
1931
2078
  };
2079
+ if (node.transition) {
2080
+ compiledIf.transition = node.transition;
2081
+ }
1932
2082
  if (node.else) {
1933
2083
  compiledIf.else = transformViewNode(node.else, ctx);
1934
2084
  }
@@ -1941,6 +2091,9 @@ function transformViewNode(node, ctx) {
1941
2091
  as: node.as,
1942
2092
  body: transformViewNode(node.body, ctx)
1943
2093
  };
2094
+ if (node.transition) {
2095
+ compiledEach.transition = node.transition;
2096
+ }
1944
2097
  if (node.index) {
1945
2098
  compiledEach.index = node.index;
1946
2099
  }
@@ -2215,7 +2368,8 @@ function compile(input) {
2215
2368
  const program = transformPass(analyzeResult.ast, analyzeResult.context);
2216
2369
  return {
2217
2370
  ok: true,
2218
- program
2371
+ program,
2372
+ warnings: analyzeResult.warnings
2219
2373
  };
2220
2374
  }
2221
2375
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.15.21",
3
+ "version": "0.16.0",
4
4
  "description": "Compiler for Constela UI framework - AST to Program transformation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@constela/core": "0.22.0"
18
+ "@constela/core": "0.23.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",