roqa 0.0.1

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.
@@ -0,0 +1,78 @@
1
+ import { extractJSXAttributes, getJSXChildren, isJSXExpressionContainer } from "../parser.js";
2
+
3
+ /**
4
+ * Transform <Show> components into showBlock() calls
5
+ *
6
+ * Input:
7
+ * <Show when={condition}><div>...</div></Show>
8
+ *
9
+ * Output:
10
+ * showBlock(container, condition, (anchor) => {
11
+ * // template creation and bindings
12
+ * return { start, end };
13
+ * });
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} ShowTransformResult
18
+ * @property {string} containerVar - Variable name of the container element
19
+ * @property {import("@babel/types").Node} conditionExpression - The `when` prop expression
20
+ * @property {import("@babel/types").JSXElement} bodyJSX - The JSX child to render conditionally
21
+ */
22
+
23
+ /**
24
+ * Extract information from a <Show> component for transformation
25
+ * @param {import("@babel/types").JSXElement} node - The <Show> JSX element
26
+ * @param {string} containerVar - Variable name of the parent container
27
+ * @returns {ShowTransformResult}
28
+ */
29
+ export function extractShowInfo(node, containerVar) {
30
+ const attrs = extractJSXAttributes(node.openingElement);
31
+
32
+ // Get the `when` prop
33
+ const whenValue = attrs.get("when");
34
+ if (!whenValue || !isJSXExpressionContainer(whenValue)) {
35
+ throw createShowError(node, "Missing required 'when' prop on <Show> component");
36
+ }
37
+ const conditionExpression = whenValue.expression;
38
+
39
+ // Get the children - should have at least one child element
40
+ const children = getJSXChildren(node);
41
+ if (children.length === 0) {
42
+ throw createShowError(node, "<Show> must have at least one child element");
43
+ }
44
+
45
+ // Get the first JSX element child
46
+ let bodyJSX = null;
47
+ for (const child of children) {
48
+ if (child.type === "JSXElement") {
49
+ bodyJSX = child;
50
+ break;
51
+ }
52
+ // Support expression container with JSX: {<div>...</div>}
53
+ if (isJSXExpressionContainer(child) && child.expression.type === "JSXElement") {
54
+ bodyJSX = child.expression;
55
+ break;
56
+ }
57
+ }
58
+
59
+ if (!bodyJSX) {
60
+ throw createShowError(node, "<Show> must have a JSX element as child");
61
+ }
62
+
63
+ return {
64
+ containerVar,
65
+ conditionExpression,
66
+ bodyJSX,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Create a formatted error for <Show> component issues
72
+ */
73
+ function createShowError(node, message) {
74
+ const error = new Error(message);
75
+ error.code = "SHOW_COMPONENT_ERROR";
76
+ error.loc = node.loc;
77
+ return error;
78
+ }
@@ -0,0 +1,80 @@
1
+ import { traverse } from "../utils.js";
2
+ import { getJSXElementName, isPascalCase, isControlFlowComponent } from "../parser.js";
3
+
4
+ /**
5
+ * Validate that no unsupported PascalCase components remain in the AST
6
+ * This runs AFTER control flow transformers (For, Show, Switch) have processed their nodes
7
+ *
8
+ * Throws helpful error messages with suggestions for alternatives
9
+ */
10
+
11
+ /**
12
+ * Validate the AST for unsupported components
13
+ * @param {import("@babel/types").File} ast - The Babel AST
14
+ * @throws {Error} If unsupported PascalCase components are found
15
+ */
16
+ export function validateNoCustomComponents(ast) {
17
+ const errors = [];
18
+
19
+ traverse(ast, {
20
+ JSXElement(path) {
21
+ const node = path.node;
22
+ const name = getJSXElementName(node);
23
+
24
+ if (!name) return;
25
+
26
+ // Skip control flow components (they should be transformed, but check anyway)
27
+ if (isControlFlowComponent(node)) {
28
+ return;
29
+ }
30
+
31
+ // Check for PascalCase (custom component)
32
+ if (isPascalCase(name)) {
33
+ errors.push(createComponentError(name, node));
34
+ }
35
+ },
36
+ noScope: true,
37
+ });
38
+
39
+ if (errors.length > 0) {
40
+ // Throw the first error (could aggregate, but one at a time is clearer)
41
+ throw errors[0];
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Create a detailed error for an unsupported component
47
+ * @param {string} name - Component name
48
+ * @param {import("@babel/types").JSXElement} node - The JSX node
49
+ * @returns {Error}
50
+ */
51
+ function createComponentError(name, node) {
52
+ const kebabName = toKebabCase(name);
53
+
54
+ const error = new Error(
55
+ `Unsupported component '${name}'.\n` + `PascalCase components are not supported in Roqa.\n`,
56
+ );
57
+
58
+ error.code = "UNSUPPORTED_COMPONENT";
59
+ error.componentName = name;
60
+ error.loc = node.loc;
61
+ error.suggestions = [
62
+ `Use web components: defineComponent("${kebabName}", ${name})`,
63
+ `Use control flow: <For each={items}>`,
64
+ `Use lowercase HTML elements: <div>, <span>, <button>`,
65
+ ];
66
+
67
+ return error;
68
+ }
69
+
70
+ /**
71
+ * Convert PascalCase to kebab-case
72
+ * @param {string} str
73
+ * @returns {string}
74
+ */
75
+ function toKebabCase(str) {
76
+ return str
77
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
78
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
79
+ .toLowerCase();
80
+ }
@@ -0,0 +1,69 @@
1
+ import _traverse from "@babel/traverse";
2
+
3
+ /**
4
+ * Shared utilities for the Roqa compiler
5
+ */
6
+
7
+ // Handle CJS/ESM interop for @babel/traverse
8
+ export const traverse = _traverse.default || _traverse;
9
+
10
+ /**
11
+ * Compiler constants to avoid magic strings
12
+ */
13
+ export const CONSTANTS = {
14
+ /** Prefix for delegated event handlers (e.g., __click) */
15
+ EVENT_PREFIX: "__",
16
+ /** Prefix for cell refs used in inline updates (e.g., ref_1) */
17
+ REF_PREFIX: "ref_",
18
+ /** Prefix for template variables (e.g., $tmpl_1) */
19
+ TEMPLATE_PREFIX: "$tmpl_",
20
+ /** Prefix for root variables (e.g., $root_1) */
21
+ ROOT_PREFIX: "$root_",
22
+ /** Internal key used for root variable counter */
23
+ ROOT_COUNTER_KEY: "$root",
24
+ };
25
+
26
+ /**
27
+ * Escape single quotes and backslashes in template strings
28
+ * @param {string} str
29
+ * @returns {string}
30
+ */
31
+ export function escapeTemplateString(str) {
32
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
33
+ }
34
+
35
+ /**
36
+ * Escape special characters in a string literal
37
+ * @param {string} str
38
+ * @returns {string}
39
+ */
40
+ export function escapeStringLiteral(str) {
41
+ return str
42
+ .replace(/\\/g, "\\\\")
43
+ .replace(/"/g, '\\"')
44
+ .replace(/\n/g, "\\n")
45
+ .replace(/\r/g, "\\r")
46
+ .replace(/\t/g, "\\t");
47
+ }
48
+
49
+ /**
50
+ * Escape HTML special characters
51
+ * @param {string} str
52
+ * @returns {string}
53
+ */
54
+ export function escapeHtml(str) {
55
+ return str
56
+ .replace(/&/g, "&amp;")
57
+ .replace(/</g, "&lt;")
58
+ .replace(/>/g, "&gt;")
59
+ .replace(/"/g, "&quot;");
60
+ }
61
+
62
+ /**
63
+ * Escape attribute values
64
+ * @param {string} str
65
+ * @returns {string}
66
+ */
67
+ export function escapeAttr(str) {
68
+ return str.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
69
+ }