@collie-lang/compiler 1.0.0 → 1.2.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.cjs +3067 -764
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -3
- package/dist/index.d.ts +109 -3
- package/dist/index.js +3052 -764
- package/dist/index.js.map +1 -1
- package/package.json +11 -5
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,40 +17,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
applyFixes: () => applyFixes,
|
|
23
34
|
compile: () => compile,
|
|
35
|
+
compileTemplate: () => compileTemplate,
|
|
24
36
|
compileToHtml: () => compileToHtml,
|
|
25
37
|
compileToJsx: () => compileToJsx,
|
|
26
38
|
compileToTsx: () => compileToTsx,
|
|
39
|
+
convertCollieToTsx: () => convertCollieToTsx,
|
|
40
|
+
convertTsxToCollie: () => convertTsxToCollie,
|
|
41
|
+
defineConfig: () => import_config.defineConfig,
|
|
42
|
+
fixAllFromDiagnostics: () => fixAllFromDiagnostics,
|
|
43
|
+
formatCollie: () => formatCollie,
|
|
44
|
+
loadAndNormalizeConfig: () => import_config.loadAndNormalizeConfig,
|
|
45
|
+
loadConfig: () => import_config.loadConfig,
|
|
46
|
+
normalizeConfig: () => import_config.normalizeConfig,
|
|
27
47
|
parse: () => parseCollie,
|
|
28
48
|
parseCollie: () => parseCollie
|
|
29
49
|
});
|
|
30
50
|
module.exports = __toCommonJS(index_exports);
|
|
31
51
|
|
|
32
52
|
// src/codegen.ts
|
|
33
|
-
function
|
|
34
|
-
const {
|
|
35
|
-
const
|
|
36
|
-
const aliasEnv = buildClassAliasEnvironment(root.classAliases);
|
|
37
|
-
const jsx = renderRootChildren(root.children, aliasEnv);
|
|
38
|
-
const propsDestructure = emitPropsDestructure(root.props);
|
|
39
|
-
const parts = [];
|
|
40
|
-
if (root.clientComponent) {
|
|
41
|
-
parts.push(`"use client";`);
|
|
42
|
-
}
|
|
43
|
-
if (jsxRuntime === "classic" && templateUsesJsx(root)) {
|
|
44
|
-
parts.push(`import React from "react";`);
|
|
45
|
-
}
|
|
46
|
-
parts.push(emitPropsType(root.props, flavor));
|
|
53
|
+
function generateRenderModule(root, options) {
|
|
54
|
+
const { prelude, propsType, propsDestructure, jsx, isTsx } = buildModuleParts(root, options);
|
|
55
|
+
const parts = [...prelude, propsType];
|
|
47
56
|
if (!isTsx) {
|
|
48
|
-
parts.push(`/** @param {
|
|
57
|
+
parts.push(`/** @param {any} props */`);
|
|
49
58
|
}
|
|
50
59
|
const functionLines = [
|
|
51
|
-
isTsx ?
|
|
60
|
+
isTsx ? "export function render(props: any) {" : "export function render(props) {"
|
|
52
61
|
];
|
|
53
62
|
if (propsDestructure) {
|
|
54
63
|
functionLines.push(` ${propsDestructure}`);
|
|
@@ -57,6 +66,22 @@ function generateModule(root, options) {
|
|
|
57
66
|
parts.push(functionLines.join("\n"));
|
|
58
67
|
return parts.join("\n\n");
|
|
59
68
|
}
|
|
69
|
+
function buildModuleParts(root, options) {
|
|
70
|
+
const { jsxRuntime, flavor } = options;
|
|
71
|
+
const isTsx = flavor === "tsx";
|
|
72
|
+
const aliasEnv = buildClassAliasEnvironment(root.classAliases);
|
|
73
|
+
const jsx = renderRootChildren(root.children, aliasEnv);
|
|
74
|
+
const propsDestructure = emitPropsDestructure(root.props);
|
|
75
|
+
const prelude = [];
|
|
76
|
+
if (root.clientComponent) {
|
|
77
|
+
prelude.push(`"use client";`);
|
|
78
|
+
}
|
|
79
|
+
if (jsxRuntime === "classic" && templateUsesJsx(root)) {
|
|
80
|
+
prelude.push(`import React from "react";`);
|
|
81
|
+
}
|
|
82
|
+
const propsType = emitPropsType(root.props, flavor);
|
|
83
|
+
return { prelude, propsType, propsDestructure, jsx, isTsx };
|
|
84
|
+
}
|
|
60
85
|
function buildClassAliasEnvironment(decl) {
|
|
61
86
|
const env = /* @__PURE__ */ new Map();
|
|
62
87
|
if (!decl) {
|
|
@@ -68,7 +93,7 @@ function buildClassAliasEnvironment(decl) {
|
|
|
68
93
|
return env;
|
|
69
94
|
}
|
|
70
95
|
function renderRootChildren(children, aliasEnv) {
|
|
71
|
-
return emitNodesExpression(children, aliasEnv);
|
|
96
|
+
return emitNodesExpression(children, aliasEnv, /* @__PURE__ */ new Set());
|
|
72
97
|
}
|
|
73
98
|
function templateUsesJsx(root) {
|
|
74
99
|
if (root.children.length === 0) {
|
|
@@ -100,58 +125,58 @@ function branchUsesJsx(branch) {
|
|
|
100
125
|
}
|
|
101
126
|
return branch.body.some((child) => nodeUsesJsx(child));
|
|
102
127
|
}
|
|
103
|
-
function emitNodeInJsx(node, aliasEnv) {
|
|
128
|
+
function emitNodeInJsx(node, aliasEnv, locals) {
|
|
104
129
|
if (node.type === "Text") {
|
|
105
|
-
return emitText(node);
|
|
130
|
+
return emitText(node, locals);
|
|
106
131
|
}
|
|
107
132
|
if (node.type === "Expression") {
|
|
108
|
-
return `{${node.value}}`;
|
|
133
|
+
return `{${emitExpressionValue(node.value, locals)}}`;
|
|
109
134
|
}
|
|
110
135
|
if (node.type === "JSXPassthrough") {
|
|
111
|
-
return `{${node.expression}}`;
|
|
136
|
+
return `{${emitJsxExpression(node.expression, locals)}}`;
|
|
112
137
|
}
|
|
113
138
|
if (node.type === "Conditional") {
|
|
114
|
-
return `{${emitConditionalExpression(node, aliasEnv)}}`;
|
|
139
|
+
return `{${emitConditionalExpression(node, aliasEnv, locals)}}`;
|
|
115
140
|
}
|
|
116
141
|
if (node.type === "For") {
|
|
117
|
-
return `{${emitForExpression(node, aliasEnv)}}`;
|
|
142
|
+
return `{${emitForExpression(node, aliasEnv, locals)}}`;
|
|
118
143
|
}
|
|
119
144
|
if (node.type === "Component") {
|
|
120
|
-
return wrapWithGuard(emitComponent(node, aliasEnv), node.guard, "jsx");
|
|
145
|
+
return wrapWithGuard(emitComponent(node, aliasEnv, locals), node.guard, "jsx", locals);
|
|
121
146
|
}
|
|
122
|
-
return wrapWithGuard(emitElement(node, aliasEnv), node.guard, "jsx");
|
|
147
|
+
return wrapWithGuard(emitElement(node, aliasEnv, locals), node.guard, "jsx", locals);
|
|
123
148
|
}
|
|
124
|
-
function emitElement(node, aliasEnv) {
|
|
149
|
+
function emitElement(node, aliasEnv, locals) {
|
|
125
150
|
const expanded = expandClasses(node.classes, aliasEnv);
|
|
126
151
|
const classAttr = expanded.length ? ` className="${expanded.join(" ")}"` : "";
|
|
127
|
-
const attrs = emitAttributes(node.attributes, aliasEnv);
|
|
152
|
+
const attrs = emitAttributes(node.attributes, aliasEnv, locals);
|
|
128
153
|
const allAttrs = classAttr + attrs;
|
|
129
|
-
const children = emitChildrenWithSpacing(node.children, aliasEnv);
|
|
154
|
+
const children = emitChildrenWithSpacing(node.children, aliasEnv, locals);
|
|
130
155
|
if (children.length > 0) {
|
|
131
156
|
return `<${node.name}${allAttrs}>${children}</${node.name}>`;
|
|
132
157
|
} else {
|
|
133
158
|
return `<${node.name}${allAttrs} />`;
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
|
-
function emitComponent(node, aliasEnv) {
|
|
137
|
-
const attrs = emitAttributes(node.attributes, aliasEnv);
|
|
138
|
-
const slotProps = emitSlotProps(node, aliasEnv);
|
|
161
|
+
function emitComponent(node, aliasEnv, locals) {
|
|
162
|
+
const attrs = emitAttributes(node.attributes, aliasEnv, locals);
|
|
163
|
+
const slotProps = emitSlotProps(node, aliasEnv, locals);
|
|
139
164
|
const allAttrs = `${attrs}${slotProps}`;
|
|
140
|
-
const children = emitChildrenWithSpacing(node.children, aliasEnv);
|
|
165
|
+
const children = emitChildrenWithSpacing(node.children, aliasEnv, locals);
|
|
141
166
|
if (children.length > 0) {
|
|
142
167
|
return `<${node.name}${allAttrs}>${children}</${node.name}>`;
|
|
143
168
|
} else {
|
|
144
169
|
return `<${node.name}${allAttrs} />`;
|
|
145
170
|
}
|
|
146
171
|
}
|
|
147
|
-
function emitChildrenWithSpacing(children, aliasEnv) {
|
|
172
|
+
function emitChildrenWithSpacing(children, aliasEnv, locals) {
|
|
148
173
|
if (children.length === 0) {
|
|
149
174
|
return "";
|
|
150
175
|
}
|
|
151
176
|
const parts = [];
|
|
152
177
|
for (let i = 0; i < children.length; i++) {
|
|
153
178
|
const child = children[i];
|
|
154
|
-
const emitted = emitNodeInJsx(child, aliasEnv);
|
|
179
|
+
const emitted = emitNodeInJsx(child, aliasEnv, locals);
|
|
155
180
|
parts.push(emitted);
|
|
156
181
|
if (i < children.length - 1) {
|
|
157
182
|
const nextChild = children[i + 1];
|
|
@@ -163,7 +188,7 @@ function emitChildrenWithSpacing(children, aliasEnv) {
|
|
|
163
188
|
}
|
|
164
189
|
return parts.join("");
|
|
165
190
|
}
|
|
166
|
-
function emitAttributes(attributes, aliasEnv) {
|
|
191
|
+
function emitAttributes(attributes, aliasEnv, locals) {
|
|
167
192
|
if (attributes.length === 0) {
|
|
168
193
|
return "";
|
|
169
194
|
}
|
|
@@ -171,28 +196,32 @@ function emitAttributes(attributes, aliasEnv) {
|
|
|
171
196
|
if (attr.value === null) {
|
|
172
197
|
return ` ${attr.name}`;
|
|
173
198
|
}
|
|
174
|
-
return ` ${attr.name}=${attr.value}`;
|
|
199
|
+
return ` ${attr.name}=${emitAttributeValue(attr.value, locals)}`;
|
|
175
200
|
}).join("");
|
|
176
201
|
}
|
|
177
|
-
function emitSlotProps(node, aliasEnv) {
|
|
202
|
+
function emitSlotProps(node, aliasEnv, locals) {
|
|
178
203
|
if (!node.slots || node.slots.length === 0) {
|
|
179
204
|
return "";
|
|
180
205
|
}
|
|
181
206
|
return node.slots.map((slot) => {
|
|
182
|
-
const expr = emitNodesExpression(slot.children, aliasEnv);
|
|
207
|
+
const expr = emitNodesExpression(slot.children, aliasEnv, locals);
|
|
183
208
|
return ` ${slot.name}={${expr}}`;
|
|
184
209
|
}).join("");
|
|
185
210
|
}
|
|
186
|
-
function wrapWithGuard(rendered, guard, context) {
|
|
211
|
+
function wrapWithGuard(rendered, guard, context, locals) {
|
|
187
212
|
if (!guard) {
|
|
188
213
|
return rendered;
|
|
189
214
|
}
|
|
190
|
-
const
|
|
215
|
+
const condition = emitExpressionValue(guard, locals);
|
|
216
|
+
const expression = `(${condition}) && ${rendered}`;
|
|
191
217
|
return context === "jsx" ? `{${expression}}` : expression;
|
|
192
218
|
}
|
|
193
|
-
function emitForExpression(node, aliasEnv) {
|
|
194
|
-
const
|
|
195
|
-
|
|
219
|
+
function emitForExpression(node, aliasEnv, locals) {
|
|
220
|
+
const arrayExpr = emitExpressionValue(node.arrayExpr, locals);
|
|
221
|
+
const nextLocals = new Set(locals);
|
|
222
|
+
nextLocals.add(node.itemName);
|
|
223
|
+
const body = emitNodesExpression(node.body, aliasEnv, nextLocals);
|
|
224
|
+
return `(${arrayExpr} ?? []).map((${node.itemName}) => ${body})`;
|
|
196
225
|
}
|
|
197
226
|
function expandClasses(classes, aliasEnv) {
|
|
198
227
|
const result = [];
|
|
@@ -210,7 +239,29 @@ function expandClasses(classes, aliasEnv) {
|
|
|
210
239
|
}
|
|
211
240
|
return result;
|
|
212
241
|
}
|
|
213
|
-
function
|
|
242
|
+
function emitExpressionValue(expression, locals) {
|
|
243
|
+
return rewriteExpression(expression, locals);
|
|
244
|
+
}
|
|
245
|
+
function emitJsxExpression(expression, locals) {
|
|
246
|
+
const trimmed = expression.trimStart();
|
|
247
|
+
if (trimmed.startsWith("<")) {
|
|
248
|
+
return rewriteJsxExpression(expression, locals);
|
|
249
|
+
}
|
|
250
|
+
return rewriteExpression(expression, locals);
|
|
251
|
+
}
|
|
252
|
+
function emitAttributeValue(value, locals) {
|
|
253
|
+
const trimmed = value.trim();
|
|
254
|
+
if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
258
|
+
const inner = trimmed.slice(1, -1);
|
|
259
|
+
const rewritten = rewriteExpression(inner, locals);
|
|
260
|
+
return `{${rewritten}}`;
|
|
261
|
+
}
|
|
262
|
+
return rewriteExpression(trimmed, locals);
|
|
263
|
+
}
|
|
264
|
+
function emitText(node, locals) {
|
|
214
265
|
if (!node.parts.length) {
|
|
215
266
|
return "";
|
|
216
267
|
}
|
|
@@ -218,65 +269,66 @@ function emitText(node) {
|
|
|
218
269
|
if (part.type === "text") {
|
|
219
270
|
return escapeText(part.value);
|
|
220
271
|
}
|
|
221
|
-
return `{${part.value}}`;
|
|
272
|
+
return `{${emitExpressionValue(part.value, locals)}}`;
|
|
222
273
|
}).join("");
|
|
223
274
|
}
|
|
224
|
-
function emitConditionalExpression(node, aliasEnv) {
|
|
275
|
+
function emitConditionalExpression(node, aliasEnv, locals) {
|
|
225
276
|
if (!node.branches.length) {
|
|
226
277
|
return "null";
|
|
227
278
|
}
|
|
228
279
|
const first = node.branches[0];
|
|
229
280
|
if (node.branches.length === 1 && first.test) {
|
|
230
|
-
|
|
281
|
+
const test = emitExpressionValue(first.test, locals);
|
|
282
|
+
return `(${test}) && ${emitBranchExpression(first, aliasEnv, locals)}`;
|
|
231
283
|
}
|
|
232
284
|
const hasElse = node.branches[node.branches.length - 1].test === void 0;
|
|
233
|
-
let fallback = hasElse ? emitBranchExpression(node.branches[node.branches.length - 1], aliasEnv) : "null";
|
|
285
|
+
let fallback = hasElse ? emitBranchExpression(node.branches[node.branches.length - 1], aliasEnv, locals) : "null";
|
|
234
286
|
const startIndex = hasElse ? node.branches.length - 2 : node.branches.length - 1;
|
|
235
287
|
if (startIndex < 0) {
|
|
236
288
|
return fallback;
|
|
237
289
|
}
|
|
238
290
|
for (let i = startIndex; i >= 0; i--) {
|
|
239
291
|
const branch = node.branches[i];
|
|
240
|
-
const test = branch.test
|
|
241
|
-
fallback = `(${test}) ? ${emitBranchExpression(branch, aliasEnv)} : ${fallback}`;
|
|
292
|
+
const test = branch.test ? emitExpressionValue(branch.test, locals) : "false";
|
|
293
|
+
fallback = `(${test}) ? ${emitBranchExpression(branch, aliasEnv, locals)} : ${fallback}`;
|
|
242
294
|
}
|
|
243
295
|
return fallback;
|
|
244
296
|
}
|
|
245
|
-
function emitBranchExpression(branch, aliasEnv) {
|
|
246
|
-
return emitNodesExpression(branch.body, aliasEnv);
|
|
297
|
+
function emitBranchExpression(branch, aliasEnv, locals) {
|
|
298
|
+
return emitNodesExpression(branch.body, aliasEnv, locals);
|
|
247
299
|
}
|
|
248
|
-
function emitNodesExpression(children, aliasEnv) {
|
|
300
|
+
function emitNodesExpression(children, aliasEnv, locals) {
|
|
249
301
|
if (children.length === 0) {
|
|
250
302
|
return "null";
|
|
251
303
|
}
|
|
252
304
|
if (children.length === 1) {
|
|
253
|
-
return emitSingleNodeExpression(children[0], aliasEnv);
|
|
305
|
+
return emitSingleNodeExpression(children[0], aliasEnv, locals);
|
|
254
306
|
}
|
|
255
|
-
return `<>${children.map((child) => emitNodeInJsx(child, aliasEnv)).join("")}</>`;
|
|
307
|
+
return `<>${children.map((child) => emitNodeInJsx(child, aliasEnv, locals)).join("")}</>`;
|
|
256
308
|
}
|
|
257
|
-
function emitSingleNodeExpression(node, aliasEnv) {
|
|
309
|
+
function emitSingleNodeExpression(node, aliasEnv, locals) {
|
|
258
310
|
if (node.type === "Expression") {
|
|
259
|
-
return node.value;
|
|
311
|
+
return emitExpressionValue(node.value, locals);
|
|
260
312
|
}
|
|
261
313
|
if (node.type === "JSXPassthrough") {
|
|
262
|
-
return node.expression;
|
|
314
|
+
return emitJsxExpression(node.expression, locals);
|
|
263
315
|
}
|
|
264
316
|
if (node.type === "Conditional") {
|
|
265
|
-
return emitConditionalExpression(node, aliasEnv);
|
|
317
|
+
return emitConditionalExpression(node, aliasEnv, locals);
|
|
266
318
|
}
|
|
267
319
|
if (node.type === "For") {
|
|
268
|
-
return emitForExpression(node, aliasEnv);
|
|
320
|
+
return emitForExpression(node, aliasEnv, locals);
|
|
269
321
|
}
|
|
270
322
|
if (node.type === "Element") {
|
|
271
|
-
return wrapWithGuard(emitElement(node, aliasEnv), node.guard, "expression");
|
|
323
|
+
return wrapWithGuard(emitElement(node, aliasEnv, locals), node.guard, "expression", locals);
|
|
272
324
|
}
|
|
273
325
|
if (node.type === "Component") {
|
|
274
|
-
return wrapWithGuard(emitComponent(node, aliasEnv), node.guard, "expression");
|
|
326
|
+
return wrapWithGuard(emitComponent(node, aliasEnv, locals), node.guard, "expression", locals);
|
|
275
327
|
}
|
|
276
328
|
if (node.type === "Text") {
|
|
277
|
-
return `<>${emitNodeInJsx(node, aliasEnv)}</>`;
|
|
329
|
+
return `<>${emitNodeInJsx(node, aliasEnv, locals)}</>`;
|
|
278
330
|
}
|
|
279
|
-
return emitNodeInJsx(node, aliasEnv);
|
|
331
|
+
return emitNodeInJsx(node, aliasEnv, locals);
|
|
280
332
|
}
|
|
281
333
|
function emitPropsType(props, flavor) {
|
|
282
334
|
if (flavor === "tsx") {
|
|
@@ -312,7 +364,7 @@ function emitPropsDestructure(props) {
|
|
|
312
364
|
return null;
|
|
313
365
|
}
|
|
314
366
|
const names = props.fields.map((field) => field.name);
|
|
315
|
-
return `const { ${names.join(", ")} } = props;`;
|
|
367
|
+
return `const { ${names.join(", ")} } = props ?? {};`;
|
|
316
368
|
}
|
|
317
369
|
function escapeText(value) {
|
|
318
370
|
return value.replace(/[&<>{}]/g, (char) => {
|
|
@@ -332,177 +384,463 @@ function escapeText(value) {
|
|
|
332
384
|
}
|
|
333
385
|
});
|
|
334
386
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
387
|
+
var IGNORED_IDENTIFIERS = /* @__PURE__ */ new Set([
|
|
388
|
+
"null",
|
|
389
|
+
"undefined",
|
|
390
|
+
"true",
|
|
391
|
+
"false",
|
|
392
|
+
"NaN",
|
|
393
|
+
"Infinity",
|
|
394
|
+
"this",
|
|
395
|
+
"props"
|
|
396
|
+
]);
|
|
397
|
+
var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
398
|
+
"await",
|
|
399
|
+
"break",
|
|
400
|
+
"case",
|
|
401
|
+
"catch",
|
|
402
|
+
"class",
|
|
403
|
+
"const",
|
|
404
|
+
"continue",
|
|
405
|
+
"debugger",
|
|
406
|
+
"default",
|
|
407
|
+
"delete",
|
|
408
|
+
"do",
|
|
409
|
+
"else",
|
|
410
|
+
"enum",
|
|
411
|
+
"export",
|
|
412
|
+
"extends",
|
|
413
|
+
"false",
|
|
414
|
+
"finally",
|
|
415
|
+
"for",
|
|
416
|
+
"function",
|
|
417
|
+
"if",
|
|
418
|
+
"import",
|
|
419
|
+
"in",
|
|
420
|
+
"instanceof",
|
|
421
|
+
"let",
|
|
422
|
+
"new",
|
|
423
|
+
"null",
|
|
424
|
+
"return",
|
|
425
|
+
"super",
|
|
426
|
+
"switch",
|
|
427
|
+
"this",
|
|
428
|
+
"throw",
|
|
429
|
+
"true",
|
|
430
|
+
"try",
|
|
431
|
+
"typeof",
|
|
432
|
+
"var",
|
|
433
|
+
"void",
|
|
434
|
+
"while",
|
|
435
|
+
"with",
|
|
436
|
+
"yield"
|
|
437
|
+
]);
|
|
438
|
+
function emitIdentifier(name) {
|
|
439
|
+
return `props?.${name}`;
|
|
358
440
|
}
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
441
|
+
function rewriteExpression(expression, locals) {
|
|
442
|
+
let i = 0;
|
|
443
|
+
let state = "code";
|
|
444
|
+
let output = "";
|
|
445
|
+
while (i < expression.length) {
|
|
446
|
+
const ch = expression[i];
|
|
447
|
+
if (state === "code") {
|
|
448
|
+
if (ch === "'" || ch === '"') {
|
|
449
|
+
state = ch === "'" ? "single" : "double";
|
|
450
|
+
output += ch;
|
|
451
|
+
i++;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (ch === "`") {
|
|
455
|
+
state = "template";
|
|
456
|
+
output += ch;
|
|
457
|
+
i++;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (ch === "/" && expression[i + 1] === "/") {
|
|
461
|
+
state = "line";
|
|
462
|
+
output += ch;
|
|
463
|
+
i++;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (ch === "/" && expression[i + 1] === "*") {
|
|
467
|
+
state = "block";
|
|
468
|
+
output += ch;
|
|
469
|
+
i++;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (isIdentifierStart(ch)) {
|
|
473
|
+
const start = i;
|
|
474
|
+
i++;
|
|
475
|
+
while (i < expression.length && isIdentifierPart(expression[i])) {
|
|
476
|
+
i++;
|
|
477
|
+
}
|
|
478
|
+
const name = expression.slice(start, i);
|
|
479
|
+
const prevNonSpace = findPreviousNonSpace(expression, start - 1);
|
|
480
|
+
const nextNonSpace = findNextNonSpace(expression, i);
|
|
481
|
+
const isMemberAccess = prevNonSpace === ".";
|
|
482
|
+
const isObjectKey = nextNonSpace === ":";
|
|
483
|
+
if (isMemberAccess || isObjectKey || locals.has(name) || shouldIgnoreIdentifier(name)) {
|
|
484
|
+
output += name;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
output += emitIdentifier(name);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
output += ch;
|
|
491
|
+
i++;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (state === "line") {
|
|
495
|
+
output += ch;
|
|
496
|
+
if (ch === "\n") {
|
|
497
|
+
state = "code";
|
|
498
|
+
}
|
|
499
|
+
i++;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (state === "block") {
|
|
503
|
+
output += ch;
|
|
504
|
+
if (ch === "*" && expression[i + 1] === "/") {
|
|
505
|
+
output += "/";
|
|
506
|
+
i += 2;
|
|
507
|
+
state = "code";
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
i++;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (state === "single") {
|
|
514
|
+
output += ch;
|
|
515
|
+
if (ch === "\\") {
|
|
516
|
+
if (i + 1 < expression.length) {
|
|
517
|
+
output += expression[i + 1];
|
|
518
|
+
i += 2;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (ch === "'") {
|
|
523
|
+
state = "code";
|
|
524
|
+
}
|
|
525
|
+
i++;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (state === "double") {
|
|
529
|
+
output += ch;
|
|
530
|
+
if (ch === "\\") {
|
|
531
|
+
if (i + 1 < expression.length) {
|
|
532
|
+
output += expression[i + 1];
|
|
533
|
+
i += 2;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (ch === '"') {
|
|
538
|
+
state = "code";
|
|
539
|
+
}
|
|
540
|
+
i++;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (state === "template") {
|
|
544
|
+
output += ch;
|
|
545
|
+
if (ch === "\\") {
|
|
546
|
+
if (i + 1 < expression.length) {
|
|
547
|
+
output += expression[i + 1];
|
|
548
|
+
i += 2;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (ch === "`") {
|
|
553
|
+
state = "code";
|
|
554
|
+
}
|
|
555
|
+
i++;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
362
558
|
}
|
|
363
|
-
|
|
364
|
-
return concatSegments(segments);
|
|
559
|
+
return output;
|
|
365
560
|
}
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
561
|
+
function rewriteJsxExpression(expression, locals) {
|
|
562
|
+
let output = "";
|
|
563
|
+
let i = 0;
|
|
564
|
+
while (i < expression.length) {
|
|
565
|
+
const ch = expression[i];
|
|
566
|
+
if (ch === "{") {
|
|
567
|
+
const braceResult = readBalancedBraces(expression, i + 1);
|
|
568
|
+
if (!braceResult) {
|
|
569
|
+
output += expression.slice(i);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
const rewritten = rewriteExpression(braceResult.content, locals);
|
|
573
|
+
output += `{${rewritten}}`;
|
|
574
|
+
i = braceResult.endIndex + 1;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
output += ch;
|
|
578
|
+
i++;
|
|
384
579
|
}
|
|
580
|
+
return output;
|
|
385
581
|
}
|
|
386
|
-
function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
582
|
+
function readBalancedBraces(source, startIndex) {
|
|
583
|
+
let i = startIndex;
|
|
584
|
+
let depth = 1;
|
|
585
|
+
let state = "code";
|
|
586
|
+
while (i < source.length) {
|
|
587
|
+
const ch = source[i];
|
|
588
|
+
if (state === "code") {
|
|
589
|
+
if (ch === "'" || ch === '"') {
|
|
590
|
+
state = ch === "'" ? "single" : "double";
|
|
591
|
+
i++;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (ch === "`") {
|
|
595
|
+
state = "template";
|
|
596
|
+
i++;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
600
|
+
state = "line";
|
|
601
|
+
i += 2;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
605
|
+
state = "block";
|
|
606
|
+
i += 2;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (ch === "{") {
|
|
610
|
+
depth += 1;
|
|
611
|
+
} else if (ch === "}") {
|
|
612
|
+
depth -= 1;
|
|
613
|
+
if (depth === 0) {
|
|
614
|
+
return { content: source.slice(startIndex, i), endIndex: i };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
i++;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (state === "line") {
|
|
621
|
+
if (ch === "\n") {
|
|
622
|
+
state = "code";
|
|
623
|
+
}
|
|
624
|
+
i++;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (state === "block") {
|
|
628
|
+
if (ch === "*" && source[i + 1] === "/") {
|
|
629
|
+
i += 2;
|
|
630
|
+
state = "code";
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
i++;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (state === "single") {
|
|
637
|
+
if (ch === "\\") {
|
|
638
|
+
i += 2;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (ch === "'") {
|
|
642
|
+
state = "code";
|
|
643
|
+
}
|
|
644
|
+
i++;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (state === "double") {
|
|
648
|
+
if (ch === "\\") {
|
|
649
|
+
i += 2;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (ch === '"') {
|
|
653
|
+
state = "code";
|
|
654
|
+
}
|
|
655
|
+
i++;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (state === "template") {
|
|
659
|
+
if (ch === "\\") {
|
|
660
|
+
i += 2;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (ch === "`") {
|
|
664
|
+
state = "code";
|
|
665
|
+
}
|
|
666
|
+
i++;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
392
669
|
}
|
|
393
|
-
|
|
394
|
-
const end = literal(`</${node.name}>`);
|
|
395
|
-
return concatSegments([start, children, end]);
|
|
670
|
+
return null;
|
|
396
671
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return start;
|
|
404
|
-
}
|
|
405
|
-
const childSegments = [];
|
|
406
|
-
if (node.children.length) {
|
|
407
|
-
childSegments.push(emitNodesString(node.children, aliasEnv));
|
|
672
|
+
function findPreviousNonSpace(text, index) {
|
|
673
|
+
for (let i = index; i >= 0; i--) {
|
|
674
|
+
const ch = text[i];
|
|
675
|
+
if (!/\s/.test(ch)) {
|
|
676
|
+
return ch;
|
|
677
|
+
}
|
|
408
678
|
}
|
|
409
|
-
|
|
410
|
-
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
function findNextNonSpace(text, index) {
|
|
682
|
+
for (let i = index; i < text.length; i++) {
|
|
683
|
+
const ch = text[i];
|
|
684
|
+
if (!/\s/.test(ch)) {
|
|
685
|
+
return ch;
|
|
686
|
+
}
|
|
411
687
|
}
|
|
412
|
-
|
|
413
|
-
const end = literal(`</${node.name}>`);
|
|
414
|
-
return concatSegments([start, children, end]);
|
|
688
|
+
return null;
|
|
415
689
|
}
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
const body = emitNodesString(slot.children, aliasEnv);
|
|
419
|
-
const end = literal("</template>");
|
|
420
|
-
return concatSegments([start, body, end]);
|
|
690
|
+
function isIdentifierStart(ch) {
|
|
691
|
+
return /[A-Za-z_$]/.test(ch);
|
|
421
692
|
}
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
return '""';
|
|
425
|
-
}
|
|
426
|
-
const first = node.branches[0];
|
|
427
|
-
if (node.branches.length === 1 && first.test) {
|
|
428
|
-
return `(${first.test}) ? ${emitBranch(first, aliasEnv)} : ""`;
|
|
429
|
-
}
|
|
430
|
-
const hasElse = node.branches[node.branches.length - 1].test === void 0;
|
|
431
|
-
let fallback = hasElse ? emitBranch(node.branches[node.branches.length - 1], aliasEnv) : '""';
|
|
432
|
-
const limit = hasElse ? node.branches.length - 2 : node.branches.length - 1;
|
|
433
|
-
for (let i = limit; i >= 0; i--) {
|
|
434
|
-
const branch = node.branches[i];
|
|
435
|
-
const test = branch.test ?? "false";
|
|
436
|
-
fallback = `(${test}) ? ${emitBranch(branch, aliasEnv)} : ${fallback}`;
|
|
437
|
-
}
|
|
438
|
-
return fallback;
|
|
693
|
+
function isIdentifierPart(ch) {
|
|
694
|
+
return /[A-Za-z0-9_$]/.test(ch);
|
|
439
695
|
}
|
|
440
|
-
function
|
|
441
|
-
return
|
|
696
|
+
function shouldIgnoreIdentifier(name) {
|
|
697
|
+
return IGNORED_IDENTIFIERS.has(name) || RESERVED_KEYWORDS.has(name);
|
|
442
698
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
699
|
+
|
|
700
|
+
// src/html-codegen.ts
|
|
701
|
+
function generateHtml(root, options = {}) {
|
|
702
|
+
const indent = options.indent ?? " ";
|
|
703
|
+
const aliasEnv = buildClassAliasEnvironment2(root.classAliases);
|
|
704
|
+
const rendered = emitNodes(root.children, aliasEnv, indent, 0);
|
|
705
|
+
return rendered.trimEnd();
|
|
446
706
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
707
|
+
function emitNodes(children, aliasEnv, indent, depth) {
|
|
708
|
+
let html = "";
|
|
709
|
+
for (const child of children) {
|
|
710
|
+
const chunk = emitNode(child, aliasEnv, indent, depth);
|
|
711
|
+
if (chunk) {
|
|
712
|
+
html += chunk;
|
|
713
|
+
}
|
|
450
714
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
715
|
+
return html;
|
|
716
|
+
}
|
|
717
|
+
function emitNode(node, aliasEnv, indent, depth) {
|
|
718
|
+
switch (node.type) {
|
|
719
|
+
case "Element":
|
|
720
|
+
return emitElement2(node, aliasEnv, indent, depth);
|
|
721
|
+
case "Text":
|
|
722
|
+
return emitTextBlock(node, indent, depth);
|
|
723
|
+
default:
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function emitElement2(node, aliasEnv, indent, depth) {
|
|
728
|
+
const indentText = indent.repeat(depth);
|
|
729
|
+
const classNames = expandClasses2(node.classes, aliasEnv);
|
|
730
|
+
const attrs = renderAttributes(node.attributes, classNames);
|
|
731
|
+
const openTag = `<${node.name}${attrs}>`;
|
|
732
|
+
if (node.children.length === 0) {
|
|
733
|
+
return `${indentText}${openTag}</${node.name}>
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
if (node.children.length === 1 && node.children[0].type === "Text") {
|
|
737
|
+
const inline = emitInlineText(node.children[0]);
|
|
738
|
+
if (inline !== null) {
|
|
739
|
+
return `${indentText}${openTag}${inline}</${node.name}>
|
|
740
|
+
`;
|
|
454
741
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
742
|
+
}
|
|
743
|
+
const children = emitNodes(node.children, aliasEnv, indent, depth + 1);
|
|
744
|
+
if (!children) {
|
|
745
|
+
return `${indentText}${openTag}</${node.name}>
|
|
746
|
+
`;
|
|
747
|
+
}
|
|
748
|
+
return `${indentText}${openTag}
|
|
749
|
+
${children}${indentText}</${node.name}>
|
|
750
|
+
`;
|
|
458
751
|
}
|
|
459
|
-
function
|
|
752
|
+
function renderAttributes(attributes, classNames) {
|
|
460
753
|
const segments = [];
|
|
461
754
|
if (classNames.length) {
|
|
462
|
-
segments.push(
|
|
755
|
+
segments.push(`class="${escapeAttributeValue(classNames.join(" "))}"`);
|
|
463
756
|
}
|
|
464
757
|
for (const attr of attributes) {
|
|
465
758
|
if (attr.value === null) {
|
|
466
|
-
segments.push(
|
|
759
|
+
segments.push(attr.name);
|
|
467
760
|
continue;
|
|
468
761
|
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
"})()"
|
|
476
|
-
].join(" ")
|
|
477
|
-
);
|
|
762
|
+
const literal = extractStaticAttributeValue(attr.value);
|
|
763
|
+
if (literal === null) {
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const name = attr.name === "className" ? "class" : attr.name;
|
|
767
|
+
segments.push(`${name}="${escapeAttributeValue(literal)}"`);
|
|
478
768
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
function attributeExpression(raw) {
|
|
482
|
-
const trimmed = raw.trim();
|
|
483
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
484
|
-
return trimmed.slice(1, -1).trim();
|
|
769
|
+
if (!segments.length) {
|
|
770
|
+
return "";
|
|
485
771
|
}
|
|
486
|
-
return
|
|
772
|
+
return " " + segments.join(" ");
|
|
487
773
|
}
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
|
|
774
|
+
function emitTextBlock(node, indent, depth) {
|
|
775
|
+
const inline = emitInlineText(node);
|
|
776
|
+
if (inline === null || inline.trim().length === 0) {
|
|
777
|
+
return "";
|
|
491
778
|
}
|
|
492
|
-
return
|
|
779
|
+
return `${indent.repeat(depth)}${inline}
|
|
780
|
+
`;
|
|
493
781
|
}
|
|
494
|
-
function
|
|
495
|
-
|
|
782
|
+
function emitInlineText(node) {
|
|
783
|
+
if (!node.parts.length) {
|
|
784
|
+
return "";
|
|
785
|
+
}
|
|
786
|
+
let text = "";
|
|
787
|
+
for (const part of node.parts) {
|
|
788
|
+
if (part.type !== "text") {
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
text += escapeStaticText(part.value);
|
|
792
|
+
}
|
|
793
|
+
return text;
|
|
496
794
|
}
|
|
497
|
-
function
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
return
|
|
795
|
+
function extractStaticAttributeValue(raw) {
|
|
796
|
+
const trimmed = raw.trim();
|
|
797
|
+
if (trimmed.length < 2) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
const quote = trimmed[0];
|
|
801
|
+
if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
const body = trimmed.slice(1, -1);
|
|
805
|
+
let result = "";
|
|
806
|
+
let escaping = false;
|
|
807
|
+
for (const char of body) {
|
|
808
|
+
if (escaping) {
|
|
809
|
+
result += unescapeChar(char, quote);
|
|
810
|
+
escaping = false;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (char === "\\") {
|
|
814
|
+
escaping = true;
|
|
815
|
+
} else {
|
|
816
|
+
result += char;
|
|
817
|
+
}
|
|
501
818
|
}
|
|
502
|
-
if (
|
|
503
|
-
|
|
819
|
+
if (escaping) {
|
|
820
|
+
result += "\\";
|
|
821
|
+
}
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
function unescapeChar(char, quote) {
|
|
825
|
+
switch (char) {
|
|
826
|
+
case "n":
|
|
827
|
+
return "\n";
|
|
828
|
+
case "r":
|
|
829
|
+
return "\r";
|
|
830
|
+
case "t":
|
|
831
|
+
return " ";
|
|
832
|
+
case "\\":
|
|
833
|
+
return "\\";
|
|
834
|
+
case '"':
|
|
835
|
+
return '"';
|
|
836
|
+
case "'":
|
|
837
|
+
return "'";
|
|
838
|
+
default:
|
|
839
|
+
if (char === quote) {
|
|
840
|
+
return quote;
|
|
841
|
+
}
|
|
842
|
+
return char;
|
|
504
843
|
}
|
|
505
|
-
return filtered.join(" + ");
|
|
506
844
|
}
|
|
507
845
|
function buildClassAliasEnvironment2(decl) {
|
|
508
846
|
const env = /* @__PURE__ */ new Map();
|
|
@@ -530,26 +868,6 @@ function expandClasses2(classes, aliasEnv) {
|
|
|
530
868
|
}
|
|
531
869
|
return result;
|
|
532
870
|
}
|
|
533
|
-
function emitJsDocPropsType2(props) {
|
|
534
|
-
if (!props) {
|
|
535
|
-
return "/** @typedef {any} Props */";
|
|
536
|
-
}
|
|
537
|
-
if (!props.fields.length) {
|
|
538
|
-
return "/** @typedef {{}} Props */";
|
|
539
|
-
}
|
|
540
|
-
const fields = props.fields.map((field) => {
|
|
541
|
-
const optional = field.optional ? "?" : "";
|
|
542
|
-
return `${field.name}${optional}: ${field.typeText}`;
|
|
543
|
-
}).join("; ");
|
|
544
|
-
return `/** @typedef {{ ${fields} }} Props */`;
|
|
545
|
-
}
|
|
546
|
-
function emitPropsDestructure2(props) {
|
|
547
|
-
if (!props || props.fields.length === 0) {
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
550
|
-
const names = props.fields.map((field) => field.name);
|
|
551
|
-
return `const { ${names.join(", ")} } = props;`;
|
|
552
|
-
}
|
|
553
871
|
function escapeStaticText(value) {
|
|
554
872
|
return value.replace(/[&<>{}]/g, (char) => {
|
|
555
873
|
switch (char) {
|
|
@@ -568,47 +886,21 @@ function escapeStaticText(value) {
|
|
|
568
886
|
}
|
|
569
887
|
});
|
|
570
888
|
}
|
|
571
|
-
function
|
|
572
|
-
return [
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
' return ">";',
|
|
587
|
-
" default:",
|
|
588
|
-
" return char;",
|
|
589
|
-
" }",
|
|
590
|
-
"}",
|
|
591
|
-
"function __collie_escapeAttr(value) {",
|
|
592
|
-
" if (value === null || value === undefined) {",
|
|
593
|
-
' return "";',
|
|
594
|
-
" }",
|
|
595
|
-
' return String(value).replace(/["&<>]/g, __collie_escapeAttrChar);',
|
|
596
|
-
"}",
|
|
597
|
-
"function __collie_escapeAttrChar(char) {",
|
|
598
|
-
" switch (char) {",
|
|
599
|
-
' case "&":',
|
|
600
|
-
' return "&";',
|
|
601
|
-
' case "<":',
|
|
602
|
-
' return "<";',
|
|
603
|
-
' case ">":',
|
|
604
|
-
' return ">";',
|
|
605
|
-
` case '"':`,
|
|
606
|
-
' return """;',
|
|
607
|
-
" default:",
|
|
608
|
-
" return char;",
|
|
609
|
-
" }",
|
|
610
|
-
"}"
|
|
611
|
-
];
|
|
889
|
+
function escapeAttributeValue(value) {
|
|
890
|
+
return value.replace(/["&<>]/g, (char) => {
|
|
891
|
+
switch (char) {
|
|
892
|
+
case "&":
|
|
893
|
+
return "&";
|
|
894
|
+
case "<":
|
|
895
|
+
return "<";
|
|
896
|
+
case ">":
|
|
897
|
+
return ">";
|
|
898
|
+
case '"':
|
|
899
|
+
return """;
|
|
900
|
+
default:
|
|
901
|
+
return char;
|
|
902
|
+
}
|
|
903
|
+
});
|
|
612
904
|
}
|
|
613
905
|
|
|
614
906
|
// src/diagnostics.ts
|
|
@@ -620,142 +912,902 @@ function createSpan(line, col, length, lineOffset) {
|
|
|
620
912
|
};
|
|
621
913
|
}
|
|
622
914
|
|
|
623
|
-
// src/
|
|
624
|
-
function
|
|
625
|
-
const match = line.match(/^\s*/);
|
|
626
|
-
return match ? match[0].length / 2 : 0;
|
|
627
|
-
}
|
|
628
|
-
function parse(source) {
|
|
915
|
+
// src/dialect.ts
|
|
916
|
+
function enforceDialect(root, config) {
|
|
629
917
|
const diagnostics = [];
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
918
|
+
if (root.idToken) {
|
|
919
|
+
diagnostics.push(
|
|
920
|
+
...evaluateToken(
|
|
921
|
+
{ kind: "id", token: root.idToken, span: root.idTokenSpan },
|
|
922
|
+
config.tokens.id
|
|
923
|
+
)
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
walkNodes(root.children, (occurrence) => {
|
|
927
|
+
const rule = config.tokens[occurrence.kind];
|
|
928
|
+
diagnostics.push(...evaluateToken(occurrence, rule));
|
|
929
|
+
});
|
|
930
|
+
return diagnostics;
|
|
931
|
+
}
|
|
932
|
+
function walkNodes(nodes, onToken) {
|
|
933
|
+
for (const node of nodes) {
|
|
934
|
+
if (node.type === "For") {
|
|
935
|
+
onFor(node, onToken);
|
|
936
|
+
walkNodes(node.body, onToken);
|
|
648
937
|
continue;
|
|
649
938
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
pushDiag(
|
|
653
|
-
diagnostics,
|
|
654
|
-
"COLLIE001",
|
|
655
|
-
"Tabs are not allowed; use spaces for indentation.",
|
|
656
|
-
lineNumber,
|
|
657
|
-
tabIndex + 1,
|
|
658
|
-
lineOffset
|
|
659
|
-
);
|
|
939
|
+
if (node.type === "Conditional") {
|
|
940
|
+
onConditional(node, onToken);
|
|
660
941
|
continue;
|
|
661
942
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
943
|
+
if (node.type === "Element" || node.type === "Component") {
|
|
944
|
+
walkNodes(node.children, onToken);
|
|
945
|
+
if (node.type === "Component" && node.slots) {
|
|
946
|
+
for (const slot of node.slots) {
|
|
947
|
+
walkNodes(slot.children, onToken);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function onFor(node, onToken) {
|
|
955
|
+
if (!node.token) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
onToken({ kind: "for", token: node.token, span: node.tokenSpan });
|
|
959
|
+
}
|
|
960
|
+
function onConditional(node, onToken) {
|
|
961
|
+
for (const branch of node.branches) {
|
|
962
|
+
onBranch(branch, onToken);
|
|
963
|
+
walkNodes(branch.body, onToken);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function onBranch(branch, onToken) {
|
|
967
|
+
if (!branch.token || !branch.kind) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
onToken({ kind: branch.kind, token: branch.token, span: branch.tokenSpan });
|
|
971
|
+
}
|
|
972
|
+
function evaluateToken(occurrence, rule) {
|
|
973
|
+
const diagnostics = [];
|
|
974
|
+
const used = occurrence.token;
|
|
975
|
+
const preferred = rule.preferred;
|
|
976
|
+
const isAllowed = rule.allow.includes(used);
|
|
977
|
+
if (!isAllowed) {
|
|
978
|
+
const severity = levelToSeverity(rule.onDisallowed);
|
|
979
|
+
if (severity) {
|
|
980
|
+
diagnostics.push(
|
|
981
|
+
createDialectDiagnostic(
|
|
982
|
+
"dialect.token.disallowed",
|
|
983
|
+
severity,
|
|
984
|
+
used,
|
|
985
|
+
preferred,
|
|
986
|
+
occurrence.span,
|
|
987
|
+
`Token "${used}" is not allowed for ${occurrence.kind}. Preferred: "${preferred}".`
|
|
988
|
+
)
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
return diagnostics;
|
|
992
|
+
}
|
|
993
|
+
if (used !== preferred) {
|
|
994
|
+
const severity = levelToSeverity(rule.onDisallowed);
|
|
995
|
+
if (severity) {
|
|
996
|
+
diagnostics.push(
|
|
997
|
+
createDialectDiagnostic(
|
|
998
|
+
"dialect.token.nonPreferred",
|
|
999
|
+
severity,
|
|
1000
|
+
used,
|
|
1001
|
+
preferred,
|
|
1002
|
+
occurrence.span,
|
|
1003
|
+
`Token "${used}" is allowed but not preferred for ${occurrence.kind}. Preferred: "${preferred}".`
|
|
1004
|
+
)
|
|
674
1005
|
);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return diagnostics;
|
|
1009
|
+
}
|
|
1010
|
+
function createDialectDiagnostic(code, severity, used, preferred, span, message) {
|
|
1011
|
+
const fix = span ? {
|
|
1012
|
+
range: span,
|
|
1013
|
+
replacementText: preferred
|
|
1014
|
+
} : void 0;
|
|
1015
|
+
return {
|
|
1016
|
+
severity,
|
|
1017
|
+
code,
|
|
1018
|
+
message: message.replace(/\\s+/g, " "),
|
|
1019
|
+
span,
|
|
1020
|
+
fix
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function levelToSeverity(level) {
|
|
1024
|
+
if (level === "off") {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
if (level === "error") {
|
|
1028
|
+
return "error";
|
|
1029
|
+
}
|
|
1030
|
+
return "warning";
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/props.ts
|
|
1034
|
+
var IGNORED_IDENTIFIERS2 = /* @__PURE__ */ new Set([
|
|
1035
|
+
"null",
|
|
1036
|
+
"undefined",
|
|
1037
|
+
"true",
|
|
1038
|
+
"false",
|
|
1039
|
+
"NaN",
|
|
1040
|
+
"Infinity",
|
|
1041
|
+
"this",
|
|
1042
|
+
"props"
|
|
1043
|
+
]);
|
|
1044
|
+
var RESERVED_KEYWORDS2 = /* @__PURE__ */ new Set([
|
|
1045
|
+
"await",
|
|
1046
|
+
"break",
|
|
1047
|
+
"case",
|
|
1048
|
+
"catch",
|
|
1049
|
+
"class",
|
|
1050
|
+
"const",
|
|
1051
|
+
"continue",
|
|
1052
|
+
"debugger",
|
|
1053
|
+
"default",
|
|
1054
|
+
"delete",
|
|
1055
|
+
"do",
|
|
1056
|
+
"else",
|
|
1057
|
+
"enum",
|
|
1058
|
+
"export",
|
|
1059
|
+
"extends",
|
|
1060
|
+
"false",
|
|
1061
|
+
"finally",
|
|
1062
|
+
"for",
|
|
1063
|
+
"function",
|
|
1064
|
+
"if",
|
|
1065
|
+
"import",
|
|
1066
|
+
"in",
|
|
1067
|
+
"instanceof",
|
|
1068
|
+
"let",
|
|
1069
|
+
"new",
|
|
1070
|
+
"null",
|
|
1071
|
+
"return",
|
|
1072
|
+
"super",
|
|
1073
|
+
"switch",
|
|
1074
|
+
"this",
|
|
1075
|
+
"throw",
|
|
1076
|
+
"true",
|
|
1077
|
+
"try",
|
|
1078
|
+
"typeof",
|
|
1079
|
+
"var",
|
|
1080
|
+
"void",
|
|
1081
|
+
"while",
|
|
1082
|
+
"with",
|
|
1083
|
+
"yield"
|
|
1084
|
+
]);
|
|
1085
|
+
function enforceProps(root, propsConfig) {
|
|
1086
|
+
const diagnostics = [];
|
|
1087
|
+
const declaredProps = /* @__PURE__ */ new Map();
|
|
1088
|
+
const usedLocal = /* @__PURE__ */ new Map();
|
|
1089
|
+
const usedNamespace = /* @__PURE__ */ new Map();
|
|
1090
|
+
const usedAny = /* @__PURE__ */ new Set();
|
|
1091
|
+
const missingReported = /* @__PURE__ */ new Set();
|
|
1092
|
+
const localStyleReported = /* @__PURE__ */ new Set();
|
|
1093
|
+
const namespaceStyleReported = /* @__PURE__ */ new Set();
|
|
1094
|
+
if (root.props?.fields) {
|
|
1095
|
+
for (const field of root.props.fields) {
|
|
1096
|
+
declaredProps.set(field.name, field);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const preferStyle = propsConfig.preferAccessStyle;
|
|
1100
|
+
const flagLocalStyle = !propsConfig.allowDeclaredLocals || preferStyle === "namespace";
|
|
1101
|
+
const flagNamespaceStyle = !propsConfig.allowPropsNamespace || preferStyle === "locals";
|
|
1102
|
+
const walkNodes2 = (nodes, locals) => {
|
|
1103
|
+
for (const node of nodes) {
|
|
1104
|
+
if (node.type === "Conditional") {
|
|
1105
|
+
handleConditional(node, locals);
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
if (node.type === "For") {
|
|
1109
|
+
handleFor(node, locals);
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (node.type === "Expression") {
|
|
1113
|
+
handleExpression(node.value, node.span, locals);
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (node.type === "JSXPassthrough") {
|
|
1117
|
+
handleExpression(node.expression, node.span, locals);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (node.type === "Text") {
|
|
1121
|
+
handleText(node.parts, locals);
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (node.type === "Element") {
|
|
1125
|
+
handleElement(node, locals);
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (node.type === "Component") {
|
|
1129
|
+
handleComponent(node, locals);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
const handleConditional = (node, locals) => {
|
|
1135
|
+
for (const branch of node.branches) {
|
|
1136
|
+
if (branch.test) {
|
|
1137
|
+
handleExpression(branch.test, branch.testSpan, locals);
|
|
1138
|
+
}
|
|
1139
|
+
walkNodes2(branch.body, locals);
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
const handleFor = (node, locals) => {
|
|
1143
|
+
handleExpression(node.arrayExpr, node.arrayExprSpan, locals);
|
|
1144
|
+
const nextLocals = new Set(locals);
|
|
1145
|
+
nextLocals.add(node.itemName);
|
|
1146
|
+
walkNodes2(node.body, nextLocals);
|
|
1147
|
+
};
|
|
1148
|
+
const handleElement = (node, locals) => {
|
|
1149
|
+
if (node.guard) {
|
|
1150
|
+
handleExpression(node.guard, node.guardSpan, locals);
|
|
1151
|
+
}
|
|
1152
|
+
handleAttributes(node.attributes, locals);
|
|
1153
|
+
walkNodes2(node.children, locals);
|
|
1154
|
+
};
|
|
1155
|
+
const handleComponent = (node, locals) => {
|
|
1156
|
+
if (node.guard) {
|
|
1157
|
+
handleExpression(node.guard, node.guardSpan, locals);
|
|
1158
|
+
}
|
|
1159
|
+
handleAttributes(node.attributes, locals);
|
|
1160
|
+
if (node.slots) {
|
|
1161
|
+
for (const slot of node.slots) {
|
|
1162
|
+
walkNodes2(slot.children, locals);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
walkNodes2(node.children, locals);
|
|
1166
|
+
};
|
|
1167
|
+
const handleText = (parts, locals) => {
|
|
1168
|
+
for (const part of parts) {
|
|
1169
|
+
if (part.type === "expr") {
|
|
1170
|
+
handleExpression(part.value, part.span, locals);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
const handleAttributes = (attributes, locals) => {
|
|
1175
|
+
for (const attr of attributes) {
|
|
1176
|
+
if (!attr.value) continue;
|
|
1177
|
+
const trimmed = attr.value.trim();
|
|
1178
|
+
if (!trimmed || trimmed.startsWith("'") || trimmed.startsWith('"')) {
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
handleExpression(trimmed, void 0, locals);
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
const handleExpression = (expression, span, locals) => {
|
|
1185
|
+
const occurrences = scanExpression(expression);
|
|
1186
|
+
for (const occurrence of occurrences) {
|
|
1187
|
+
const name = occurrence.name;
|
|
1188
|
+
if (occurrence.kind === "local" && locals.has(name)) {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (shouldIgnoreIdentifier2(name)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const usageSpan = span ? offsetSpan(span, occurrence.index, occurrence.length) : void 0;
|
|
1195
|
+
if (occurrence.kind === "namespace") {
|
|
1196
|
+
recordUsage(usedNamespace, name, usageSpan);
|
|
1197
|
+
usedAny.add(name);
|
|
1198
|
+
if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
|
|
1199
|
+
const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
|
|
1200
|
+
if (severity) {
|
|
1201
|
+
diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
|
|
1202
|
+
missingReported.add(name);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (flagNamespaceStyle && !namespaceStyleReported.has(name)) {
|
|
1206
|
+
const severity = levelToSeverity2(propsConfig.diagnostics.style);
|
|
1207
|
+
if (severity) {
|
|
1208
|
+
diagnostics.push(
|
|
1209
|
+
createStyleDiagnostic(
|
|
1210
|
+
name,
|
|
1211
|
+
"namespace",
|
|
1212
|
+
severity,
|
|
1213
|
+
usageSpan,
|
|
1214
|
+
propsConfig.allowPropsNamespace
|
|
1215
|
+
)
|
|
1216
|
+
);
|
|
1217
|
+
namespaceStyleReported.add(name);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
recordUsage(usedLocal, name, usageSpan);
|
|
1223
|
+
usedAny.add(name);
|
|
1224
|
+
if (propsConfig.requireDeclarationForLocals && !declaredProps.has(name) && !missingReported.has(name)) {
|
|
1225
|
+
const severity = levelToSeverity2(propsConfig.diagnostics.missingDeclaration);
|
|
1226
|
+
if (severity) {
|
|
1227
|
+
diagnostics.push(createMissingDeclarationDiagnostic(name, severity, usageSpan));
|
|
1228
|
+
missingReported.add(name);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (flagLocalStyle && !localStyleReported.has(name)) {
|
|
1232
|
+
const severity = levelToSeverity2(propsConfig.diagnostics.style);
|
|
1233
|
+
if (severity) {
|
|
1234
|
+
diagnostics.push(
|
|
1235
|
+
createStyleDiagnostic(name, "local", severity, usageSpan, propsConfig.allowDeclaredLocals)
|
|
1236
|
+
);
|
|
1237
|
+
localStyleReported.add(name);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
walkNodes2(root.children, /* @__PURE__ */ new Set());
|
|
1243
|
+
if (root.props?.fields) {
|
|
1244
|
+
for (const field of root.props.fields) {
|
|
1245
|
+
if (!usedAny.has(field.name)) {
|
|
1246
|
+
const severity = levelToSeverity2(propsConfig.diagnostics.unusedDeclaration);
|
|
1247
|
+
if (severity) {
|
|
1248
|
+
diagnostics.push({
|
|
1249
|
+
severity,
|
|
1250
|
+
code: "props.unusedDeclaration",
|
|
1251
|
+
message: `Prop "${field.name}" is declared but never used.`,
|
|
1252
|
+
span: field.span
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (propsConfig.requirePropsBlockWhen.enabled && !root.props && usedAny.size >= propsConfig.requirePropsBlockWhen.minUniquePropsUsed) {
|
|
1259
|
+
const severity = levelToSeverity2(propsConfig.requirePropsBlockWhen.severity);
|
|
1260
|
+
if (severity) {
|
|
1261
|
+
diagnostics.push({
|
|
1262
|
+
severity,
|
|
1263
|
+
code: "props.block.recommendedOrRequired",
|
|
1264
|
+
message: `Props block recommended: ${usedAny.size} unique prop${usedAny.size === 1 ? "" : "s"} used.`
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return diagnostics;
|
|
1269
|
+
}
|
|
1270
|
+
function createMissingDeclarationDiagnostic(name, severity, span) {
|
|
1271
|
+
return {
|
|
1272
|
+
severity,
|
|
1273
|
+
code: "props.missingDeclaration",
|
|
1274
|
+
message: `Prop \`${name}\` is used but not declared in \`#props\`.`,
|
|
1275
|
+
span,
|
|
1276
|
+
data: {
|
|
1277
|
+
kind: "addPropDeclaration",
|
|
1278
|
+
propName: name
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
function createStyleDiagnostic(name, kind, severity, span, allowed) {
|
|
1283
|
+
if (kind === "namespace") {
|
|
1284
|
+
const message2 = allowed ? `props.${name} is allowed but not preferred; use "${name}" instead.` : `props.${name} is disabled; use "${name}" instead.`;
|
|
1285
|
+
return {
|
|
1286
|
+
severity,
|
|
1287
|
+
code: "props.style.nonPreferred",
|
|
1288
|
+
message: message2,
|
|
1289
|
+
span
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const message = allowed ? `"${name}" is allowed but not preferred; use props.${name} instead.` : `"${name}" is disabled; use props.${name} instead.`;
|
|
1293
|
+
return {
|
|
1294
|
+
severity,
|
|
1295
|
+
code: "props.style.nonPreferred",
|
|
1296
|
+
message,
|
|
1297
|
+
span
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
function recordUsage(map, name, span) {
|
|
1301
|
+
const existing = map.get(name);
|
|
1302
|
+
if (existing) {
|
|
1303
|
+
existing.count += 1;
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
map.set(name, { count: 1, span });
|
|
1307
|
+
}
|
|
1308
|
+
function scanExpression(expression) {
|
|
1309
|
+
const occurrences = [];
|
|
1310
|
+
let i = 0;
|
|
1311
|
+
let state = "code";
|
|
1312
|
+
while (i < expression.length) {
|
|
1313
|
+
const ch = expression[i];
|
|
1314
|
+
if (state === "code") {
|
|
1315
|
+
if (ch === "'" || ch === '"') {
|
|
1316
|
+
state = ch === "'" ? "single" : "double";
|
|
1317
|
+
i++;
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (ch === "`") {
|
|
1321
|
+
state = "template";
|
|
1322
|
+
i++;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (ch === "/" && expression[i + 1] === "/") {
|
|
1326
|
+
state = "line";
|
|
1327
|
+
i += 2;
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
if (ch === "/" && expression[i + 1] === "*") {
|
|
1331
|
+
state = "block";
|
|
1332
|
+
i += 2;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (isIdentifierStart2(ch)) {
|
|
1336
|
+
const start = i;
|
|
1337
|
+
i++;
|
|
1338
|
+
while (i < expression.length && isIdentifierPart2(expression[i])) {
|
|
1339
|
+
i++;
|
|
1340
|
+
}
|
|
1341
|
+
const name = expression.slice(start, i);
|
|
1342
|
+
const prevNonSpace = findPreviousNonSpace2(expression, start - 1);
|
|
1343
|
+
if (name === "props" && prevNonSpace !== ".") {
|
|
1344
|
+
const namespace = readNamespaceAccess(expression, i);
|
|
1345
|
+
if (namespace) {
|
|
1346
|
+
occurrences.push({
|
|
1347
|
+
name: namespace.name,
|
|
1348
|
+
kind: "namespace",
|
|
1349
|
+
index: namespace.index,
|
|
1350
|
+
length: namespace.name.length
|
|
1351
|
+
});
|
|
1352
|
+
i = namespace.endIndex;
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (prevNonSpace !== ".") {
|
|
1357
|
+
occurrences.push({ name, kind: "local", index: start, length: name.length });
|
|
1358
|
+
}
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
i++;
|
|
675
1362
|
continue;
|
|
676
1363
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1364
|
+
if (state === "line") {
|
|
1365
|
+
if (ch === "\n") {
|
|
1366
|
+
state = "code";
|
|
1367
|
+
}
|
|
1368
|
+
i++;
|
|
1369
|
+
continue;
|
|
680
1370
|
}
|
|
681
|
-
if (
|
|
682
|
-
|
|
1371
|
+
if (state === "block") {
|
|
1372
|
+
if (ch === "*" && expression[i + 1] === "/") {
|
|
1373
|
+
state = "code";
|
|
1374
|
+
i += 2;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
i++;
|
|
1378
|
+
continue;
|
|
683
1379
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
"
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
lineOffset
|
|
695
|
-
);
|
|
696
|
-
level = top.level + 1;
|
|
1380
|
+
if (state === "single") {
|
|
1381
|
+
if (ch === "\\") {
|
|
1382
|
+
i += 2;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (ch === "'") {
|
|
1386
|
+
state = "code";
|
|
1387
|
+
}
|
|
1388
|
+
i++;
|
|
1389
|
+
continue;
|
|
697
1390
|
}
|
|
698
|
-
|
|
699
|
-
|
|
1391
|
+
if (state === "double") {
|
|
1392
|
+
if (ch === "\\") {
|
|
1393
|
+
i += 2;
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
if (ch === '"') {
|
|
1397
|
+
state = "code";
|
|
1398
|
+
}
|
|
1399
|
+
i++;
|
|
1400
|
+
continue;
|
|
700
1401
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1402
|
+
if (state === "template") {
|
|
1403
|
+
if (ch === "\\") {
|
|
1404
|
+
i += 2;
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
if (ch === "`") {
|
|
1408
|
+
state = "code";
|
|
1409
|
+
i++;
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
i++;
|
|
1413
|
+
continue;
|
|
706
1414
|
}
|
|
707
|
-
|
|
708
|
-
|
|
1415
|
+
}
|
|
1416
|
+
return occurrences;
|
|
1417
|
+
}
|
|
1418
|
+
function readNamespaceAccess(expression, startIndex) {
|
|
1419
|
+
let i = startIndex;
|
|
1420
|
+
while (i < expression.length && /\s/.test(expression[i])) {
|
|
1421
|
+
i++;
|
|
1422
|
+
}
|
|
1423
|
+
if (expression[i] === "?") {
|
|
1424
|
+
if (expression[i + 1] !== ".") {
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
i += 2;
|
|
1428
|
+
} else if (expression[i] === ".") {
|
|
1429
|
+
i++;
|
|
1430
|
+
} else {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
while (i < expression.length && /\s/.test(expression[i])) {
|
|
1434
|
+
i++;
|
|
1435
|
+
}
|
|
1436
|
+
if (!isIdentifierStart2(expression[i])) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
const propStart = i;
|
|
1440
|
+
i++;
|
|
1441
|
+
while (i < expression.length && isIdentifierPart2(expression[i])) {
|
|
1442
|
+
i++;
|
|
1443
|
+
}
|
|
1444
|
+
return {
|
|
1445
|
+
name: expression.slice(propStart, i),
|
|
1446
|
+
index: propStart,
|
|
1447
|
+
endIndex: i
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function findPreviousNonSpace2(text, index) {
|
|
1451
|
+
for (let i = index; i >= 0; i--) {
|
|
1452
|
+
const ch = text[i];
|
|
1453
|
+
if (!/\s/.test(ch)) {
|
|
1454
|
+
return ch;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
function isIdentifierStart2(ch) {
|
|
1460
|
+
return /[A-Za-z_$]/.test(ch);
|
|
1461
|
+
}
|
|
1462
|
+
function isIdentifierPart2(ch) {
|
|
1463
|
+
return /[A-Za-z0-9_$]/.test(ch);
|
|
1464
|
+
}
|
|
1465
|
+
function shouldIgnoreIdentifier2(name) {
|
|
1466
|
+
return IGNORED_IDENTIFIERS2.has(name) || RESERVED_KEYWORDS2.has(name);
|
|
1467
|
+
}
|
|
1468
|
+
function levelToSeverity2(level) {
|
|
1469
|
+
if (level === "off") {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
if (level === "error") {
|
|
1473
|
+
return "error";
|
|
1474
|
+
}
|
|
1475
|
+
return "warning";
|
|
1476
|
+
}
|
|
1477
|
+
function offsetSpan(base, index, length) {
|
|
1478
|
+
const startOffset = base.start.offset + index;
|
|
1479
|
+
const startCol = base.start.col + index;
|
|
1480
|
+
return {
|
|
1481
|
+
start: {
|
|
1482
|
+
line: base.start.line,
|
|
1483
|
+
col: startCol,
|
|
1484
|
+
offset: startOffset
|
|
1485
|
+
},
|
|
1486
|
+
end: {
|
|
1487
|
+
line: base.start.line,
|
|
1488
|
+
col: startCol + length,
|
|
1489
|
+
offset: startOffset + length
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/parser.ts
|
|
1495
|
+
var TEMPLATE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
|
1496
|
+
function getIndentLevel(line) {
|
|
1497
|
+
const match = line.match(/^\s*/);
|
|
1498
|
+
return match ? match[0].length / 2 : 0;
|
|
1499
|
+
}
|
|
1500
|
+
function getIdValueSpan(lineContent, indent, lineNumber, lineOffset, tokenLength, valueLength) {
|
|
1501
|
+
if (valueLength <= 0) {
|
|
1502
|
+
return void 0;
|
|
1503
|
+
}
|
|
1504
|
+
let cursor = tokenLength;
|
|
1505
|
+
while (cursor < lineContent.length && /\s/.test(lineContent[cursor])) {
|
|
1506
|
+
cursor++;
|
|
1507
|
+
}
|
|
1508
|
+
if (lineContent[cursor] === ":" || lineContent[cursor] === "=") {
|
|
1509
|
+
cursor++;
|
|
1510
|
+
while (cursor < lineContent.length && /\s/.test(lineContent[cursor])) {
|
|
1511
|
+
cursor++;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const column = indent + cursor + 1;
|
|
1515
|
+
return createSpan(lineNumber, column, valueLength, lineOffset);
|
|
1516
|
+
}
|
|
1517
|
+
function parse(source, options = {}) {
|
|
1518
|
+
const diagnostics = [];
|
|
1519
|
+
const templates = [];
|
|
1520
|
+
const normalized = source.replace(/\r\n?/g, "\n");
|
|
1521
|
+
const lines = normalized.split("\n");
|
|
1522
|
+
const lineOffsets = buildLineOffsets(lines);
|
|
1523
|
+
let currentHeader = null;
|
|
1524
|
+
let sawIdBlock = false;
|
|
1525
|
+
const seenIds = /* @__PURE__ */ new Map();
|
|
1526
|
+
const finalizeTemplate = (endIndex) => {
|
|
1527
|
+
if (!currentHeader) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const result = parseTemplateBlock(lines, lineOffsets, currentHeader.bodyStartIndex, endIndex, options);
|
|
1531
|
+
const prefixedDiagnostics = prefixDiagnostics(result.diagnostics, currentHeader.id);
|
|
1532
|
+
const unit = {
|
|
1533
|
+
id: currentHeader.id,
|
|
1534
|
+
rawId: currentHeader.rawId,
|
|
1535
|
+
span: currentHeader.span,
|
|
1536
|
+
ast: result.root,
|
|
1537
|
+
diagnostics: prefixedDiagnostics
|
|
1538
|
+
};
|
|
1539
|
+
templates.push(unit);
|
|
1540
|
+
diagnostics.push(...prefixedDiagnostics);
|
|
1541
|
+
currentHeader = null;
|
|
1542
|
+
};
|
|
1543
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1544
|
+
const rawLine = lines[i];
|
|
1545
|
+
const lineNumber = i + 1;
|
|
1546
|
+
const lineOffset = lineOffsets[i];
|
|
1547
|
+
if (/^\s*$/.test(rawLine)) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const indentMatch = rawLine.match(/^\s*/) ?? [""];
|
|
1551
|
+
const indent = indentMatch[0].length;
|
|
1552
|
+
const lineContent = rawLine.slice(indent);
|
|
1553
|
+
const trimmed = lineContent.trimEnd();
|
|
1554
|
+
const idMatch = trimmed.match(/^#id\b(.*)$/);
|
|
1555
|
+
if (idMatch) {
|
|
1556
|
+
if (indent !== 0) {
|
|
709
1557
|
pushDiag(
|
|
710
1558
|
diagnostics,
|
|
711
|
-
"
|
|
712
|
-
"
|
|
1559
|
+
"COLLIE701",
|
|
1560
|
+
"#id directives must appear at the top level.",
|
|
713
1561
|
lineNumber,
|
|
714
1562
|
indent + 1,
|
|
715
1563
|
lineOffset,
|
|
716
1564
|
trimmed.length
|
|
717
1565
|
);
|
|
718
|
-
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
finalizeTemplate(i);
|
|
1569
|
+
sawIdBlock = true;
|
|
1570
|
+
const remainderRaw = idMatch[1] ?? "";
|
|
1571
|
+
if (remainderRaw && !/^[\s:=]/.test(remainderRaw)) {
|
|
719
1572
|
pushDiag(
|
|
720
1573
|
diagnostics,
|
|
721
|
-
"
|
|
722
|
-
|
|
1574
|
+
"COLLIE702",
|
|
1575
|
+
'Invalid #id directive syntax. Use "#id <id>".',
|
|
723
1576
|
lineNumber,
|
|
724
1577
|
indent + 1,
|
|
725
1578
|
lineOffset,
|
|
726
1579
|
trimmed.length
|
|
727
1580
|
);
|
|
728
|
-
} else {
|
|
729
|
-
if (!root.classAliases) {
|
|
730
|
-
root.classAliases = { aliases: [] };
|
|
731
|
-
}
|
|
732
|
-
classesBlockLevel = level;
|
|
733
1581
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1582
|
+
let valuePart = remainderRaw.trim();
|
|
1583
|
+
if (valuePart.startsWith("=") || valuePart.startsWith(":")) {
|
|
1584
|
+
valuePart = valuePart.slice(1).trim();
|
|
1585
|
+
}
|
|
1586
|
+
const valueSpan = getIdValueSpan(
|
|
1587
|
+
lineContent,
|
|
1588
|
+
indent,
|
|
1589
|
+
lineNumber,
|
|
1590
|
+
lineOffset,
|
|
1591
|
+
"#id".length,
|
|
1592
|
+
valuePart.length
|
|
1593
|
+
);
|
|
1594
|
+
const valueColumn = valueSpan?.start.col ?? indent + 1;
|
|
1595
|
+
const valueLength = valueSpan ? valuePart.length : trimmed.length;
|
|
1596
|
+
if (!valuePart) {
|
|
738
1597
|
pushDiag(
|
|
739
1598
|
diagnostics,
|
|
740
|
-
"
|
|
741
|
-
"
|
|
1599
|
+
"COLLIE702",
|
|
1600
|
+
"#id directives must specify an identifier value.",
|
|
742
1601
|
lineNumber,
|
|
743
|
-
|
|
1602
|
+
valueColumn,
|
|
744
1603
|
lineOffset,
|
|
745
|
-
|
|
1604
|
+
valueLength
|
|
746
1605
|
);
|
|
747
|
-
} else if (
|
|
1606
|
+
} else if (!TEMPLATE_ID_PATTERN.test(valuePart)) {
|
|
748
1607
|
pushDiag(
|
|
749
1608
|
diagnostics,
|
|
750
|
-
"
|
|
751
|
-
|
|
1609
|
+
"COLLIE702",
|
|
1610
|
+
'Invalid #id value. IDs must match "^[A-Za-z][A-Za-z0-9._-]*$".',
|
|
752
1611
|
lineNumber,
|
|
753
|
-
|
|
1612
|
+
valueColumn,
|
|
1613
|
+
lineOffset,
|
|
1614
|
+
valueLength
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
if (valuePart && TEMPLATE_ID_PATTERN.test(valuePart)) {
|
|
1618
|
+
const previous = seenIds.get(valuePart);
|
|
1619
|
+
if (previous) {
|
|
1620
|
+
const previousLine = previous.start.line;
|
|
1621
|
+
pushDiag(
|
|
1622
|
+
diagnostics,
|
|
1623
|
+
"COLLIE703",
|
|
1624
|
+
`Duplicate #id "${valuePart}" (first declared on line ${previousLine}).`,
|
|
1625
|
+
lineNumber,
|
|
1626
|
+
valueColumn,
|
|
1627
|
+
lineOffset,
|
|
1628
|
+
valueLength
|
|
1629
|
+
);
|
|
1630
|
+
} else {
|
|
1631
|
+
seenIds.set(valuePart, valueSpan);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
currentHeader = {
|
|
1635
|
+
id: valuePart,
|
|
1636
|
+
rawId: valuePart,
|
|
1637
|
+
span: valueSpan,
|
|
1638
|
+
bodyStartIndex: i + 1
|
|
1639
|
+
};
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (!sawIdBlock && !currentHeader) {
|
|
1643
|
+
pushDiag(
|
|
1644
|
+
diagnostics,
|
|
1645
|
+
"COLLIE701",
|
|
1646
|
+
"Content before the first #id block is not allowed.",
|
|
1647
|
+
lineNumber,
|
|
1648
|
+
indent + 1,
|
|
1649
|
+
lineOffset,
|
|
1650
|
+
trimmed.length
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
finalizeTemplate(lines.length);
|
|
1655
|
+
if (!sawIdBlock) {
|
|
1656
|
+
pushDiag(
|
|
1657
|
+
diagnostics,
|
|
1658
|
+
"COLLIE701",
|
|
1659
|
+
"A .collie file must contain at least one #id block.",
|
|
1660
|
+
1,
|
|
1661
|
+
1,
|
|
1662
|
+
0
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
return { templates, diagnostics };
|
|
1666
|
+
}
|
|
1667
|
+
function parseTemplateBlock(lines, lineOffsets, startIndex, endIndex, options) {
|
|
1668
|
+
const diagnostics = [];
|
|
1669
|
+
const root = { type: "Root", children: [] };
|
|
1670
|
+
const stack = [{ node: root, level: -1 }];
|
|
1671
|
+
let propsBlockLevel = null;
|
|
1672
|
+
let classesBlockLevel = null;
|
|
1673
|
+
let sawTopLevelTemplateNode = false;
|
|
1674
|
+
const conditionalChains = /* @__PURE__ */ new Map();
|
|
1675
|
+
const branchLocations = [];
|
|
1676
|
+
let i = startIndex;
|
|
1677
|
+
while (i < endIndex) {
|
|
1678
|
+
const rawLine = lines[i];
|
|
1679
|
+
const lineNumber = i + 1;
|
|
1680
|
+
const lineOffset = lineOffsets[i];
|
|
1681
|
+
i++;
|
|
1682
|
+
if (/^\s*$/.test(rawLine)) {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
const tabIndex = rawLine.indexOf(" ");
|
|
1686
|
+
if (tabIndex !== -1) {
|
|
1687
|
+
pushDiag(
|
|
1688
|
+
diagnostics,
|
|
1689
|
+
"COLLIE001",
|
|
1690
|
+
"Tabs are not allowed; use spaces for indentation.",
|
|
1691
|
+
lineNumber,
|
|
1692
|
+
tabIndex + 1,
|
|
1693
|
+
lineOffset
|
|
1694
|
+
);
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
const indentMatch = rawLine.match(/^\s*/) ?? [""];
|
|
1698
|
+
const indent = indentMatch[0].length;
|
|
1699
|
+
const lineContent = rawLine.slice(indent);
|
|
1700
|
+
const trimmed = lineContent.trimEnd();
|
|
1701
|
+
if (indent % 2 !== 0) {
|
|
1702
|
+
pushDiag(
|
|
1703
|
+
diagnostics,
|
|
1704
|
+
"COLLIE002",
|
|
1705
|
+
"Indentation must be multiples of two spaces.",
|
|
1706
|
+
lineNumber,
|
|
1707
|
+
indent + 1,
|
|
1708
|
+
lineOffset
|
|
1709
|
+
);
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
let level = indent / 2;
|
|
1713
|
+
if (propsBlockLevel !== null && level <= propsBlockLevel) {
|
|
1714
|
+
propsBlockLevel = null;
|
|
1715
|
+
}
|
|
1716
|
+
if (classesBlockLevel !== null && level <= classesBlockLevel) {
|
|
1717
|
+
classesBlockLevel = null;
|
|
1718
|
+
}
|
|
1719
|
+
const isInPropsBlock = propsBlockLevel !== null && level > propsBlockLevel;
|
|
1720
|
+
const isInClassesBlock = classesBlockLevel !== null && level > classesBlockLevel;
|
|
1721
|
+
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
|
|
1722
|
+
stack.pop();
|
|
1723
|
+
}
|
|
1724
|
+
const parentLevel = stack[stack.length - 1].level;
|
|
1725
|
+
if (level > parentLevel + 1 && !isInPropsBlock && !isInClassesBlock) {
|
|
1726
|
+
pushDiag(
|
|
1727
|
+
diagnostics,
|
|
1728
|
+
"COLLIE003",
|
|
1729
|
+
"Indentation jumped more than one level.",
|
|
1730
|
+
lineNumber,
|
|
1731
|
+
indent + 1,
|
|
1732
|
+
lineOffset
|
|
1733
|
+
);
|
|
1734
|
+
level = parentLevel + 1;
|
|
1735
|
+
}
|
|
1736
|
+
cleanupConditionalChains(conditionalChains, level);
|
|
1737
|
+
const isElseIfLine = /^@elseIf\b/.test(trimmed);
|
|
1738
|
+
const isElseLine = /^@else\b/.test(trimmed) && !isElseIfLine;
|
|
1739
|
+
if (!isElseIfLine && !isElseLine) {
|
|
1740
|
+
conditionalChains.delete(level);
|
|
1741
|
+
}
|
|
1742
|
+
if (trimmed === "classes") {
|
|
1743
|
+
if (level !== 0) {
|
|
1744
|
+
pushDiag(
|
|
1745
|
+
diagnostics,
|
|
1746
|
+
"COLLIE301",
|
|
1747
|
+
"Classes block must be at the top level.",
|
|
1748
|
+
lineNumber,
|
|
1749
|
+
indent + 1,
|
|
1750
|
+
lineOffset,
|
|
1751
|
+
trimmed.length
|
|
1752
|
+
);
|
|
1753
|
+
} else if (sawTopLevelTemplateNode) {
|
|
1754
|
+
pushDiag(
|
|
1755
|
+
diagnostics,
|
|
1756
|
+
"COLLIE302",
|
|
1757
|
+
"Classes block must appear before any template nodes.",
|
|
1758
|
+
lineNumber,
|
|
1759
|
+
indent + 1,
|
|
1760
|
+
lineOffset,
|
|
1761
|
+
trimmed.length
|
|
1762
|
+
);
|
|
1763
|
+
} else {
|
|
1764
|
+
if (!root.classAliases) {
|
|
1765
|
+
root.classAliases = { aliases: [] };
|
|
1766
|
+
}
|
|
1767
|
+
classesBlockLevel = level;
|
|
1768
|
+
}
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if (trimmed === "props") {
|
|
1772
|
+
pushDiag(
|
|
1773
|
+
diagnostics,
|
|
1774
|
+
"COLLIE103",
|
|
1775
|
+
"`props` must be declared using `#props`.",
|
|
1776
|
+
lineNumber,
|
|
1777
|
+
indent + 1,
|
|
1778
|
+
lineOffset,
|
|
1779
|
+
trimmed.length
|
|
1780
|
+
);
|
|
1781
|
+
if (level === 0) {
|
|
1782
|
+
propsBlockLevel = level;
|
|
1783
|
+
}
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
if (trimmed === "#props") {
|
|
1787
|
+
if (level !== 0) {
|
|
1788
|
+
pushDiag(
|
|
1789
|
+
diagnostics,
|
|
1790
|
+
"COLLIE102",
|
|
1791
|
+
"#props block must be at the top level.",
|
|
1792
|
+
lineNumber,
|
|
1793
|
+
indent + 1,
|
|
1794
|
+
lineOffset,
|
|
1795
|
+
trimmed.length
|
|
1796
|
+
);
|
|
1797
|
+
} else if (root.props) {
|
|
1798
|
+
pushDiag(
|
|
1799
|
+
diagnostics,
|
|
1800
|
+
"COLLIE101",
|
|
1801
|
+
"Only one #props block is allowed per #id.",
|
|
1802
|
+
lineNumber,
|
|
1803
|
+
indent + 1,
|
|
754
1804
|
lineOffset,
|
|
755
1805
|
trimmed.length
|
|
756
1806
|
);
|
|
757
1807
|
} else {
|
|
758
1808
|
root.props = { fields: [] };
|
|
1809
|
+
}
|
|
1810
|
+
if (level === 0) {
|
|
759
1811
|
propsBlockLevel = level;
|
|
760
1812
|
}
|
|
761
1813
|
continue;
|
|
@@ -801,17 +1853,13 @@ function parse(source) {
|
|
|
801
1853
|
pushDiag(
|
|
802
1854
|
diagnostics,
|
|
803
1855
|
"COLLIE102",
|
|
804
|
-
"
|
|
1856
|
+
"#props lines must be indented two spaces under the #props header.",
|
|
805
1857
|
lineNumber,
|
|
806
1858
|
indent + 1,
|
|
807
1859
|
lineOffset
|
|
808
1860
|
);
|
|
809
1861
|
continue;
|
|
810
1862
|
}
|
|
811
|
-
const field = parsePropsField(trimmed, lineNumber, indent + 1, lineOffset, diagnostics);
|
|
812
|
-
if (field && root.props) {
|
|
813
|
-
root.props.fields.push(field);
|
|
814
|
-
}
|
|
815
1863
|
continue;
|
|
816
1864
|
}
|
|
817
1865
|
if (classesBlockLevel !== null && level > classesBlockLevel) {
|
|
@@ -848,7 +1896,10 @@ function parse(source) {
|
|
|
848
1896
|
type: "For",
|
|
849
1897
|
itemName: forHeader.itemName,
|
|
850
1898
|
arrayExpr: forHeader.arrayExpr,
|
|
851
|
-
body: []
|
|
1899
|
+
body: [],
|
|
1900
|
+
token: forHeader.token,
|
|
1901
|
+
tokenSpan: forHeader.tokenSpan,
|
|
1902
|
+
arrayExprSpan: forHeader.arrayExprSpan
|
|
852
1903
|
};
|
|
853
1904
|
addChildToParent(parent, forNode);
|
|
854
1905
|
if (parent === root) {
|
|
@@ -870,7 +1921,14 @@ function parse(source) {
|
|
|
870
1921
|
continue;
|
|
871
1922
|
}
|
|
872
1923
|
const chain = { type: "Conditional", branches: [] };
|
|
873
|
-
const branch = {
|
|
1924
|
+
const branch = {
|
|
1925
|
+
kind: "if",
|
|
1926
|
+
test: header.test,
|
|
1927
|
+
body: [],
|
|
1928
|
+
token: header.token,
|
|
1929
|
+
tokenSpan: header.tokenSpan,
|
|
1930
|
+
testSpan: header.testSpan
|
|
1931
|
+
};
|
|
874
1932
|
chain.branches.push(branch);
|
|
875
1933
|
addChildToParent(parent, chain);
|
|
876
1934
|
if (parent === root) {
|
|
@@ -937,7 +1995,14 @@ function parse(source) {
|
|
|
937
1995
|
if (!header) {
|
|
938
1996
|
continue;
|
|
939
1997
|
}
|
|
940
|
-
const branch = {
|
|
1998
|
+
const branch = {
|
|
1999
|
+
kind: "elseIf",
|
|
2000
|
+
test: header.test,
|
|
2001
|
+
body: [],
|
|
2002
|
+
token: header.token,
|
|
2003
|
+
tokenSpan: header.tokenSpan,
|
|
2004
|
+
testSpan: header.testSpan
|
|
2005
|
+
};
|
|
941
2006
|
chain.node.branches.push(branch);
|
|
942
2007
|
branchLocations.push({
|
|
943
2008
|
branch,
|
|
@@ -992,7 +2057,13 @@ function parse(source) {
|
|
|
992
2057
|
if (!header) {
|
|
993
2058
|
continue;
|
|
994
2059
|
}
|
|
995
|
-
const branch = {
|
|
2060
|
+
const branch = {
|
|
2061
|
+
kind: "else",
|
|
2062
|
+
test: void 0,
|
|
2063
|
+
body: [],
|
|
2064
|
+
token: header.token,
|
|
2065
|
+
tokenSpan: header.tokenSpan
|
|
2066
|
+
};
|
|
996
2067
|
chain.node.branches.push(branch);
|
|
997
2068
|
chain.hasElse = true;
|
|
998
2069
|
branchLocations.push({
|
|
@@ -1075,10 +2146,9 @@ function parse(source) {
|
|
|
1075
2146
|
}
|
|
1076
2147
|
if (lineContent.startsWith("=")) {
|
|
1077
2148
|
const payload = lineContent.slice(1).trim();
|
|
1078
|
-
if (payload.endsWith("(") || payload.endsWith("<") || i <
|
|
2149
|
+
if (payload.endsWith("(") || payload.endsWith("<") || i < endIndex && level < getIndentLevel(lines[i])) {
|
|
1079
2150
|
let jsxContent = payload;
|
|
1080
|
-
|
|
1081
|
-
while (i < lines.length) {
|
|
2151
|
+
while (i < endIndex) {
|
|
1082
2152
|
const nextRaw = lines[i];
|
|
1083
2153
|
const nextIndent = getIndentLevel(nextRaw);
|
|
1084
2154
|
const nextTrimmed = nextRaw.trim();
|
|
@@ -1136,7 +2206,7 @@ function parse(source) {
|
|
|
1136
2206
|
let multilineEnd = i;
|
|
1137
2207
|
if (trimmed.includes("(") && !trimmed.includes(")")) {
|
|
1138
2208
|
let parenDepth = (trimmed.match(/\(/g) || []).length - (trimmed.match(/\)/g) || []).length;
|
|
1139
|
-
while (multilineEnd <
|
|
2209
|
+
while (multilineEnd < endIndex && parenDepth > 0) {
|
|
1140
2210
|
const nextRaw = lines[multilineEnd];
|
|
1141
2211
|
multilineEnd++;
|
|
1142
2212
|
fullLine += "\n" + nextRaw;
|
|
@@ -1144,8 +2214,8 @@ function parse(source) {
|
|
|
1144
2214
|
}
|
|
1145
2215
|
i = multilineEnd;
|
|
1146
2216
|
}
|
|
1147
|
-
const
|
|
1148
|
-
if (!
|
|
2217
|
+
const elementResult = parseElementWithInfo(fullLine, lineNumber, indent + 1, lineOffset, diagnostics);
|
|
2218
|
+
if (!elementResult) {
|
|
1149
2219
|
const textNode = parseTextPayload(trimmed, lineNumber, indent + 1, lineOffset, diagnostics);
|
|
1150
2220
|
if (textNode && textNode.parts.length > 0) {
|
|
1151
2221
|
addChildToParent(parent, textNode);
|
|
@@ -1155,11 +2225,31 @@ function parse(source) {
|
|
|
1155
2225
|
}
|
|
1156
2226
|
continue;
|
|
1157
2227
|
}
|
|
2228
|
+
const element = elementResult.node;
|
|
2229
|
+
let hasIndentedAttributeLines = false;
|
|
2230
|
+
if ((element.type === "Element" || element.type === "Component") && element.children.length === 0) {
|
|
2231
|
+
const indentedAttributes = collectIndentedAttributeLines(
|
|
2232
|
+
lines,
|
|
2233
|
+
lineOffsets,
|
|
2234
|
+
i,
|
|
2235
|
+
endIndex,
|
|
2236
|
+
level,
|
|
2237
|
+
diagnostics
|
|
2238
|
+
);
|
|
2239
|
+
if (indentedAttributes.attributes.length > 0) {
|
|
2240
|
+
element.attributes.push(...indentedAttributes.attributes);
|
|
2241
|
+
hasIndentedAttributeLines = true;
|
|
2242
|
+
}
|
|
2243
|
+
i = indentedAttributes.nextIndex;
|
|
2244
|
+
}
|
|
1158
2245
|
addChildToParent(parent, element);
|
|
1159
2246
|
if (parent === root) {
|
|
1160
2247
|
sawTopLevelTemplateNode = true;
|
|
1161
2248
|
}
|
|
1162
2249
|
stack.push({ node: element, level });
|
|
2250
|
+
if (hasIndentedAttributeLines) {
|
|
2251
|
+
stack.push({ node: element, level: level + 1 });
|
|
2252
|
+
}
|
|
1163
2253
|
}
|
|
1164
2254
|
if (root.classAliases) {
|
|
1165
2255
|
validateClassAliasDefinitions(root.classAliases, diagnostics);
|
|
@@ -1178,8 +2268,33 @@ function parse(source) {
|
|
|
1178
2268
|
);
|
|
1179
2269
|
}
|
|
1180
2270
|
}
|
|
2271
|
+
if (options.dialect) {
|
|
2272
|
+
diagnostics.push(...enforceDialect(root, options.dialect));
|
|
2273
|
+
diagnostics.push(...enforceProps(root, options.dialect.props));
|
|
2274
|
+
}
|
|
1181
2275
|
return { root, diagnostics };
|
|
1182
2276
|
}
|
|
2277
|
+
function buildLineOffsets(lines) {
|
|
2278
|
+
const offsets = [];
|
|
2279
|
+
let offset = 0;
|
|
2280
|
+
for (const line of lines) {
|
|
2281
|
+
offsets.push(offset);
|
|
2282
|
+
offset += line.length + 1;
|
|
2283
|
+
}
|
|
2284
|
+
return offsets;
|
|
2285
|
+
}
|
|
2286
|
+
function prefixDiagnostics(diagnostics, templateId) {
|
|
2287
|
+
if (!templateId) {
|
|
2288
|
+
return diagnostics;
|
|
2289
|
+
}
|
|
2290
|
+
const prefix = `In template "${templateId}": `;
|
|
2291
|
+
return diagnostics.map((diag) => {
|
|
2292
|
+
if (diag.message.startsWith(prefix)) {
|
|
2293
|
+
return diag;
|
|
2294
|
+
}
|
|
2295
|
+
return { ...diag, message: `${prefix}${diag.message}` };
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
1183
2298
|
function cleanupConditionalChains(state, level) {
|
|
1184
2299
|
for (const key of Array.from(state.keys())) {
|
|
1185
2300
|
if (key > level) {
|
|
@@ -1202,21 +2317,59 @@ function isComponentNode(parent) {
|
|
|
1202
2317
|
}
|
|
1203
2318
|
function parseConditionalHeader(kind, lineContent, lineNumber, column, lineOffset, diagnostics) {
|
|
1204
2319
|
const trimmed = lineContent.trimEnd();
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
if (!match) {
|
|
2320
|
+
const token = kind === "if" ? "@if" : "@elseIf";
|
|
2321
|
+
if (!trimmed.startsWith(token)) {
|
|
1208
2322
|
pushDiag(
|
|
1209
2323
|
diagnostics,
|
|
1210
2324
|
"COLLIE201",
|
|
1211
|
-
kind === "if" ? "Invalid @if syntax. Use @if
|
|
2325
|
+
kind === "if" ? "Invalid @if syntax. Use @if <condition>." : "Invalid @elseIf syntax. Use @elseIf <condition>.",
|
|
1212
2326
|
lineNumber,
|
|
1213
2327
|
column,
|
|
1214
2328
|
lineOffset,
|
|
1215
|
-
trimmed.length ||
|
|
2329
|
+
trimmed.length || token.length
|
|
2330
|
+
);
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
const tokenSpan = createSpan(lineNumber, column, token.length, lineOffset);
|
|
2334
|
+
const remainder = trimmed.slice(token.length);
|
|
2335
|
+
if (!remainder.trim()) {
|
|
2336
|
+
pushDiag(
|
|
2337
|
+
diagnostics,
|
|
2338
|
+
"COLLIE201",
|
|
2339
|
+
kind === "if" ? "@if condition cannot be empty." : "@elseIf condition cannot be empty.",
|
|
2340
|
+
lineNumber,
|
|
2341
|
+
column,
|
|
2342
|
+
lineOffset,
|
|
2343
|
+
trimmed.length || token.length
|
|
1216
2344
|
);
|
|
1217
2345
|
return null;
|
|
1218
2346
|
}
|
|
1219
|
-
const
|
|
2347
|
+
const remainderTrimmed = remainder.trimStart();
|
|
2348
|
+
const usesParens = remainderTrimmed.startsWith("(");
|
|
2349
|
+
let testRaw = "";
|
|
2350
|
+
let remainderRaw = "";
|
|
2351
|
+
if (usesParens) {
|
|
2352
|
+
const openIndex = trimmed.indexOf("(", token.length);
|
|
2353
|
+
const closeIndex = trimmed.lastIndexOf(")");
|
|
2354
|
+
if (openIndex === -1 || closeIndex <= openIndex) {
|
|
2355
|
+
pushDiag(
|
|
2356
|
+
diagnostics,
|
|
2357
|
+
"COLLIE201",
|
|
2358
|
+
kind === "if" ? "Invalid @if syntax. Use @if <condition>." : "Invalid @elseIf syntax. Use @elseIf <condition>.",
|
|
2359
|
+
lineNumber,
|
|
2360
|
+
column,
|
|
2361
|
+
lineOffset,
|
|
2362
|
+
trimmed.length || token.length
|
|
2363
|
+
);
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
testRaw = trimmed.slice(openIndex + 1, closeIndex);
|
|
2367
|
+
remainderRaw = trimmed.slice(closeIndex + 1);
|
|
2368
|
+
} else {
|
|
2369
|
+
testRaw = remainderTrimmed;
|
|
2370
|
+
remainderRaw = "";
|
|
2371
|
+
}
|
|
2372
|
+
const test = testRaw.trim();
|
|
1220
2373
|
if (!test) {
|
|
1221
2374
|
pushDiag(
|
|
1222
2375
|
diagnostics,
|
|
@@ -1229,7 +2382,9 @@ function parseConditionalHeader(kind, lineContent, lineNumber, column, lineOffse
|
|
|
1229
2382
|
);
|
|
1230
2383
|
return null;
|
|
1231
2384
|
}
|
|
1232
|
-
const
|
|
2385
|
+
const testLeadingWhitespace = testRaw.length - testRaw.trimStart().length;
|
|
2386
|
+
const testColumn = usesParens ? column + trimmed.indexOf("(", token.length) + 1 + testLeadingWhitespace : column + token.length + (remainder.length - remainder.trimStart().length) + testLeadingWhitespace;
|
|
2387
|
+
const testSpan = createSpan(lineNumber, testColumn, test.length, lineOffset);
|
|
1233
2388
|
const inlineBody = remainderRaw.trim();
|
|
1234
2389
|
const remainderOffset = trimmed.length - remainderRaw.length;
|
|
1235
2390
|
const leadingWhitespace = remainderRaw.length - inlineBody.length;
|
|
@@ -1238,7 +2393,10 @@ function parseConditionalHeader(kind, lineContent, lineNumber, column, lineOffse
|
|
|
1238
2393
|
test,
|
|
1239
2394
|
inlineBody: inlineBody.length ? inlineBody : void 0,
|
|
1240
2395
|
inlineColumn,
|
|
1241
|
-
directiveLength: trimmed.length || 3
|
|
2396
|
+
directiveLength: trimmed.length || 3,
|
|
2397
|
+
token,
|
|
2398
|
+
tokenSpan,
|
|
2399
|
+
testSpan
|
|
1242
2400
|
};
|
|
1243
2401
|
}
|
|
1244
2402
|
function parseElseHeader(lineContent, lineNumber, column, lineOffset, diagnostics) {
|
|
@@ -1256,6 +2414,8 @@ function parseElseHeader(lineContent, lineNumber, column, lineOffset, diagnostic
|
|
|
1256
2414
|
);
|
|
1257
2415
|
return null;
|
|
1258
2416
|
}
|
|
2417
|
+
const token = "@else";
|
|
2418
|
+
const tokenSpan = createSpan(lineNumber, column, token.length, lineOffset);
|
|
1259
2419
|
const remainderRaw = match[1] ?? "";
|
|
1260
2420
|
const inlineBody = remainderRaw.trim();
|
|
1261
2421
|
const remainderOffset = trimmed.length - remainderRaw.length;
|
|
@@ -1264,7 +2424,9 @@ function parseElseHeader(lineContent, lineNumber, column, lineOffset, diagnostic
|
|
|
1264
2424
|
return {
|
|
1265
2425
|
inlineBody: inlineBody.length ? inlineBody : void 0,
|
|
1266
2426
|
inlineColumn,
|
|
1267
|
-
directiveLength: trimmed.length || 4
|
|
2427
|
+
directiveLength: trimmed.length || 4,
|
|
2428
|
+
token,
|
|
2429
|
+
tokenSpan
|
|
1268
2430
|
};
|
|
1269
2431
|
}
|
|
1270
2432
|
function parseForHeader(lineContent, lineNumber, column, lineOffset, diagnostics) {
|
|
@@ -1282,6 +2444,8 @@ function parseForHeader(lineContent, lineNumber, column, lineOffset, diagnostics
|
|
|
1282
2444
|
);
|
|
1283
2445
|
return null;
|
|
1284
2446
|
}
|
|
2447
|
+
const token = "@for";
|
|
2448
|
+
const tokenSpan = createSpan(lineNumber, column, token.length, lineOffset);
|
|
1285
2449
|
const itemName = match[1];
|
|
1286
2450
|
const arrayExprRaw = match[2];
|
|
1287
2451
|
if (!itemName || !arrayExprRaw) {
|
|
@@ -1309,7 +2473,11 @@ function parseForHeader(lineContent, lineNumber, column, lineOffset, diagnostics
|
|
|
1309
2473
|
);
|
|
1310
2474
|
return null;
|
|
1311
2475
|
}
|
|
1312
|
-
|
|
2476
|
+
const arrayExprLeadingWhitespace = arrayExprRaw.length - arrayExprRaw.trimStart().length;
|
|
2477
|
+
const arrayExprStart = trimmed.length - arrayExprRaw.length;
|
|
2478
|
+
const arrayExprColumn = column + arrayExprStart + arrayExprLeadingWhitespace;
|
|
2479
|
+
const arrayExprSpan = createSpan(lineNumber, arrayExprColumn, arrayExpr.length, lineOffset);
|
|
2480
|
+
return { itemName, arrayExpr, token, tokenSpan, arrayExprSpan };
|
|
1313
2481
|
}
|
|
1314
2482
|
function parseInlineNode(source, lineNumber, column, lineOffset, diagnostics) {
|
|
1315
2483
|
const trimmed = source.trim();
|
|
@@ -1417,7 +2585,14 @@ function parseTextPayload(payload, lineNumber, payloadColumn, lineOffset, diagno
|
|
|
1417
2585
|
exprEnd2 - exprStart2
|
|
1418
2586
|
);
|
|
1419
2587
|
} else {
|
|
1420
|
-
|
|
2588
|
+
const innerRaw = payload.slice(exprStart2 + 2, exprEnd2);
|
|
2589
|
+
const leadingWhitespace = innerRaw.length - innerRaw.trimStart().length;
|
|
2590
|
+
const exprColumn = payloadColumn + exprStart2 + 2 + leadingWhitespace;
|
|
2591
|
+
parts.push({
|
|
2592
|
+
type: "expr",
|
|
2593
|
+
value: inner2,
|
|
2594
|
+
span: createSpan(lineNumber, exprColumn, inner2.length, lineOffset)
|
|
2595
|
+
});
|
|
1421
2596
|
}
|
|
1422
2597
|
cursor = exprEnd2 + 2;
|
|
1423
2598
|
continue;
|
|
@@ -1448,7 +2623,14 @@ function parseTextPayload(payload, lineNumber, payloadColumn, lineOffset, diagno
|
|
|
1448
2623
|
exprEnd - exprStart
|
|
1449
2624
|
);
|
|
1450
2625
|
} else {
|
|
1451
|
-
|
|
2626
|
+
const innerRaw = payload.slice(exprStart + 1, exprEnd);
|
|
2627
|
+
const leadingWhitespace = innerRaw.length - innerRaw.trimStart().length;
|
|
2628
|
+
const exprColumn = payloadColumn + exprStart + 1 + leadingWhitespace;
|
|
2629
|
+
parts.push({
|
|
2630
|
+
type: "expr",
|
|
2631
|
+
value: inner,
|
|
2632
|
+
span: createSpan(lineNumber, exprColumn, inner.length, lineOffset)
|
|
2633
|
+
});
|
|
1452
2634
|
}
|
|
1453
2635
|
cursor = exprEnd + 1;
|
|
1454
2636
|
continue;
|
|
@@ -1523,474 +2705,1529 @@ function parseExpressionLine(line, lineNumber, column, lineOffset, diagnostics)
|
|
|
1523
2705
|
);
|
|
1524
2706
|
return null;
|
|
1525
2707
|
}
|
|
1526
|
-
|
|
2708
|
+
const innerRaw = trimmed.slice(2, closeIndex);
|
|
2709
|
+
const leadingWhitespace = innerRaw.length - innerRaw.trimStart().length;
|
|
2710
|
+
const exprColumn = column + 2 + leadingWhitespace;
|
|
2711
|
+
return {
|
|
2712
|
+
type: "Expression",
|
|
2713
|
+
value: inner,
|
|
2714
|
+
span: createSpan(lineNumber, exprColumn, inner.length, lineOffset)
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
function parseJSXPassthrough(line, lineNumber, column, lineOffset, diagnostics) {
|
|
2718
|
+
if (!line.startsWith("=")) {
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
const payload = line.slice(1).trim();
|
|
2722
|
+
if (!payload) {
|
|
2723
|
+
pushDiag(
|
|
2724
|
+
diagnostics,
|
|
2725
|
+
"COLLIE005",
|
|
2726
|
+
"JSX passthrough expression cannot be empty.",
|
|
2727
|
+
lineNumber,
|
|
2728
|
+
column,
|
|
2729
|
+
lineOffset
|
|
2730
|
+
);
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
const rawPayload = line.slice(1);
|
|
2734
|
+
const leadingWhitespace = rawPayload.length - rawPayload.trimStart().length;
|
|
2735
|
+
const exprColumn = column + 1 + leadingWhitespace;
|
|
2736
|
+
return {
|
|
2737
|
+
type: "JSXPassthrough",
|
|
2738
|
+
expression: payload,
|
|
2739
|
+
span: createSpan(lineNumber, exprColumn, payload.length, lineOffset)
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
function parseClassAliasLine(line, lineNumber, column, lineOffset, diagnostics) {
|
|
2743
|
+
const match = line.match(/^([^=]+?)\s*=\s*(.+)$/);
|
|
2744
|
+
if (!match) {
|
|
2745
|
+
pushDiag(
|
|
2746
|
+
diagnostics,
|
|
2747
|
+
"COLLIE304",
|
|
2748
|
+
"Classes lines must be in the form `name = class.tokens`.",
|
|
2749
|
+
lineNumber,
|
|
2750
|
+
column,
|
|
2751
|
+
lineOffset,
|
|
2752
|
+
Math.max(line.length, 1)
|
|
2753
|
+
);
|
|
2754
|
+
return null;
|
|
2755
|
+
}
|
|
2756
|
+
const rawName = match[1].trim();
|
|
2757
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(rawName)) {
|
|
2758
|
+
pushDiag(
|
|
2759
|
+
diagnostics,
|
|
2760
|
+
"COLLIE305",
|
|
2761
|
+
`Class alias name '${rawName}' must be a valid identifier.`,
|
|
2762
|
+
lineNumber,
|
|
2763
|
+
column,
|
|
2764
|
+
lineOffset,
|
|
2765
|
+
Math.max(rawName.length, 1)
|
|
2766
|
+
);
|
|
2767
|
+
return null;
|
|
2768
|
+
}
|
|
2769
|
+
const rhs = match[2];
|
|
2770
|
+
const rhsIndex = line.indexOf(rhs);
|
|
2771
|
+
const rhsColumn = rhsIndex >= 0 ? column + rhsIndex : column;
|
|
2772
|
+
const classes = parseAliasClasses(rhs, lineNumber, rhsColumn, lineOffset, diagnostics);
|
|
2773
|
+
if (!classes.length) {
|
|
2774
|
+
return null;
|
|
2775
|
+
}
|
|
2776
|
+
const nameIndex = line.indexOf(rawName);
|
|
2777
|
+
const nameColumn = nameIndex >= 0 ? column + nameIndex : column;
|
|
2778
|
+
const span = createSpan(lineNumber, nameColumn, rawName.length, lineOffset);
|
|
2779
|
+
return { name: rawName, classes, span };
|
|
2780
|
+
}
|
|
2781
|
+
function parseAliasClasses(rhs, lineNumber, column, lineOffset, diagnostics) {
|
|
2782
|
+
const trimmed = rhs.trim();
|
|
2783
|
+
if (!trimmed) {
|
|
2784
|
+
pushDiag(
|
|
2785
|
+
diagnostics,
|
|
2786
|
+
"COLLIE304",
|
|
2787
|
+
"Classes lines must provide one or more class tokens after '='.",
|
|
2788
|
+
lineNumber,
|
|
2789
|
+
column,
|
|
2790
|
+
lineOffset,
|
|
2791
|
+
Math.max(rhs.length, 1)
|
|
2792
|
+
);
|
|
2793
|
+
return [];
|
|
2794
|
+
}
|
|
2795
|
+
const withoutDotPrefix = trimmed.startsWith(".") ? trimmed.slice(1) : trimmed;
|
|
2796
|
+
const parts = withoutDotPrefix.split(".");
|
|
2797
|
+
const classes = [];
|
|
2798
|
+
for (const part of parts) {
|
|
2799
|
+
const token = part.trim();
|
|
2800
|
+
if (!token) {
|
|
2801
|
+
pushDiag(
|
|
2802
|
+
diagnostics,
|
|
2803
|
+
"COLLIE304",
|
|
2804
|
+
"Classes lines must provide one or more class tokens after '='.",
|
|
2805
|
+
lineNumber,
|
|
2806
|
+
column,
|
|
2807
|
+
lineOffset,
|
|
2808
|
+
Math.max(rhs.length, 1)
|
|
2809
|
+
);
|
|
2810
|
+
return [];
|
|
2811
|
+
}
|
|
2812
|
+
classes.push(token);
|
|
2813
|
+
}
|
|
2814
|
+
return classes;
|
|
2815
|
+
}
|
|
2816
|
+
function validateClassAliasDefinitions(classAliases, diagnostics) {
|
|
2817
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2818
|
+
for (const alias of classAliases.aliases) {
|
|
2819
|
+
const previous = seen.get(alias.name);
|
|
2820
|
+
if (previous) {
|
|
2821
|
+
if (alias.span) {
|
|
2822
|
+
diagnostics.push({
|
|
2823
|
+
severity: "error",
|
|
2824
|
+
code: "COLLIE306",
|
|
2825
|
+
message: `Duplicate class alias '${alias.name}'.`,
|
|
2826
|
+
span: alias.span
|
|
2827
|
+
});
|
|
2828
|
+
} else {
|
|
2829
|
+
pushDiag(diagnostics, "COLLIE306", `Duplicate class alias '${alias.name}'.`, 1, 1, 0);
|
|
2830
|
+
}
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
seen.set(alias.name, alias);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
function validateClassAliasUsages(root, diagnostics) {
|
|
2837
|
+
const defined = new Set(root.classAliases?.aliases.map((alias) => alias.name) ?? []);
|
|
2838
|
+
for (const child of root.children) {
|
|
2839
|
+
validateNodeClassAliases(child, defined, diagnostics);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
function validateNodeClassAliases(node, defined, diagnostics) {
|
|
2843
|
+
if (node.type === "Element" || node.type === "Component") {
|
|
2844
|
+
const spans = node.type === "Element" ? node.classSpans ?? [] : [];
|
|
2845
|
+
const classes = node.type === "Element" ? node.classes : [];
|
|
2846
|
+
classes.forEach((cls, index) => {
|
|
2847
|
+
const match = cls.match(/^\$([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
2848
|
+
if (!match) {
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
const aliasName = match[1];
|
|
2852
|
+
if (defined.has(aliasName)) {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
const span = spans[index];
|
|
2856
|
+
if (span) {
|
|
2857
|
+
diagnostics.push({
|
|
2858
|
+
severity: "error",
|
|
2859
|
+
code: "COLLIE307",
|
|
2860
|
+
message: `Undefined class alias '${aliasName}'.`,
|
|
2861
|
+
span
|
|
2862
|
+
});
|
|
2863
|
+
} else {
|
|
2864
|
+
pushDiag(diagnostics, "COLLIE307", `Undefined class alias '${aliasName}'.`, 1, 1, 0);
|
|
2865
|
+
}
|
|
2866
|
+
});
|
|
2867
|
+
for (const child of node.children) {
|
|
2868
|
+
validateNodeClassAliases(child, defined, diagnostics);
|
|
2869
|
+
}
|
|
2870
|
+
if (node.type === "Component" && node.slots) {
|
|
2871
|
+
for (const slot of node.slots) {
|
|
2872
|
+
for (const child of slot.children) {
|
|
2873
|
+
validateNodeClassAliases(child, defined, diagnostics);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (node.type === "Conditional") {
|
|
2880
|
+
for (const branch of node.branches) {
|
|
2881
|
+
for (const child of branch.body) {
|
|
2882
|
+
validateNodeClassAliases(child, defined, diagnostics);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
if (node.type === "For") {
|
|
2887
|
+
for (const child of node.body) {
|
|
2888
|
+
validateNodeClassAliases(child, defined, diagnostics);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
function parseAttributeTokensFromStart(source, lineNumber, column, lineOffset, diagnostics) {
|
|
2893
|
+
let remaining = source;
|
|
2894
|
+
let consumed = 0;
|
|
2895
|
+
let parsedAny = false;
|
|
2896
|
+
const attributes = [];
|
|
2897
|
+
while (remaining.length > 0) {
|
|
2898
|
+
if (!/^([A-Za-z][A-Za-z0-9_-]*)\s*=/.test(remaining)) {
|
|
2899
|
+
break;
|
|
2900
|
+
}
|
|
2901
|
+
parsedAny = true;
|
|
2902
|
+
const before = remaining;
|
|
2903
|
+
const next = parseAndAddAttribute(
|
|
2904
|
+
remaining,
|
|
2905
|
+
attributes,
|
|
2906
|
+
diagnostics,
|
|
2907
|
+
lineNumber,
|
|
2908
|
+
column + consumed,
|
|
2909
|
+
lineOffset
|
|
2910
|
+
);
|
|
2911
|
+
if (next.length === before.length) {
|
|
2912
|
+
break;
|
|
2913
|
+
}
|
|
2914
|
+
consumed += before.length - next.length;
|
|
2915
|
+
remaining = next;
|
|
2916
|
+
}
|
|
2917
|
+
if (!parsedAny) {
|
|
2918
|
+
return null;
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
attributes,
|
|
2922
|
+
rest: remaining,
|
|
2923
|
+
restColumn: column + consumed
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
function parseAttributeLine(source, lineNumber, column, lineOffset, diagnostics) {
|
|
2927
|
+
const result = parseAttributeTokensFromStart(
|
|
2928
|
+
source,
|
|
2929
|
+
lineNumber,
|
|
2930
|
+
column,
|
|
2931
|
+
lineOffset,
|
|
2932
|
+
diagnostics
|
|
2933
|
+
);
|
|
2934
|
+
if (!result || result.rest.length > 0) {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
return result.attributes;
|
|
2938
|
+
}
|
|
2939
|
+
function parseElementWithInfo(line, lineNumber, column, lineOffset, diagnostics) {
|
|
2940
|
+
let name;
|
|
2941
|
+
let cursor = 0;
|
|
2942
|
+
let hasAttributeGroup = false;
|
|
2943
|
+
if (line[cursor] === ".") {
|
|
2944
|
+
name = "div";
|
|
2945
|
+
} else {
|
|
2946
|
+
const nameMatch = line.match(/^([A-Za-z][A-Za-z0-9_]*)/);
|
|
2947
|
+
if (!nameMatch) {
|
|
2948
|
+
return null;
|
|
2949
|
+
}
|
|
2950
|
+
name = nameMatch[1];
|
|
2951
|
+
cursor = name.length;
|
|
2952
|
+
}
|
|
2953
|
+
const nextPart = line.slice(cursor);
|
|
2954
|
+
const isComponent = /^[A-Z]/.test(name);
|
|
2955
|
+
if (isComponent && nextPart.length > 0) {
|
|
2956
|
+
const trimmedNext = nextPart.trimStart();
|
|
2957
|
+
if (trimmedNext.length > 0 && !trimmedNext.startsWith("(")) {
|
|
2958
|
+
return null;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
if (cursor < line.length) {
|
|
2962
|
+
const nextChar = line[cursor];
|
|
2963
|
+
if (nextChar !== "." && nextChar !== "(" && !/\s/.test(nextChar)) {
|
|
2964
|
+
return null;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
const classes = [];
|
|
2968
|
+
const classSpans = [];
|
|
2969
|
+
if (!isComponent) {
|
|
2970
|
+
while (cursor < line.length && line[cursor] === ".") {
|
|
2971
|
+
cursor++;
|
|
2972
|
+
const classMatch = line.slice(cursor).match(/^([A-Za-z0-9_$-]+)/);
|
|
2973
|
+
if (!classMatch) {
|
|
2974
|
+
pushDiag(
|
|
2975
|
+
diagnostics,
|
|
2976
|
+
"COLLIE004",
|
|
2977
|
+
"Class names must contain only letters, numbers, underscores, hyphens, or `$` (for aliases).",
|
|
2978
|
+
lineNumber,
|
|
2979
|
+
column + cursor,
|
|
2980
|
+
lineOffset
|
|
2981
|
+
);
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
const className = classMatch[1];
|
|
2985
|
+
classes.push(className);
|
|
2986
|
+
classSpans.push(createSpan(lineNumber, column + cursor, className.length, lineOffset));
|
|
2987
|
+
cursor += className.length;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
const attributes = [];
|
|
2991
|
+
if (cursor < line.length && line[cursor] === "(") {
|
|
2992
|
+
const attrResult = parseAttributes(line, cursor, lineNumber, column, lineOffset, diagnostics);
|
|
2993
|
+
if (!attrResult) {
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
attributes.push(...attrResult.attributes);
|
|
2997
|
+
hasAttributeGroup = true;
|
|
2998
|
+
cursor = attrResult.endIndex;
|
|
2999
|
+
}
|
|
3000
|
+
let guard;
|
|
3001
|
+
let guardSpan;
|
|
3002
|
+
const guardProbeStart = cursor;
|
|
3003
|
+
while (cursor < line.length && /\s/.test(line[cursor])) {
|
|
3004
|
+
cursor++;
|
|
3005
|
+
}
|
|
3006
|
+
if (cursor < line.length && line[cursor] === "?") {
|
|
3007
|
+
const guardColumn = column + cursor;
|
|
3008
|
+
cursor++;
|
|
3009
|
+
const guardRaw = line.slice(cursor);
|
|
3010
|
+
const guardExpr = guardRaw.trim();
|
|
3011
|
+
if (!guardExpr) {
|
|
3012
|
+
pushDiag(
|
|
3013
|
+
diagnostics,
|
|
3014
|
+
"COLLIE601",
|
|
3015
|
+
"Guard expressions require a condition after '?'.",
|
|
3016
|
+
lineNumber,
|
|
3017
|
+
guardColumn,
|
|
3018
|
+
lineOffset
|
|
3019
|
+
);
|
|
3020
|
+
} else {
|
|
3021
|
+
guard = guardExpr;
|
|
3022
|
+
const leadingWhitespace = guardRaw.length - guardRaw.trimStart().length;
|
|
3023
|
+
const guardExprColumn = column + cursor + leadingWhitespace;
|
|
3024
|
+
guardSpan = createSpan(lineNumber, guardExprColumn, guardExpr.length, lineOffset);
|
|
3025
|
+
}
|
|
3026
|
+
cursor = line.length;
|
|
3027
|
+
} else {
|
|
3028
|
+
cursor = guardProbeStart;
|
|
3029
|
+
}
|
|
3030
|
+
const restRaw = line.slice(cursor);
|
|
3031
|
+
let rest = restRaw.trimStart();
|
|
3032
|
+
let restColumn = column + cursor + (restRaw.length - rest.length);
|
|
3033
|
+
const children = [];
|
|
3034
|
+
if (rest.length > 0) {
|
|
3035
|
+
const inlineAttrs = parseAttributeTokensFromStart(
|
|
3036
|
+
rest,
|
|
3037
|
+
lineNumber,
|
|
3038
|
+
restColumn,
|
|
3039
|
+
lineOffset,
|
|
3040
|
+
diagnostics
|
|
3041
|
+
);
|
|
3042
|
+
if (inlineAttrs) {
|
|
3043
|
+
attributes.push(...inlineAttrs.attributes);
|
|
3044
|
+
rest = inlineAttrs.rest;
|
|
3045
|
+
restColumn = inlineAttrs.restColumn;
|
|
3046
|
+
}
|
|
3047
|
+
if (rest.length > 0) {
|
|
3048
|
+
if (!rest.startsWith("|")) {
|
|
3049
|
+
pushDiag(
|
|
3050
|
+
diagnostics,
|
|
3051
|
+
"COLLIE004",
|
|
3052
|
+
"Inline text must start with '|'.",
|
|
3053
|
+
lineNumber,
|
|
3054
|
+
restColumn,
|
|
3055
|
+
lineOffset,
|
|
3056
|
+
Math.max(rest.length, 1)
|
|
3057
|
+
);
|
|
3058
|
+
} else {
|
|
3059
|
+
let payload = rest.slice(1);
|
|
3060
|
+
let payloadColumn = restColumn + 1;
|
|
3061
|
+
if (payload.startsWith(" ")) {
|
|
3062
|
+
payload = payload.slice(1);
|
|
3063
|
+
payloadColumn += 1;
|
|
3064
|
+
}
|
|
3065
|
+
const textNode = parseTextPayload(
|
|
3066
|
+
payload,
|
|
3067
|
+
lineNumber,
|
|
3068
|
+
payloadColumn,
|
|
3069
|
+
lineOffset,
|
|
3070
|
+
diagnostics
|
|
3071
|
+
);
|
|
3072
|
+
if (textNode) {
|
|
3073
|
+
children.push(textNode);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
if (isComponent) {
|
|
3079
|
+
const component = {
|
|
3080
|
+
type: "Component",
|
|
3081
|
+
name,
|
|
3082
|
+
attributes,
|
|
3083
|
+
children
|
|
3084
|
+
};
|
|
3085
|
+
if (guard) {
|
|
3086
|
+
component.guard = guard;
|
|
3087
|
+
component.guardSpan = guardSpan;
|
|
3088
|
+
}
|
|
3089
|
+
return { node: component, hasAttributeGroup };
|
|
3090
|
+
} else {
|
|
3091
|
+
const element = {
|
|
3092
|
+
type: "Element",
|
|
3093
|
+
name,
|
|
3094
|
+
classes,
|
|
3095
|
+
attributes,
|
|
3096
|
+
children
|
|
3097
|
+
};
|
|
3098
|
+
if (classSpans.length) {
|
|
3099
|
+
element.classSpans = classSpans;
|
|
3100
|
+
}
|
|
3101
|
+
if (guard) {
|
|
3102
|
+
element.guard = guard;
|
|
3103
|
+
element.guardSpan = guardSpan;
|
|
3104
|
+
}
|
|
3105
|
+
return { node: element, hasAttributeGroup };
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
function parseElement(line, lineNumber, column, lineOffset, diagnostics) {
|
|
3109
|
+
const result = parseElementWithInfo(line, lineNumber, column, lineOffset, diagnostics);
|
|
3110
|
+
return result ? result.node : null;
|
|
3111
|
+
}
|
|
3112
|
+
function collectIndentedAttributeLines(lines, lineOffsets, startIndex, endIndex, parentLevel, diagnostics) {
|
|
3113
|
+
const attributes = [];
|
|
3114
|
+
let index = startIndex;
|
|
3115
|
+
while (index < endIndex) {
|
|
3116
|
+
const rawLine = lines[index];
|
|
3117
|
+
if (/^\s*$/.test(rawLine)) {
|
|
3118
|
+
break;
|
|
3119
|
+
}
|
|
3120
|
+
if (rawLine.includes(" ")) {
|
|
3121
|
+
break;
|
|
3122
|
+
}
|
|
3123
|
+
const indentMatch = rawLine.match(/^\s*/) ?? [""];
|
|
3124
|
+
const indent = indentMatch[0].length;
|
|
3125
|
+
if (indent % 2 !== 0) {
|
|
3126
|
+
break;
|
|
3127
|
+
}
|
|
3128
|
+
const level = indent / 2;
|
|
3129
|
+
if (level !== parentLevel + 1) {
|
|
3130
|
+
break;
|
|
3131
|
+
}
|
|
3132
|
+
const lineContent = rawLine.slice(indent);
|
|
3133
|
+
const trimmed = lineContent.trimEnd();
|
|
3134
|
+
const leadingWhitespace = trimmed.length - trimmed.trimStart().length;
|
|
3135
|
+
const attrLine = trimmed.trimStart();
|
|
3136
|
+
if (!attrLine) {
|
|
3137
|
+
break;
|
|
3138
|
+
}
|
|
3139
|
+
const lineNumber = index + 1;
|
|
3140
|
+
const lineOffset = lineOffsets[index];
|
|
3141
|
+
const attrColumn = indent + 1 + leadingWhitespace;
|
|
3142
|
+
const lineAttributes = parseAttributeLine(
|
|
3143
|
+
attrLine,
|
|
3144
|
+
lineNumber,
|
|
3145
|
+
attrColumn,
|
|
3146
|
+
lineOffset,
|
|
3147
|
+
diagnostics
|
|
3148
|
+
);
|
|
3149
|
+
if (!lineAttributes) {
|
|
3150
|
+
break;
|
|
3151
|
+
}
|
|
3152
|
+
attributes.push(...lineAttributes);
|
|
3153
|
+
index++;
|
|
3154
|
+
}
|
|
3155
|
+
return { attributes, nextIndex: index };
|
|
3156
|
+
}
|
|
3157
|
+
function parseAttributes(line, startIndex, lineNumber, column, lineOffset, diagnostics) {
|
|
3158
|
+
if (line[startIndex] !== "(") {
|
|
3159
|
+
return null;
|
|
3160
|
+
}
|
|
3161
|
+
const attributes = [];
|
|
3162
|
+
let cursor = startIndex + 1;
|
|
3163
|
+
let depth = 1;
|
|
3164
|
+
let attrBuffer = "";
|
|
3165
|
+
while (cursor < line.length && depth > 0) {
|
|
3166
|
+
const ch = line[cursor];
|
|
3167
|
+
if (ch === "(") {
|
|
3168
|
+
depth++;
|
|
3169
|
+
attrBuffer += ch;
|
|
3170
|
+
} else if (ch === ")") {
|
|
3171
|
+
depth--;
|
|
3172
|
+
if (depth > 0) {
|
|
3173
|
+
attrBuffer += ch;
|
|
3174
|
+
}
|
|
3175
|
+
} else {
|
|
3176
|
+
attrBuffer += ch;
|
|
3177
|
+
}
|
|
3178
|
+
cursor++;
|
|
3179
|
+
}
|
|
3180
|
+
if (depth !== 0) {
|
|
3181
|
+
pushDiag(
|
|
3182
|
+
diagnostics,
|
|
3183
|
+
"COLLIE004",
|
|
3184
|
+
"Unclosed attribute parentheses.",
|
|
3185
|
+
lineNumber,
|
|
3186
|
+
column + startIndex,
|
|
3187
|
+
lineOffset
|
|
3188
|
+
);
|
|
3189
|
+
return null;
|
|
3190
|
+
}
|
|
3191
|
+
const trimmedAttrs = attrBuffer.trim();
|
|
3192
|
+
if (trimmedAttrs.length === 0) {
|
|
3193
|
+
return { attributes: [], endIndex: cursor };
|
|
3194
|
+
}
|
|
3195
|
+
const attrLines = trimmedAttrs.split("\n");
|
|
3196
|
+
let currentAttr = "";
|
|
3197
|
+
for (const attrLine of attrLines) {
|
|
3198
|
+
const trimmedLine = attrLine.trim();
|
|
3199
|
+
if (trimmedLine.length === 0) continue;
|
|
3200
|
+
const eqIndex = trimmedLine.indexOf("=");
|
|
3201
|
+
if (eqIndex > 0 && /^[A-Za-z][A-Za-z0-9_-]*\s*=/.test(trimmedLine)) {
|
|
3202
|
+
if (currentAttr) {
|
|
3203
|
+
let remaining = parseAndAddAttribute(currentAttr, attributes, diagnostics, lineNumber, column, lineOffset);
|
|
3204
|
+
while (remaining) {
|
|
3205
|
+
remaining = parseAndAddAttribute(remaining, attributes, diagnostics, lineNumber, column, lineOffset);
|
|
3206
|
+
}
|
|
3207
|
+
currentAttr = "";
|
|
3208
|
+
}
|
|
3209
|
+
currentAttr = trimmedLine;
|
|
3210
|
+
} else {
|
|
3211
|
+
if (currentAttr) {
|
|
3212
|
+
currentAttr += " " + trimmedLine;
|
|
3213
|
+
} else {
|
|
3214
|
+
currentAttr = trimmedLine;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (currentAttr) {
|
|
3219
|
+
let remaining = parseAndAddAttribute(currentAttr, attributes, diagnostics, lineNumber, column, lineOffset);
|
|
3220
|
+
while (remaining) {
|
|
3221
|
+
remaining = parseAndAddAttribute(remaining, attributes, diagnostics, lineNumber, column, lineOffset);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
return { attributes, endIndex: cursor };
|
|
3225
|
+
}
|
|
3226
|
+
function scanBraceAttributeValue(source, diagnostics, lineNumber, column, lineOffset) {
|
|
3227
|
+
if (!source.startsWith("{")) {
|
|
3228
|
+
return null;
|
|
3229
|
+
}
|
|
3230
|
+
let braceDepth = 0;
|
|
3231
|
+
let parenDepth = 0;
|
|
3232
|
+
let bracketDepth = 0;
|
|
3233
|
+
let quote = null;
|
|
3234
|
+
let escaped = false;
|
|
3235
|
+
for (let i = 0; i < source.length; i++) {
|
|
3236
|
+
const char = source[i];
|
|
3237
|
+
if (quote) {
|
|
3238
|
+
if (escaped) {
|
|
3239
|
+
escaped = false;
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
if (char === "\\") {
|
|
3243
|
+
escaped = true;
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3246
|
+
if (char === quote) {
|
|
3247
|
+
quote = null;
|
|
3248
|
+
}
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
3252
|
+
quote = char;
|
|
3253
|
+
continue;
|
|
3254
|
+
}
|
|
3255
|
+
if (char === "{") {
|
|
3256
|
+
braceDepth++;
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
if (char === "}") {
|
|
3260
|
+
braceDepth--;
|
|
3261
|
+
if (braceDepth === 0 && parenDepth === 0 && bracketDepth === 0) {
|
|
3262
|
+
return { value: source.slice(0, i + 1), rest: source.slice(i + 1).trim() };
|
|
3263
|
+
}
|
|
3264
|
+
continue;
|
|
3265
|
+
}
|
|
3266
|
+
if (char === "(") {
|
|
3267
|
+
parenDepth++;
|
|
3268
|
+
continue;
|
|
3269
|
+
}
|
|
3270
|
+
if (char === ")") {
|
|
3271
|
+
if (parenDepth > 0) {
|
|
3272
|
+
parenDepth--;
|
|
3273
|
+
}
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
if (char === "[") {
|
|
3277
|
+
bracketDepth++;
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
if (char === "]") {
|
|
3281
|
+
if (bracketDepth > 0) {
|
|
3282
|
+
bracketDepth--;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
pushDiag(
|
|
3287
|
+
diagnostics,
|
|
3288
|
+
"COLLIE004",
|
|
3289
|
+
"Unclosed brace in attribute value.",
|
|
3290
|
+
lineNumber,
|
|
3291
|
+
column,
|
|
3292
|
+
lineOffset
|
|
3293
|
+
);
|
|
3294
|
+
return null;
|
|
3295
|
+
}
|
|
3296
|
+
function parseAndAddAttribute(attrStr, attributes, diagnostics, lineNumber, column, lineOffset) {
|
|
3297
|
+
const trimmed = attrStr.trim();
|
|
3298
|
+
const nameMatch = trimmed.match(/^([A-Za-z][A-Za-z0-9_-]*)\s*=\s*/);
|
|
3299
|
+
if (nameMatch) {
|
|
3300
|
+
const attrName = nameMatch[1];
|
|
3301
|
+
const afterEquals = trimmed.slice(nameMatch[0].length);
|
|
3302
|
+
if (afterEquals.length === 0) {
|
|
3303
|
+
pushDiag(
|
|
3304
|
+
diagnostics,
|
|
3305
|
+
"COLLIE004",
|
|
3306
|
+
`Attribute ${attrName} missing value`,
|
|
3307
|
+
lineNumber,
|
|
3308
|
+
column,
|
|
3309
|
+
lineOffset
|
|
3310
|
+
);
|
|
3311
|
+
return "";
|
|
3312
|
+
}
|
|
3313
|
+
const braceValue = scanBraceAttributeValue(afterEquals, diagnostics, lineNumber, column, lineOffset);
|
|
3314
|
+
if (braceValue) {
|
|
3315
|
+
attributes.push({ name: attrName, value: braceValue.value });
|
|
3316
|
+
return braceValue.rest;
|
|
3317
|
+
}
|
|
3318
|
+
const quoteChar = afterEquals[0];
|
|
3319
|
+
if (quoteChar === '"' || quoteChar === "'") {
|
|
3320
|
+
let i = 1;
|
|
3321
|
+
let value = "";
|
|
3322
|
+
let escaped = false;
|
|
3323
|
+
while (i < afterEquals.length) {
|
|
3324
|
+
const char = afterEquals[i];
|
|
3325
|
+
if (escaped) {
|
|
3326
|
+
value += char;
|
|
3327
|
+
escaped = false;
|
|
3328
|
+
} else if (char === "\\") {
|
|
3329
|
+
escaped = true;
|
|
3330
|
+
} else if (char === quoteChar) {
|
|
3331
|
+
attributes.push({ name: attrName, value: quoteChar + value + quoteChar });
|
|
3332
|
+
return afterEquals.slice(i + 1).trim();
|
|
3333
|
+
} else {
|
|
3334
|
+
value += char;
|
|
3335
|
+
}
|
|
3336
|
+
i++;
|
|
3337
|
+
}
|
|
3338
|
+
pushDiag(
|
|
3339
|
+
diagnostics,
|
|
3340
|
+
"COLLIE004",
|
|
3341
|
+
`Unclosed quote in attribute ${attrName}`,
|
|
3342
|
+
lineNumber,
|
|
3343
|
+
column,
|
|
3344
|
+
lineOffset
|
|
3345
|
+
);
|
|
3346
|
+
return "";
|
|
3347
|
+
} else {
|
|
3348
|
+
const unquotedMatch = afterEquals.match(/^(\S+)/);
|
|
3349
|
+
if (unquotedMatch) {
|
|
3350
|
+
attributes.push({ name: attrName, value: unquotedMatch[1] });
|
|
3351
|
+
return afterEquals.slice(unquotedMatch[1].length).trim();
|
|
3352
|
+
}
|
|
3353
|
+
return "";
|
|
3354
|
+
}
|
|
3355
|
+
} else {
|
|
3356
|
+
const boolMatch = trimmed.match(/^([A-Za-z][A-Za-z0-9_-]*)(\s+.*)?$/);
|
|
3357
|
+
if (boolMatch) {
|
|
3358
|
+
attributes.push({ name: boolMatch[1], value: null });
|
|
3359
|
+
return boolMatch[2] ? boolMatch[2].trim() : "";
|
|
3360
|
+
} else {
|
|
3361
|
+
pushDiag(
|
|
3362
|
+
diagnostics,
|
|
3363
|
+
"COLLIE004",
|
|
3364
|
+
`Invalid attribute syntax: ${trimmed.slice(0, 30)}`,
|
|
3365
|
+
lineNumber,
|
|
3366
|
+
column,
|
|
3367
|
+
lineOffset
|
|
3368
|
+
);
|
|
3369
|
+
return "";
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
function pushDiag(diagnostics, code, message, line, column, lineOffset, length = 1) {
|
|
3374
|
+
diagnostics.push({
|
|
3375
|
+
severity: "error",
|
|
3376
|
+
code,
|
|
3377
|
+
message,
|
|
3378
|
+
span: createSpan(line, column, Math.max(length, 1), lineOffset)
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// src/index.ts
|
|
3383
|
+
var import_config = require("@collie-lang/config");
|
|
3384
|
+
|
|
3385
|
+
// src/fixes.ts
|
|
3386
|
+
function applyFixes(sourceText, fixes) {
|
|
3387
|
+
const normalized = [];
|
|
3388
|
+
const skipped = [];
|
|
3389
|
+
for (const fix of fixes) {
|
|
3390
|
+
const offsets = getSpanOffsets(fix.range);
|
|
3391
|
+
if (!offsets) {
|
|
3392
|
+
skipped.push(fix);
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3395
|
+
if (offsets.start < 0 || offsets.end < offsets.start || offsets.end > sourceText.length) {
|
|
3396
|
+
skipped.push(fix);
|
|
3397
|
+
continue;
|
|
3398
|
+
}
|
|
3399
|
+
normalized.push({ fix, start: offsets.start, end: offsets.end });
|
|
3400
|
+
}
|
|
3401
|
+
normalized.sort((a, b) => a.start === b.start ? a.end - b.end : a.start - b.start);
|
|
3402
|
+
const accepted = [];
|
|
3403
|
+
let currentEnd = -1;
|
|
3404
|
+
for (const item of normalized) {
|
|
3405
|
+
if (item.start < currentEnd) {
|
|
3406
|
+
skipped.push(item.fix);
|
|
3407
|
+
continue;
|
|
3408
|
+
}
|
|
3409
|
+
accepted.push(item);
|
|
3410
|
+
currentEnd = item.end;
|
|
3411
|
+
}
|
|
3412
|
+
let text = sourceText;
|
|
3413
|
+
for (let i = accepted.length - 1; i >= 0; i--) {
|
|
3414
|
+
const { start, end, fix } = accepted[i];
|
|
3415
|
+
text = `${text.slice(0, start)}${fix.replacementText}${text.slice(end)}`;
|
|
3416
|
+
}
|
|
3417
|
+
return { text, applied: accepted.map((item) => item.fix), skipped };
|
|
1527
3418
|
}
|
|
1528
|
-
function
|
|
1529
|
-
|
|
3419
|
+
function fixAllFromDiagnostics(sourceText, diagnostics) {
|
|
3420
|
+
const fixes = diagnostics.flatMap((diag) => diag.fix ? [diag.fix] : []);
|
|
3421
|
+
return applyFixes(sourceText, fixes);
|
|
3422
|
+
}
|
|
3423
|
+
function getSpanOffsets(span) {
|
|
3424
|
+
if (!span) {
|
|
1530
3425
|
return null;
|
|
1531
3426
|
}
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
diagnostics,
|
|
1536
|
-
"COLLIE005",
|
|
1537
|
-
"JSX passthrough expression cannot be empty.",
|
|
1538
|
-
lineNumber,
|
|
1539
|
-
column,
|
|
1540
|
-
lineOffset
|
|
1541
|
-
);
|
|
3427
|
+
const start = span.start?.offset;
|
|
3428
|
+
const end = span.end?.offset;
|
|
3429
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
1542
3430
|
return null;
|
|
1543
3431
|
}
|
|
1544
|
-
return {
|
|
3432
|
+
return { start, end };
|
|
1545
3433
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
);
|
|
1558
|
-
return null;
|
|
1559
|
-
}
|
|
1560
|
-
const [, name, optionalFlag, typePart] = match;
|
|
1561
|
-
const typeText = typePart.trim();
|
|
1562
|
-
if (!typeText) {
|
|
1563
|
-
pushDiag(
|
|
3434
|
+
|
|
3435
|
+
// src/format.ts
|
|
3436
|
+
function formatCollie(source, options = {}) {
|
|
3437
|
+
const indentSize = validateIndentOption(options.indent);
|
|
3438
|
+
const normalized = source.replace(/\r\n?/g, "\n");
|
|
3439
|
+
const parseResult = parse(normalized);
|
|
3440
|
+
const diagnostics = normalizeDiagnostics(parseResult.diagnostics);
|
|
3441
|
+
const hasErrors2 = diagnostics.some((diag) => diag.severity === "error");
|
|
3442
|
+
if (hasErrors2) {
|
|
3443
|
+
return {
|
|
3444
|
+
formatted: source,
|
|
1564
3445
|
diagnostics,
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
lineNumber,
|
|
1568
|
-
column,
|
|
1569
|
-
lineOffset,
|
|
1570
|
-
Math.max(line.length, 1)
|
|
1571
|
-
);
|
|
1572
|
-
return null;
|
|
3446
|
+
success: false
|
|
3447
|
+
};
|
|
1573
3448
|
}
|
|
3449
|
+
const serialized = serializeTemplates(parseResult.templates, indentSize);
|
|
3450
|
+
const formatted = ensureTrailingNewline(serialized);
|
|
1574
3451
|
return {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
3452
|
+
formatted,
|
|
3453
|
+
diagnostics,
|
|
3454
|
+
success: true
|
|
1578
3455
|
};
|
|
1579
3456
|
}
|
|
1580
|
-
function
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
3457
|
+
function normalizeDiagnostics(diagnostics) {
|
|
3458
|
+
return diagnostics.map((diag) => {
|
|
3459
|
+
if (diag.range || !diag.span) {
|
|
3460
|
+
return diag;
|
|
3461
|
+
}
|
|
3462
|
+
return {
|
|
3463
|
+
...diag,
|
|
3464
|
+
range: diag.span
|
|
3465
|
+
};
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
function validateIndentOption(indent) {
|
|
3469
|
+
if (indent === void 0) {
|
|
3470
|
+
return 2;
|
|
1593
3471
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
pushDiag(
|
|
1597
|
-
diagnostics,
|
|
1598
|
-
"COLLIE305",
|
|
1599
|
-
`Class alias name '${rawName}' must be a valid identifier.`,
|
|
1600
|
-
lineNumber,
|
|
1601
|
-
column,
|
|
1602
|
-
lineOffset,
|
|
1603
|
-
Math.max(rawName.length, 1)
|
|
1604
|
-
);
|
|
1605
|
-
return null;
|
|
3472
|
+
if (!Number.isFinite(indent) || indent < 1) {
|
|
3473
|
+
throw new Error("Indent width must be a positive integer.");
|
|
1606
3474
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
3475
|
+
return Math.floor(indent);
|
|
3476
|
+
}
|
|
3477
|
+
function serializeTemplates(templates, indentSize) {
|
|
3478
|
+
const lines = [];
|
|
3479
|
+
for (const template of templates) {
|
|
3480
|
+
if (lines.length && lines[lines.length - 1] !== "") {
|
|
3481
|
+
lines.push("");
|
|
3482
|
+
}
|
|
3483
|
+
const idValue = template.rawId || template.id;
|
|
3484
|
+
lines.push(cleanLine(`#id ${idValue}`));
|
|
3485
|
+
const body = serializeRoot(template.ast, indentSize);
|
|
3486
|
+
if (body.trim().length > 0) {
|
|
3487
|
+
lines.push(...body.split("\n"));
|
|
3488
|
+
}
|
|
1613
3489
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
return
|
|
3490
|
+
while (lines.length && lines[lines.length - 1] === "") {
|
|
3491
|
+
lines.pop();
|
|
3492
|
+
}
|
|
3493
|
+
return lines.join("\n");
|
|
1618
3494
|
}
|
|
1619
|
-
function
|
|
1620
|
-
const
|
|
1621
|
-
if (
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
3495
|
+
function serializeRoot(root, indentSize) {
|
|
3496
|
+
const sections = [];
|
|
3497
|
+
if (root.classAliases && root.classAliases.aliases.length > 0) {
|
|
3498
|
+
sections.push(formatClassAliases(root.classAliases, indentSize));
|
|
3499
|
+
}
|
|
3500
|
+
if (root.props && root.props.fields.length > 0) {
|
|
3501
|
+
sections.push(formatProps(root.props, indentSize));
|
|
3502
|
+
}
|
|
3503
|
+
if (root.children.length > 0) {
|
|
3504
|
+
sections.push(formatNodes(root.children, 0, indentSize));
|
|
3505
|
+
}
|
|
3506
|
+
const lines = [];
|
|
3507
|
+
for (const section of sections) {
|
|
3508
|
+
if (!section.length) continue;
|
|
3509
|
+
if (lines.length && lines[lines.length - 1] !== "") {
|
|
3510
|
+
lines.push("");
|
|
3511
|
+
}
|
|
3512
|
+
for (const line of section) {
|
|
3513
|
+
lines.push(line);
|
|
3514
|
+
}
|
|
1632
3515
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
3516
|
+
while (lines.length && lines[lines.length - 1] === "") {
|
|
3517
|
+
lines.pop();
|
|
3518
|
+
}
|
|
3519
|
+
return lines.join("\n");
|
|
3520
|
+
}
|
|
3521
|
+
function formatClassAliases(decl, indentSize) {
|
|
3522
|
+
const indent = indentString(1, indentSize);
|
|
3523
|
+
const lines = ["classes"];
|
|
3524
|
+
for (const alias of decl.aliases) {
|
|
3525
|
+
const rhs = alias.classes.join(".");
|
|
3526
|
+
lines.push(cleanLine(`${indent}${alias.name} = ${rhs}`));
|
|
3527
|
+
}
|
|
3528
|
+
return lines;
|
|
3529
|
+
}
|
|
3530
|
+
function formatProps(props, indentSize) {
|
|
3531
|
+
const indent = indentString(1, indentSize);
|
|
3532
|
+
const lines = ["props"];
|
|
3533
|
+
for (const field of props.fields) {
|
|
3534
|
+
const optionalFlag = field.optional ? "?" : "";
|
|
3535
|
+
lines.push(cleanLine(`${indent}${field.name}${optionalFlag}: ${field.typeText.trim()}`));
|
|
3536
|
+
}
|
|
3537
|
+
return lines;
|
|
3538
|
+
}
|
|
3539
|
+
function formatNodes(nodes, level, indentSize) {
|
|
3540
|
+
const lines = [];
|
|
3541
|
+
for (const node of nodes) {
|
|
3542
|
+
lines.push(...formatNode(node, level, indentSize));
|
|
3543
|
+
}
|
|
3544
|
+
return lines;
|
|
3545
|
+
}
|
|
3546
|
+
function formatNode(node, level, indentSize) {
|
|
3547
|
+
switch (node.type) {
|
|
3548
|
+
case "Element":
|
|
3549
|
+
return formatElement(node, level, indentSize);
|
|
3550
|
+
case "Component":
|
|
3551
|
+
return formatComponent(node, level, indentSize);
|
|
3552
|
+
case "Text":
|
|
3553
|
+
return [formatTextNode(node, level, indentSize)];
|
|
3554
|
+
case "Expression":
|
|
3555
|
+
return [cleanLine(`${indentString(level, indentSize)}{{ ${node.value} }}`)];
|
|
3556
|
+
case "JSXPassthrough":
|
|
3557
|
+
return formatJsxPassthrough(node.expression, level, indentSize);
|
|
3558
|
+
case "For":
|
|
3559
|
+
return formatFor(node, level, indentSize);
|
|
3560
|
+
case "Conditional":
|
|
3561
|
+
return formatConditional(node, level, indentSize);
|
|
3562
|
+
default:
|
|
1648
3563
|
return [];
|
|
1649
|
-
}
|
|
1650
|
-
classes.push(token);
|
|
1651
3564
|
}
|
|
1652
|
-
return classes;
|
|
1653
3565
|
}
|
|
1654
|
-
function
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
3566
|
+
function formatElement(node, level, indentSize) {
|
|
3567
|
+
const indent = indentString(level, indentSize);
|
|
3568
|
+
let line = `${indent}${node.name}${formatClassList(node.classes)}`;
|
|
3569
|
+
const attrs = formatAttributes(node.attributes);
|
|
3570
|
+
if (attrs) {
|
|
3571
|
+
line += `(${attrs})`;
|
|
3572
|
+
}
|
|
3573
|
+
const children = formatNodes(node.children, level + 1, indentSize);
|
|
3574
|
+
if (children.length === 0) {
|
|
3575
|
+
return [cleanLine(line)];
|
|
3576
|
+
}
|
|
3577
|
+
return [cleanLine(line), ...children];
|
|
3578
|
+
}
|
|
3579
|
+
function formatComponent(node, level, indentSize) {
|
|
3580
|
+
const indent = indentString(level, indentSize);
|
|
3581
|
+
let line = `${indent}${node.name}`;
|
|
3582
|
+
const attrs = formatAttributes(node.attributes);
|
|
3583
|
+
if (attrs) {
|
|
3584
|
+
line += `(${attrs})`;
|
|
3585
|
+
}
|
|
3586
|
+
const children = formatNodes(node.children, level + 1, indentSize);
|
|
3587
|
+
if (children.length === 0) {
|
|
3588
|
+
return [cleanLine(line)];
|
|
3589
|
+
}
|
|
3590
|
+
return [cleanLine(line), ...children];
|
|
3591
|
+
}
|
|
3592
|
+
function formatTextNode(node, level, indentSize) {
|
|
3593
|
+
const indent = indentString(level, indentSize);
|
|
3594
|
+
const text = renderTextParts(node.parts);
|
|
3595
|
+
return cleanLine(`${indent}| ${text}`);
|
|
3596
|
+
}
|
|
3597
|
+
function renderTextParts(parts) {
|
|
3598
|
+
return parts.map((part) => {
|
|
3599
|
+
if (part.type === "text") {
|
|
3600
|
+
return part.value;
|
|
3601
|
+
}
|
|
3602
|
+
return `{${part.value}}`;
|
|
3603
|
+
}).join("");
|
|
3604
|
+
}
|
|
3605
|
+
function formatJsxPassthrough(expression, level, indentSize) {
|
|
3606
|
+
const indent = indentString(level, indentSize);
|
|
3607
|
+
const childIndent = indentString(level + 1, indentSize);
|
|
3608
|
+
const normalized = expression.replace(/\r\n?/g, "\n").trimEnd();
|
|
3609
|
+
if (!normalized.trim()) {
|
|
3610
|
+
return [cleanLine(`${indent}= ${normalized.trim()}`)];
|
|
3611
|
+
}
|
|
3612
|
+
const lines = normalized.split("\n");
|
|
3613
|
+
const [first, ...rest] = lines;
|
|
3614
|
+
const result = [cleanLine(`${indent}= ${first.trim()}`)];
|
|
3615
|
+
if (rest.length === 0) {
|
|
3616
|
+
return result;
|
|
3617
|
+
}
|
|
3618
|
+
const dedent = computeDedent(rest);
|
|
3619
|
+
for (const raw of rest) {
|
|
3620
|
+
if (!raw.trim()) {
|
|
3621
|
+
result.push("");
|
|
1669
3622
|
continue;
|
|
1670
3623
|
}
|
|
1671
|
-
|
|
3624
|
+
const withoutIndent = raw.slice(Math.min(dedent, raw.length)).trimEnd();
|
|
3625
|
+
result.push(cleanLine(`${childIndent}${withoutIndent}`));
|
|
1672
3626
|
}
|
|
3627
|
+
return result;
|
|
1673
3628
|
}
|
|
1674
|
-
function
|
|
1675
|
-
|
|
1676
|
-
for (const
|
|
1677
|
-
|
|
3629
|
+
function computeDedent(lines) {
|
|
3630
|
+
let min = Number.POSITIVE_INFINITY;
|
|
3631
|
+
for (const line of lines) {
|
|
3632
|
+
if (!line.trim()) continue;
|
|
3633
|
+
const indentMatch = line.match(/^\s*/);
|
|
3634
|
+
const indentLength = indentMatch ? indentMatch[0].length : 0;
|
|
3635
|
+
min = Math.min(min, indentLength);
|
|
3636
|
+
}
|
|
3637
|
+
return Number.isFinite(min) ? min : 0;
|
|
3638
|
+
}
|
|
3639
|
+
function formatFor(node, level, indentSize) {
|
|
3640
|
+
const indent = indentString(level, indentSize);
|
|
3641
|
+
const header = cleanLine(`${indent}@for ${node.itemName} in ${node.arrayExpr}`);
|
|
3642
|
+
const body = formatNodes(node.body, level + 1, indentSize);
|
|
3643
|
+
return body.length ? [header, ...body] : [header];
|
|
3644
|
+
}
|
|
3645
|
+
function formatConditional(node, level, indentSize) {
|
|
3646
|
+
const indent = indentString(level, indentSize);
|
|
3647
|
+
const lines = [];
|
|
3648
|
+
node.branches.forEach((branch, index) => {
|
|
3649
|
+
let directive;
|
|
3650
|
+
if (index === 0) {
|
|
3651
|
+
directive = `@if (${branch.test ?? ""})`;
|
|
3652
|
+
} else if (branch.test) {
|
|
3653
|
+
directive = `@elseIf (${branch.test})`;
|
|
3654
|
+
} else {
|
|
3655
|
+
directive = "@else";
|
|
3656
|
+
}
|
|
3657
|
+
lines.push(cleanLine(`${indent}${directive}`));
|
|
3658
|
+
const body = formatNodes(branch.body, level + 1, indentSize);
|
|
3659
|
+
lines.push(...body);
|
|
3660
|
+
});
|
|
3661
|
+
return lines;
|
|
3662
|
+
}
|
|
3663
|
+
function formatAttributes(attributes) {
|
|
3664
|
+
if (!attributes.length) {
|
|
3665
|
+
return "";
|
|
1678
3666
|
}
|
|
3667
|
+
const sorted = [...attributes].sort((a, b) => {
|
|
3668
|
+
if (a.name === b.name) return 0;
|
|
3669
|
+
if (a.name === "class") return -1;
|
|
3670
|
+
if (b.name === "class") return 1;
|
|
3671
|
+
return a.name.localeCompare(b.name);
|
|
3672
|
+
});
|
|
3673
|
+
return sorted.map((attr) => {
|
|
3674
|
+
if (attr.value === null) {
|
|
3675
|
+
return attr.name;
|
|
3676
|
+
}
|
|
3677
|
+
return `${attr.name}=${normalizeAttributeValue(attr.value)}`;
|
|
3678
|
+
}).join(" ");
|
|
1679
3679
|
}
|
|
1680
|
-
function
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
3680
|
+
function normalizeAttributeValue(value) {
|
|
3681
|
+
const trimmed = value.trim();
|
|
3682
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("<")) {
|
|
3683
|
+
return trimmed;
|
|
3684
|
+
}
|
|
3685
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
3686
|
+
return trimmed;
|
|
3687
|
+
}
|
|
3688
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
3689
|
+
const inner = trimmed.slice(1, -1).replace(/"/g, '\\"');
|
|
3690
|
+
return `"${inner}"`;
|
|
3691
|
+
}
|
|
3692
|
+
return trimmed;
|
|
3693
|
+
}
|
|
3694
|
+
function formatClassList(classes) {
|
|
3695
|
+
if (!classes.length) return "";
|
|
3696
|
+
return classes.map((cls) => `.${cls}`).join("");
|
|
3697
|
+
}
|
|
3698
|
+
function indentString(level, indentSize) {
|
|
3699
|
+
return " ".repeat(level * indentSize);
|
|
3700
|
+
}
|
|
3701
|
+
function ensureTrailingNewline(output) {
|
|
3702
|
+
const trimmed = output.replace(/\s+$/g, "");
|
|
3703
|
+
return trimmed.length ? `${trimmed}
|
|
3704
|
+
` : "\n";
|
|
3705
|
+
}
|
|
3706
|
+
function cleanLine(line) {
|
|
3707
|
+
return line.replace(/[ \t]+$/g, "");
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
// src/convert.ts
|
|
3711
|
+
var import_typescript = __toESM(require("typescript"), 1);
|
|
3712
|
+
function convertTsxToCollie(source, options = {}) {
|
|
3713
|
+
const filename = options.filename ?? "input.tsx";
|
|
3714
|
+
const sourceFile = import_typescript.default.createSourceFile(
|
|
3715
|
+
filename,
|
|
3716
|
+
source,
|
|
3717
|
+
import_typescript.default.ScriptTarget.Latest,
|
|
3718
|
+
true,
|
|
3719
|
+
inferScriptKind(filename)
|
|
3720
|
+
);
|
|
3721
|
+
const warnings = [];
|
|
3722
|
+
const ctx = { sourceFile, warnings };
|
|
3723
|
+
const propDeclarations = collectPropDeclarations(sourceFile);
|
|
3724
|
+
const component = findComponentInfo(sourceFile, propDeclarations, ctx);
|
|
3725
|
+
if (!component) {
|
|
3726
|
+
throw new Error("Could not find a component that returns JSX in this file.");
|
|
3727
|
+
}
|
|
3728
|
+
const propsLines = buildPropsBlock(component, propDeclarations, ctx);
|
|
3729
|
+
const templateLines = convertJsxNode(component.jsxRoot, ctx, 0);
|
|
3730
|
+
if (!templateLines.length) {
|
|
3731
|
+
throw new Error("Unable to convert JSX tree to Collie template.");
|
|
3732
|
+
}
|
|
3733
|
+
const sections = [];
|
|
3734
|
+
if (propsLines.length) {
|
|
3735
|
+
sections.push(propsLines.join("\n"));
|
|
3736
|
+
}
|
|
3737
|
+
sections.push(templateLines.join("\n"));
|
|
3738
|
+
const collie = `${sections.join("\n\n").trimEnd()}
|
|
3739
|
+
`;
|
|
3740
|
+
return { collie, warnings };
|
|
3741
|
+
}
|
|
3742
|
+
function inferScriptKind(filename) {
|
|
3743
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
3744
|
+
const ext = dotIndex === -1 ? "" : filename.slice(dotIndex).toLowerCase();
|
|
3745
|
+
if (ext === ".tsx") return import_typescript.default.ScriptKind.TSX;
|
|
3746
|
+
if (ext === ".jsx") return import_typescript.default.ScriptKind.JSX;
|
|
3747
|
+
if (ext === ".ts") return import_typescript.default.ScriptKind.TS;
|
|
3748
|
+
return import_typescript.default.ScriptKind.JS;
|
|
3749
|
+
}
|
|
3750
|
+
function collectPropDeclarations(sourceFile) {
|
|
3751
|
+
const map = /* @__PURE__ */ new Map();
|
|
3752
|
+
for (const statement of sourceFile.statements) {
|
|
3753
|
+
if (import_typescript.default.isInterfaceDeclaration(statement) && statement.name) {
|
|
3754
|
+
map.set(statement.name.text, extractPropsFromMembers(statement.members, sourceFile));
|
|
3755
|
+
} else if (import_typescript.default.isTypeAliasDeclaration(statement) && import_typescript.default.isTypeLiteralNode(statement.type)) {
|
|
3756
|
+
map.set(statement.name.text, extractPropsFromMembers(statement.type.members, sourceFile));
|
|
1707
3757
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
3758
|
+
}
|
|
3759
|
+
return map;
|
|
3760
|
+
}
|
|
3761
|
+
function extractPropsFromMembers(members, sourceFile) {
|
|
3762
|
+
const fields = [];
|
|
3763
|
+
for (const member of members) {
|
|
3764
|
+
if (!import_typescript.default.isPropertySignature(member) || member.name === void 0) {
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
const name = getPropertyName(member.name, sourceFile);
|
|
3768
|
+
if (!name) {
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3771
|
+
const typeText = member.type ? member.type.getText(sourceFile).trim() : "any";
|
|
3772
|
+
fields.push({
|
|
3773
|
+
name,
|
|
3774
|
+
optional: Boolean(member.questionToken),
|
|
3775
|
+
typeText
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3778
|
+
return fields;
|
|
3779
|
+
}
|
|
3780
|
+
function findComponentInfo(sourceFile, declarations, ctx) {
|
|
3781
|
+
for (const statement of sourceFile.statements) {
|
|
3782
|
+
if (import_typescript.default.isFunctionDeclaration(statement) && statement.body) {
|
|
3783
|
+
const jsx = findJsxReturn(statement.body);
|
|
3784
|
+
if (jsx) {
|
|
3785
|
+
const defaults = extractDefaultsFromParameters(statement.parameters, ctx);
|
|
3786
|
+
const propsInfo = resolvePropsFromParameters(statement.parameters, declarations, ctx);
|
|
3787
|
+
return {
|
|
3788
|
+
jsxRoot: jsx,
|
|
3789
|
+
propsTypeName: propsInfo.typeName,
|
|
3790
|
+
inlineProps: propsInfo.inline,
|
|
3791
|
+
defaults
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
} else if (import_typescript.default.isVariableStatement(statement)) {
|
|
3795
|
+
for (const decl of statement.declarationList.declarations) {
|
|
3796
|
+
const init = decl.initializer;
|
|
3797
|
+
if (!init) continue;
|
|
3798
|
+
if (import_typescript.default.isArrowFunction(init) || import_typescript.default.isFunctionExpression(init)) {
|
|
3799
|
+
const jsx = init.body ? findJsxInFunctionBody(init.body) : void 0;
|
|
3800
|
+
if (!jsx) {
|
|
3801
|
+
continue;
|
|
3802
|
+
}
|
|
3803
|
+
const defaults = extractDefaultsFromParameters(init.parameters, ctx);
|
|
3804
|
+
const propsInfo = resolvePropsFromParameters(init.parameters, declarations, ctx);
|
|
3805
|
+
if (!propsInfo.typeName && !propsInfo.inline && decl.type) {
|
|
3806
|
+
const inferred = resolvePropsFromTypeAnnotation(decl.type, sourceFile, declarations);
|
|
3807
|
+
if (inferred.typeName && !propsInfo.typeName) {
|
|
3808
|
+
propsInfo.typeName = inferred.typeName;
|
|
3809
|
+
}
|
|
3810
|
+
if (inferred.inline && !propsInfo.inline) {
|
|
3811
|
+
propsInfo.inline = inferred.inline;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
return {
|
|
3815
|
+
jsxRoot: jsx,
|
|
3816
|
+
propsTypeName: propsInfo.typeName,
|
|
3817
|
+
inlineProps: propsInfo.inline,
|
|
3818
|
+
defaults
|
|
3819
|
+
};
|
|
1712
3820
|
}
|
|
1713
3821
|
}
|
|
1714
3822
|
}
|
|
1715
|
-
return;
|
|
1716
3823
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3826
|
+
function resolvePropsFromParameters(parameters, declarations, ctx) {
|
|
3827
|
+
if (!parameters.length) {
|
|
3828
|
+
return {};
|
|
3829
|
+
}
|
|
3830
|
+
const param = parameters[0];
|
|
3831
|
+
if (param.type) {
|
|
3832
|
+
const inferred = resolvePropsFromTypeAnnotation(param.type, ctx.sourceFile, declarations);
|
|
3833
|
+
if (inferred.inline) {
|
|
3834
|
+
return inferred;
|
|
3835
|
+
}
|
|
3836
|
+
if (inferred.typeName) {
|
|
3837
|
+
return inferred;
|
|
1722
3838
|
}
|
|
1723
3839
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
3840
|
+
return {};
|
|
3841
|
+
}
|
|
3842
|
+
function resolvePropsFromTypeAnnotation(typeNode, sourceFile, declarations) {
|
|
3843
|
+
if (import_typescript.default.isTypeReferenceNode(typeNode)) {
|
|
3844
|
+
const referenced = getTypeReferenceName(typeNode.typeName);
|
|
3845
|
+
if (referenced && declarations.has(referenced)) {
|
|
3846
|
+
return { typeName: referenced };
|
|
3847
|
+
}
|
|
3848
|
+
const typeArg = typeNode.typeArguments?.[0];
|
|
3849
|
+
if (typeArg) {
|
|
3850
|
+
if (import_typescript.default.isTypeReferenceNode(typeArg)) {
|
|
3851
|
+
const nested = getTypeReferenceName(typeArg.typeName);
|
|
3852
|
+
if (nested && declarations.has(nested)) {
|
|
3853
|
+
return { typeName: nested };
|
|
3854
|
+
}
|
|
3855
|
+
} else if (import_typescript.default.isTypeLiteralNode(typeArg)) {
|
|
3856
|
+
return { inline: extractPropsFromMembers(typeArg.members, sourceFile) };
|
|
3857
|
+
}
|
|
1727
3858
|
}
|
|
1728
3859
|
}
|
|
3860
|
+
if (import_typescript.default.isTypeLiteralNode(typeNode)) {
|
|
3861
|
+
return { inline: extractPropsFromMembers(typeNode.members, sourceFile) };
|
|
3862
|
+
}
|
|
3863
|
+
return {};
|
|
1729
3864
|
}
|
|
1730
|
-
function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
return null;
|
|
3865
|
+
function getTypeReferenceName(typeName) {
|
|
3866
|
+
if (import_typescript.default.isIdentifier(typeName)) {
|
|
3867
|
+
return typeName.text;
|
|
1734
3868
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
const nextPart = line.slice(cursor);
|
|
1738
|
-
const isComponent = /^[A-Z]/.test(name);
|
|
1739
|
-
if (isComponent && nextPart.length > 0) {
|
|
1740
|
-
const trimmedNext = nextPart.trimStart();
|
|
1741
|
-
if (trimmedNext.length > 0 && !trimmedNext.startsWith("(")) {
|
|
1742
|
-
return null;
|
|
1743
|
-
}
|
|
3869
|
+
if (import_typescript.default.isQualifiedName(typeName)) {
|
|
3870
|
+
return typeName.right.text;
|
|
1744
3871
|
}
|
|
1745
|
-
if (
|
|
1746
|
-
|
|
1747
|
-
if (nextChar !== "." && nextChar !== "(" && !/\s/.test(nextChar)) {
|
|
1748
|
-
return null;
|
|
1749
|
-
}
|
|
3872
|
+
if (import_typescript.default.isPropertyAccessExpression(typeName)) {
|
|
3873
|
+
return getTypeReferenceName(typeName.name);
|
|
1750
3874
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
const
|
|
1757
|
-
if (
|
|
1758
|
-
|
|
1759
|
-
diagnostics,
|
|
1760
|
-
"COLLIE004",
|
|
1761
|
-
"Class names must contain only letters, numbers, underscores, hyphens, or `$` (for aliases).",
|
|
1762
|
-
lineNumber,
|
|
1763
|
-
column + cursor,
|
|
1764
|
-
lineOffset
|
|
1765
|
-
);
|
|
1766
|
-
return null;
|
|
3875
|
+
return void 0;
|
|
3876
|
+
}
|
|
3877
|
+
function findJsxReturn(body) {
|
|
3878
|
+
for (const statement of body.statements) {
|
|
3879
|
+
if (import_typescript.default.isReturnStatement(statement) && statement.expression) {
|
|
3880
|
+
const jsx = unwrapJsx(statement.expression);
|
|
3881
|
+
if (jsx) {
|
|
3882
|
+
return jsx;
|
|
1767
3883
|
}
|
|
1768
|
-
const className = classMatch[1];
|
|
1769
|
-
classes.push(className);
|
|
1770
|
-
classSpans.push(createSpan(lineNumber, column + cursor, className.length, lineOffset));
|
|
1771
|
-
cursor += className.length;
|
|
1772
3884
|
}
|
|
1773
3885
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
}
|
|
1780
|
-
attributes.push(...attrResult.attributes);
|
|
1781
|
-
cursor = attrResult.endIndex;
|
|
3886
|
+
return void 0;
|
|
3887
|
+
}
|
|
3888
|
+
function findJsxInFunctionBody(body) {
|
|
3889
|
+
if (import_typescript.default.isBlock(body)) {
|
|
3890
|
+
return findJsxReturn(body);
|
|
1782
3891
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
3892
|
+
return unwrapJsx(body);
|
|
3893
|
+
}
|
|
3894
|
+
function unwrapJsx(expression) {
|
|
3895
|
+
let current = expression;
|
|
3896
|
+
while (import_typescript.default.isParenthesizedExpression(current)) {
|
|
3897
|
+
current = current.expression;
|
|
1787
3898
|
}
|
|
1788
|
-
if (
|
|
1789
|
-
|
|
1790
|
-
cursor++;
|
|
1791
|
-
const guardExpr = line.slice(cursor).trim();
|
|
1792
|
-
if (!guardExpr) {
|
|
1793
|
-
pushDiag(
|
|
1794
|
-
diagnostics,
|
|
1795
|
-
"COLLIE601",
|
|
1796
|
-
"Guard expressions require a condition after '?'.",
|
|
1797
|
-
lineNumber,
|
|
1798
|
-
guardColumn,
|
|
1799
|
-
lineOffset
|
|
1800
|
-
);
|
|
1801
|
-
} else {
|
|
1802
|
-
guard = guardExpr;
|
|
1803
|
-
}
|
|
1804
|
-
cursor = line.length;
|
|
1805
|
-
} else {
|
|
1806
|
-
cursor = guardProbeStart;
|
|
3899
|
+
if (import_typescript.default.isJsxElement(current) || import_typescript.default.isJsxFragment(current) || import_typescript.default.isJsxSelfClosingElement(current)) {
|
|
3900
|
+
return current;
|
|
1807
3901
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
}
|
|
3902
|
+
return void 0;
|
|
3903
|
+
}
|
|
3904
|
+
function extractDefaultsFromParameters(parameters, ctx) {
|
|
3905
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
3906
|
+
if (!parameters.length) {
|
|
3907
|
+
return defaults;
|
|
1815
3908
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
if (guard) {
|
|
1824
|
-
component.guard = guard;
|
|
3909
|
+
const param = parameters[0];
|
|
3910
|
+
if (!import_typescript.default.isObjectBindingPattern(param.name)) {
|
|
3911
|
+
return defaults;
|
|
3912
|
+
}
|
|
3913
|
+
for (const element of param.name.elements) {
|
|
3914
|
+
if (!element.initializer) {
|
|
3915
|
+
continue;
|
|
1825
3916
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
name,
|
|
1831
|
-
classes,
|
|
1832
|
-
attributes,
|
|
1833
|
-
children
|
|
1834
|
-
};
|
|
1835
|
-
if (classSpans.length) {
|
|
1836
|
-
element.classSpans = classSpans;
|
|
3917
|
+
const propName = getBindingElementPropName(element, ctx.sourceFile);
|
|
3918
|
+
if (!propName) {
|
|
3919
|
+
ctx.warnings.push("Skipping complex destructured default value.");
|
|
3920
|
+
continue;
|
|
1837
3921
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
3922
|
+
defaults.set(propName, element.initializer.getText(ctx.sourceFile).trim());
|
|
3923
|
+
}
|
|
3924
|
+
return defaults;
|
|
3925
|
+
}
|
|
3926
|
+
function getBindingElementPropName(element, sourceFile) {
|
|
3927
|
+
const prop = element.propertyName;
|
|
3928
|
+
if (prop) {
|
|
3929
|
+
if (import_typescript.default.isIdentifier(prop) || import_typescript.default.isStringLiteral(prop) || import_typescript.default.isNumericLiteral(prop)) {
|
|
3930
|
+
return prop.text;
|
|
1840
3931
|
}
|
|
1841
|
-
return
|
|
3932
|
+
return prop.getText(sourceFile);
|
|
1842
3933
|
}
|
|
3934
|
+
if (import_typescript.default.isIdentifier(element.name)) {
|
|
3935
|
+
return element.name.text;
|
|
3936
|
+
}
|
|
3937
|
+
return void 0;
|
|
1843
3938
|
}
|
|
1844
|
-
function
|
|
1845
|
-
if (
|
|
1846
|
-
return
|
|
3939
|
+
function getPropertyName(name, sourceFile) {
|
|
3940
|
+
if (import_typescript.default.isIdentifier(name)) {
|
|
3941
|
+
return name.text;
|
|
1847
3942
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
3943
|
+
if (import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
3944
|
+
return name.text;
|
|
3945
|
+
}
|
|
3946
|
+
return name.getText(sourceFile);
|
|
3947
|
+
}
|
|
3948
|
+
function buildPropsBlock(info, propDeclarations, ctx) {
|
|
3949
|
+
const fields = info.inlineProps ?? (info.propsTypeName ? propDeclarations.get(info.propsTypeName) ?? [] : void 0) ?? [];
|
|
3950
|
+
if (!fields.length && !info.defaults.size) {
|
|
3951
|
+
return [];
|
|
3952
|
+
}
|
|
3953
|
+
const lines = ["props"];
|
|
3954
|
+
if (fields.length) {
|
|
3955
|
+
for (const field of fields) {
|
|
3956
|
+
const def = info.defaults.get(field.name);
|
|
3957
|
+
let line = ` ${field.name}${field.optional ? "?" : ""}: ${field.typeText}`;
|
|
3958
|
+
if (def) {
|
|
3959
|
+
line += ` = ${def}`;
|
|
1861
3960
|
}
|
|
1862
|
-
|
|
1863
|
-
|
|
3961
|
+
lines.push(line);
|
|
3962
|
+
}
|
|
3963
|
+
} else {
|
|
3964
|
+
for (const [name, defValue] of info.defaults.entries()) {
|
|
3965
|
+
lines.push(` ${name}: any = ${defValue}`);
|
|
1864
3966
|
}
|
|
1865
|
-
cursor++;
|
|
1866
3967
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
lineNumber,
|
|
1873
|
-
column + startIndex,
|
|
1874
|
-
lineOffset
|
|
1875
|
-
);
|
|
1876
|
-
return null;
|
|
3968
|
+
return lines;
|
|
3969
|
+
}
|
|
3970
|
+
function convertJsxNode(node, ctx, indent) {
|
|
3971
|
+
if (import_typescript.default.isJsxElement(node)) {
|
|
3972
|
+
return convertJsxElement(node, ctx, indent);
|
|
1877
3973
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
return { attributes: [], endIndex: cursor };
|
|
3974
|
+
if (import_typescript.default.isJsxSelfClosingElement(node)) {
|
|
3975
|
+
return convertJsxSelfClosing(node, ctx, indent);
|
|
1881
3976
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
3977
|
+
if (import_typescript.default.isJsxFragment(node)) {
|
|
3978
|
+
return convertJsxFragment(node, ctx, indent);
|
|
3979
|
+
}
|
|
3980
|
+
if (import_typescript.default.isJsxText(node)) {
|
|
3981
|
+
return convertJsxText(node, ctx, indent);
|
|
3982
|
+
}
|
|
3983
|
+
if (import_typescript.default.isJsxExpression(node)) {
|
|
3984
|
+
return convertJsxExpression(node, ctx, indent);
|
|
3985
|
+
}
|
|
3986
|
+
return [];
|
|
3987
|
+
}
|
|
3988
|
+
function convertJsxFragment(fragment, ctx, indent) {
|
|
3989
|
+
const lines = [];
|
|
3990
|
+
for (const child of fragment.children) {
|
|
3991
|
+
lines.push(...convertJsxNode(child, ctx, indent));
|
|
3992
|
+
}
|
|
3993
|
+
return lines;
|
|
3994
|
+
}
|
|
3995
|
+
function convertJsxElement(element, ctx, indent) {
|
|
3996
|
+
const line = buildElementLine(element.openingElement, ctx, indent);
|
|
3997
|
+
const children = [];
|
|
3998
|
+
for (const child of element.children) {
|
|
3999
|
+
children.push(...convertJsxNode(child, ctx, indent + 1));
|
|
4000
|
+
}
|
|
4001
|
+
if (!children.length) {
|
|
4002
|
+
return [line];
|
|
4003
|
+
}
|
|
4004
|
+
return [line, ...children];
|
|
4005
|
+
}
|
|
4006
|
+
function convertJsxSelfClosing(element, ctx, indent) {
|
|
4007
|
+
return [buildElementLine(element, ctx, indent)];
|
|
4008
|
+
}
|
|
4009
|
+
function buildElementLine(element, ctx, indent) {
|
|
4010
|
+
const indentStr = " ".repeat(indent);
|
|
4011
|
+
const tagName = getTagName(element.tagName, ctx);
|
|
4012
|
+
const { classSegments, attributes } = convertAttributes(element.attributes, ctx);
|
|
4013
|
+
const classes = classSegments.length ? classSegments.map((cls) => `.${cls}`).join("") : "";
|
|
4014
|
+
const attrString = attributes.length ? `(${attributes.join(" ")})` : "";
|
|
4015
|
+
return `${indentStr}${tagName}${classes}${attrString}`;
|
|
4016
|
+
}
|
|
4017
|
+
function getTagName(tag, ctx) {
|
|
4018
|
+
const fallback = tag.getText(ctx.sourceFile);
|
|
4019
|
+
if (import_typescript.default.isIdentifier(tag)) {
|
|
4020
|
+
return tag.text;
|
|
4021
|
+
}
|
|
4022
|
+
if (import_typescript.default.isPropertyAccessExpression(tag)) {
|
|
4023
|
+
const left = getTagName(tag.expression, ctx);
|
|
4024
|
+
return `${left}.${tag.name.text}`;
|
|
4025
|
+
}
|
|
4026
|
+
if (tag.kind === import_typescript.default.SyntaxKind.ThisKeyword) {
|
|
4027
|
+
return "this";
|
|
4028
|
+
}
|
|
4029
|
+
if (import_typescript.default.isJsxNamespacedName(tag)) {
|
|
4030
|
+
return `${tag.namespace.text}:${tag.name.text}`;
|
|
4031
|
+
}
|
|
4032
|
+
return fallback;
|
|
4033
|
+
}
|
|
4034
|
+
function convertAttributes(attributes, ctx) {
|
|
4035
|
+
const classSegments = [];
|
|
4036
|
+
const attrs = [];
|
|
4037
|
+
for (const attr of attributes.properties) {
|
|
4038
|
+
if (import_typescript.default.isJsxAttribute(attr)) {
|
|
4039
|
+
const attrName = getAttributeName(attr.name, ctx);
|
|
4040
|
+
if (!attrName) {
|
|
4041
|
+
ctx.warnings.push("Skipping unsupported attribute name.");
|
|
4042
|
+
continue;
|
|
1892
4043
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
4044
|
+
if (attrName === "className" || attrName === "class") {
|
|
4045
|
+
const handled = handleClassAttribute(attr, ctx, classSegments, attrs);
|
|
4046
|
+
if (!handled) {
|
|
4047
|
+
attrs.push(formatAttribute(attrName === "className" ? "className" : attrName, attr.initializer, ctx));
|
|
4048
|
+
}
|
|
4049
|
+
continue;
|
|
1899
4050
|
}
|
|
4051
|
+
attrs.push(formatAttribute(attrName, attr.initializer, ctx));
|
|
4052
|
+
} else if (import_typescript.default.isJsxSpreadAttribute(attr)) {
|
|
4053
|
+
ctx.warnings.push("Spread attributes are not supported and were skipped.");
|
|
1900
4054
|
}
|
|
1901
4055
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
4056
|
+
return { classSegments, attributes: attrs.filter(Boolean) };
|
|
4057
|
+
}
|
|
4058
|
+
function handleClassAttribute(attr, ctx, classSegments, attrs) {
|
|
4059
|
+
if (!attr.initializer) {
|
|
4060
|
+
return false;
|
|
1904
4061
|
}
|
|
1905
|
-
|
|
4062
|
+
if (import_typescript.default.isStringLiteral(attr.initializer)) {
|
|
4063
|
+
classSegments.push(...splitClassNames(attr.initializer.text));
|
|
4064
|
+
return true;
|
|
4065
|
+
}
|
|
4066
|
+
if (import_typescript.default.isJsxExpression(attr.initializer) && attr.initializer.expression) {
|
|
4067
|
+
const expressionText = attr.initializer.expression.getText(ctx.sourceFile).trim();
|
|
4068
|
+
attrs.push(`className={${expressionText}}`);
|
|
4069
|
+
return true;
|
|
4070
|
+
}
|
|
4071
|
+
ctx.warnings.push("Unsupported class attribute value; leaving as-is.");
|
|
4072
|
+
return false;
|
|
1906
4073
|
}
|
|
1907
|
-
function
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
"COLLIE004",
|
|
1922
|
-
`Invalid attribute syntax: ${trimmed.slice(0, 30)}`,
|
|
1923
|
-
lineNumber,
|
|
1924
|
-
column,
|
|
1925
|
-
lineOffset
|
|
1926
|
-
);
|
|
4074
|
+
function splitClassNames(value) {
|
|
4075
|
+
return value.split(/\s+/).map((cls) => cls.trim()).filter(Boolean);
|
|
4076
|
+
}
|
|
4077
|
+
function formatAttribute(name, initializer, ctx) {
|
|
4078
|
+
if (!initializer) {
|
|
4079
|
+
return name;
|
|
4080
|
+
}
|
|
4081
|
+
if (import_typescript.default.isStringLiteral(initializer)) {
|
|
4082
|
+
return `${name}="${initializer.text}"`;
|
|
4083
|
+
}
|
|
4084
|
+
if (import_typescript.default.isJsxExpression(initializer)) {
|
|
4085
|
+
if (initializer.expression) {
|
|
4086
|
+
const expr = initializer.expression.getText(ctx.sourceFile).trim();
|
|
4087
|
+
return `${name}={${expr}}`;
|
|
1927
4088
|
}
|
|
4089
|
+
return name;
|
|
1928
4090
|
}
|
|
4091
|
+
ctx.warnings.push("Unsupported JSX attribute value; leaving as-is.");
|
|
4092
|
+
return name;
|
|
1929
4093
|
}
|
|
1930
|
-
function
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
}
|
|
4094
|
+
function getAttributeName(name, ctx) {
|
|
4095
|
+
if (import_typescript.default.isIdentifier(name)) {
|
|
4096
|
+
return name.text;
|
|
4097
|
+
}
|
|
4098
|
+
if (import_typescript.default.isJsxNamespacedName(name)) {
|
|
4099
|
+
return `${name.namespace.text}:${name.name.text}`;
|
|
4100
|
+
}
|
|
4101
|
+
return null;
|
|
4102
|
+
}
|
|
4103
|
+
function convertJsxText(textNode, ctx, indent) {
|
|
4104
|
+
const text = textNode.getText(ctx.sourceFile).replace(/\s+/g, " ").trim();
|
|
4105
|
+
if (!text) {
|
|
4106
|
+
return [];
|
|
4107
|
+
}
|
|
4108
|
+
return [`${" ".repeat(indent)}| ${text}`];
|
|
4109
|
+
}
|
|
4110
|
+
function convertJsxExpression(expressionNode, ctx, indent) {
|
|
4111
|
+
if (!expressionNode.expression) {
|
|
4112
|
+
return [];
|
|
4113
|
+
}
|
|
4114
|
+
const exprText = expressionNode.expression.getText(ctx.sourceFile).trim();
|
|
4115
|
+
if (!exprText) {
|
|
4116
|
+
return [];
|
|
4117
|
+
}
|
|
4118
|
+
return [`${" ".repeat(indent)}{{ ${exprText} }}`];
|
|
1937
4119
|
}
|
|
1938
4120
|
|
|
1939
4121
|
// src/index.ts
|
|
1940
4122
|
function parseCollie(source, options = {}) {
|
|
1941
|
-
const result = parse(source);
|
|
4123
|
+
const result = parse(source, { dialect: options.dialect });
|
|
1942
4124
|
if (!options.filename) {
|
|
1943
|
-
return
|
|
4125
|
+
return {
|
|
4126
|
+
templates: result.templates.map((template) => ({
|
|
4127
|
+
...template,
|
|
4128
|
+
diagnostics: normalizeDiagnostics2(template.diagnostics)
|
|
4129
|
+
})),
|
|
4130
|
+
diagnostics: normalizeDiagnostics2(result.diagnostics)
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
return {
|
|
4134
|
+
templates: result.templates.map((template) => ({
|
|
4135
|
+
...template,
|
|
4136
|
+
diagnostics: normalizeDiagnostics2(template.diagnostics, options.filename)
|
|
4137
|
+
})),
|
|
4138
|
+
diagnostics: normalizeDiagnostics2(result.diagnostics, options.filename)
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
function compileTemplate(template, options = {}) {
|
|
4142
|
+
const diagnostics = normalizeDiagnostics2(template.diagnostics, options.filename);
|
|
4143
|
+
const jsxRuntime = options.jsxRuntime ?? "automatic";
|
|
4144
|
+
const flavor = options.flavor ?? "tsx";
|
|
4145
|
+
let code = createStubRender(flavor);
|
|
4146
|
+
if (!hasErrors(diagnostics)) {
|
|
4147
|
+
code = generateRenderModule(template.ast, { jsxRuntime, flavor });
|
|
1944
4148
|
}
|
|
1945
|
-
|
|
4149
|
+
const meta = buildCompileMeta(template, options.filename);
|
|
4150
|
+
return { code, diagnostics, map: void 0, meta };
|
|
1946
4151
|
}
|
|
1947
4152
|
function compileToJsx(sourceOrAst, options = {}) {
|
|
1948
|
-
const document = normalizeDocument(sourceOrAst, options.filename);
|
|
1949
|
-
const diagnostics =
|
|
4153
|
+
const document = normalizeDocument(sourceOrAst, options.filename, options.dialect);
|
|
4154
|
+
const diagnostics = normalizeDiagnostics2(document.diagnostics, options.filename);
|
|
4155
|
+
const template = document.templates[0];
|
|
1950
4156
|
const componentName = options.componentNameHint ?? "CollieTemplate";
|
|
1951
4157
|
const jsxRuntime = options.jsxRuntime ?? "automatic";
|
|
1952
4158
|
let code = createStubComponent(componentName, "jsx");
|
|
1953
|
-
if (!hasErrors(diagnostics)) {
|
|
1954
|
-
|
|
4159
|
+
if (!hasErrors(diagnostics) && template) {
|
|
4160
|
+
const renderResult = compileTemplate(template, {
|
|
4161
|
+
filename: options.filename,
|
|
4162
|
+
jsxRuntime,
|
|
4163
|
+
flavor: "jsx"
|
|
4164
|
+
});
|
|
4165
|
+
code = wrapRenderModuleAsComponent(renderResult.code, componentName, "jsx");
|
|
1955
4166
|
}
|
|
1956
|
-
|
|
4167
|
+
const meta = buildCompileMeta(template, options.filename);
|
|
4168
|
+
return { code, diagnostics, map: void 0, meta };
|
|
1957
4169
|
}
|
|
1958
4170
|
function compileToTsx(sourceOrAst, options = {}) {
|
|
1959
|
-
const document = normalizeDocument(sourceOrAst, options.filename);
|
|
1960
|
-
const diagnostics =
|
|
4171
|
+
const document = normalizeDocument(sourceOrAst, options.filename, options.dialect);
|
|
4172
|
+
const diagnostics = normalizeDiagnostics2(document.diagnostics, options.filename);
|
|
4173
|
+
const template = document.templates[0];
|
|
1961
4174
|
const componentName = options.componentNameHint ?? "CollieTemplate";
|
|
1962
4175
|
const jsxRuntime = options.jsxRuntime ?? "automatic";
|
|
1963
4176
|
let code = createStubComponent(componentName, "tsx");
|
|
1964
|
-
if (!hasErrors(diagnostics)) {
|
|
1965
|
-
|
|
4177
|
+
if (!hasErrors(diagnostics) && template) {
|
|
4178
|
+
const renderResult = compileTemplate(template, {
|
|
4179
|
+
filename: options.filename,
|
|
4180
|
+
jsxRuntime,
|
|
4181
|
+
flavor: "tsx"
|
|
4182
|
+
});
|
|
4183
|
+
code = wrapRenderModuleAsComponent(renderResult.code, componentName, "tsx");
|
|
1966
4184
|
}
|
|
1967
|
-
|
|
4185
|
+
const meta = buildCompileMeta(template, options.filename);
|
|
4186
|
+
return { code, diagnostics, map: void 0, meta };
|
|
4187
|
+
}
|
|
4188
|
+
function convertCollieToTsx(source, options = {}) {
|
|
4189
|
+
const result = compileToTsx(source, options);
|
|
4190
|
+
return {
|
|
4191
|
+
tsx: result.code,
|
|
4192
|
+
diagnostics: result.diagnostics,
|
|
4193
|
+
meta: result.meta
|
|
4194
|
+
};
|
|
1968
4195
|
}
|
|
1969
4196
|
function compileToHtml(sourceOrAst, options = {}) {
|
|
1970
|
-
const document = normalizeDocument(sourceOrAst, options.filename);
|
|
1971
|
-
const diagnostics =
|
|
1972
|
-
const
|
|
1973
|
-
let code = createStubHtml(
|
|
1974
|
-
if (!hasErrors(diagnostics)) {
|
|
1975
|
-
code =
|
|
1976
|
-
}
|
|
1977
|
-
|
|
4197
|
+
const document = normalizeDocument(sourceOrAst, options.filename, options.dialect);
|
|
4198
|
+
const diagnostics = normalizeDiagnostics2(document.diagnostics, options.filename);
|
|
4199
|
+
const template = document.templates[0];
|
|
4200
|
+
let code = createStubHtml();
|
|
4201
|
+
if (!hasErrors(diagnostics) && template) {
|
|
4202
|
+
code = generateHtml(template.ast);
|
|
4203
|
+
}
|
|
4204
|
+
const meta = buildCompileMeta(template, options.filename);
|
|
4205
|
+
return { code, diagnostics, map: void 0, meta };
|
|
1978
4206
|
}
|
|
1979
4207
|
function compile(source, options = {}) {
|
|
1980
4208
|
return compileToJsx(source, options);
|
|
1981
4209
|
}
|
|
1982
|
-
function normalizeDocument(sourceOrAst, filename) {
|
|
4210
|
+
function normalizeDocument(sourceOrAst, filename, dialect) {
|
|
1983
4211
|
if (typeof sourceOrAst === "string") {
|
|
1984
|
-
return parseCollie(sourceOrAst, { filename });
|
|
4212
|
+
return parseCollie(sourceOrAst, { filename, dialect });
|
|
1985
4213
|
}
|
|
1986
4214
|
if (isCollieDocument(sourceOrAst)) {
|
|
1987
4215
|
if (!filename) {
|
|
1988
4216
|
return sourceOrAst;
|
|
1989
4217
|
}
|
|
1990
|
-
return
|
|
4218
|
+
return attachFilenameToDocument(sourceOrAst, filename);
|
|
1991
4219
|
}
|
|
1992
4220
|
if (isRootNode(sourceOrAst)) {
|
|
1993
|
-
|
|
4221
|
+
const id = sourceOrAst.id ?? sourceOrAst.rawId ?? "";
|
|
4222
|
+
const rawId = sourceOrAst.rawId ?? sourceOrAst.id ?? "";
|
|
4223
|
+
const template = {
|
|
4224
|
+
id,
|
|
4225
|
+
rawId,
|
|
4226
|
+
span: sourceOrAst.idTokenSpan,
|
|
4227
|
+
ast: sourceOrAst,
|
|
4228
|
+
diagnostics: []
|
|
4229
|
+
};
|
|
4230
|
+
return { templates: [template], diagnostics: [] };
|
|
1994
4231
|
}
|
|
1995
4232
|
throw new TypeError("Collie compiler expected source text, a parsed document, or a root node.");
|
|
1996
4233
|
}
|
|
@@ -1998,7 +4235,7 @@ function isRootNode(value) {
|
|
|
1998
4235
|
return !!value && typeof value === "object" && value.type === "Root";
|
|
1999
4236
|
}
|
|
2000
4237
|
function isCollieDocument(value) {
|
|
2001
|
-
return !!value && typeof value === "object" &&
|
|
4238
|
+
return !!value && typeof value === "object" && Array.isArray(value.templates) && Array.isArray(value.diagnostics);
|
|
2002
4239
|
}
|
|
2003
4240
|
function hasErrors(diagnostics) {
|
|
2004
4241
|
return diagnostics.some((diag) => diag.severity === "error");
|
|
@@ -2014,21 +4251,87 @@ function createStubComponent(name, flavor) {
|
|
|
2014
4251
|
}
|
|
2015
4252
|
return [`export default function ${name}(props) {`, " return null;", "}"].join("\n");
|
|
2016
4253
|
}
|
|
2017
|
-
function
|
|
2018
|
-
|
|
4254
|
+
function createStubRender(flavor) {
|
|
4255
|
+
if (flavor === "tsx") {
|
|
4256
|
+
return [
|
|
4257
|
+
"export type Props = Record<string, never>;",
|
|
4258
|
+
"export function render(props: any) {",
|
|
4259
|
+
" return null;",
|
|
4260
|
+
"}"
|
|
4261
|
+
].join("\n");
|
|
4262
|
+
}
|
|
4263
|
+
return ["export function render(props) {", " return null;", "}"].join("\n");
|
|
4264
|
+
}
|
|
4265
|
+
function wrapRenderModuleAsComponent(renderModule, name, flavor) {
|
|
4266
|
+
const signature = flavor === "tsx" ? `export default function ${name}(props: Props) {` : `export default function ${name}(props) {`;
|
|
4267
|
+
const wrapper = [signature, " return render(props);", "}"].join("\n");
|
|
4268
|
+
return `${renderModule}
|
|
4269
|
+
|
|
4270
|
+
${wrapper}`;
|
|
4271
|
+
}
|
|
4272
|
+
function createStubHtml() {
|
|
4273
|
+
return "";
|
|
4274
|
+
}
|
|
4275
|
+
function buildCompileMeta(template, filename) {
|
|
4276
|
+
const meta = {};
|
|
4277
|
+
if (filename) {
|
|
4278
|
+
meta.filename = filename;
|
|
4279
|
+
}
|
|
4280
|
+
if (template?.rawId) {
|
|
4281
|
+
meta.rawId = template.rawId;
|
|
4282
|
+
}
|
|
4283
|
+
if (template?.id) {
|
|
4284
|
+
meta.id = template.id;
|
|
4285
|
+
}
|
|
4286
|
+
if (template?.span) {
|
|
4287
|
+
meta.span = template.span;
|
|
4288
|
+
}
|
|
4289
|
+
return meta.id || meta.rawId || meta.filename ? meta : void 0;
|
|
2019
4290
|
}
|
|
2020
|
-
function
|
|
4291
|
+
function attachFilenameToDocument(document, filename) {
|
|
2021
4292
|
if (!filename) {
|
|
2022
|
-
return
|
|
4293
|
+
return document;
|
|
2023
4294
|
}
|
|
2024
|
-
return
|
|
4295
|
+
return {
|
|
4296
|
+
templates: document.templates.map((template) => ({
|
|
4297
|
+
...template,
|
|
4298
|
+
diagnostics: normalizeDiagnostics2(template.diagnostics, filename)
|
|
4299
|
+
})),
|
|
4300
|
+
diagnostics: normalizeDiagnostics2(document.diagnostics, filename)
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
function normalizeDiagnostics2(diagnostics, filename) {
|
|
4304
|
+
return diagnostics.map((diag) => {
|
|
4305
|
+
const filePath = diag.filePath ?? diag.file ?? filename;
|
|
4306
|
+
const file = diag.file ?? filename;
|
|
4307
|
+
const range = diag.range ?? diag.span;
|
|
4308
|
+
if (filePath === diag.filePath && file === diag.file && range === diag.range) {
|
|
4309
|
+
return diag;
|
|
4310
|
+
}
|
|
4311
|
+
return {
|
|
4312
|
+
...diag,
|
|
4313
|
+
filePath,
|
|
4314
|
+
file,
|
|
4315
|
+
range
|
|
4316
|
+
};
|
|
4317
|
+
});
|
|
2025
4318
|
}
|
|
2026
4319
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2027
4320
|
0 && (module.exports = {
|
|
4321
|
+
applyFixes,
|
|
2028
4322
|
compile,
|
|
4323
|
+
compileTemplate,
|
|
2029
4324
|
compileToHtml,
|
|
2030
4325
|
compileToJsx,
|
|
2031
4326
|
compileToTsx,
|
|
4327
|
+
convertCollieToTsx,
|
|
4328
|
+
convertTsxToCollie,
|
|
4329
|
+
defineConfig,
|
|
4330
|
+
fixAllFromDiagnostics,
|
|
4331
|
+
formatCollie,
|
|
4332
|
+
loadAndNormalizeConfig,
|
|
4333
|
+
loadConfig,
|
|
4334
|
+
normalizeConfig,
|
|
2032
4335
|
parse,
|
|
2033
4336
|
parseCollie
|
|
2034
4337
|
});
|