mancha 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,311 @@
1
+ import * as ts from "typescript";
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { JSDOM } from "jsdom";
6
+ import { getAttributeOrDataset } from "./dome.js";
7
+ async function getDiagnostics(content, options) {
8
+ // Use OS temp directory to avoid interfering with project structure
9
+ // but configure baseUrl for correct module resolution if HTML file path is provided
10
+ const baseDir = options.filePath ? path.dirname(path.resolve(options.filePath)) : process.cwd();
11
+ const tempFilePath = path.join(os.tmpdir(), `temp_type_check_${Math.random().toString(36).substring(2, 15)}.ts`);
12
+ await fs.writeFile(tempFilePath, content);
13
+ const host = ts.createCompilerHost({
14
+ noEmit: true,
15
+ strict: options.strict,
16
+ strictNullChecks: options.strict,
17
+ target: ts.ScriptTarget.ES2022,
18
+ module: ts.ModuleKind.ESNext,
19
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
20
+ baseUrl: baseDir,
21
+ skipLibCheck: true,
22
+ skipDefaultLibCheck: false,
23
+ allowImportingTsExtensions: true,
24
+ resolveJsonModule: true,
25
+ allowSyntheticDefaultImports: true,
26
+ });
27
+ const program = ts.createProgram([tempFilePath], {
28
+ noEmit: true,
29
+ strict: options.strict,
30
+ strictNullChecks: options.strict,
31
+ target: ts.ScriptTarget.ES2022,
32
+ module: ts.ModuleKind.ESNext,
33
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
34
+ baseUrl: baseDir,
35
+ skipLibCheck: true,
36
+ skipDefaultLibCheck: false,
37
+ allowImportingTsExtensions: true,
38
+ resolveJsonModule: true,
39
+ allowSyntheticDefaultImports: true,
40
+ }, host);
41
+ const allDiagnostics = ts.getPreEmitDiagnostics(program);
42
+ await fs.unlink(tempFilePath);
43
+ // Filter out irrelevant diagnostics (keep semantic errors 2000-2999 and strict mode errors 18000-18999)
44
+ return allDiagnostics.filter((d) => (d.code >= 2000 && d.code < 3000) || (d.code >= 18000 && d.code < 19000));
45
+ }
46
+ // Replace @import:MODULE_PATH:TYPE_NAME with import("MODULE_PATH").TYPE_NAME
47
+ // and resolve relative paths to absolute paths
48
+ function replaceImportSyntax(typeString, baseDir) {
49
+ return typeString.replace(/@import:([^:]+):([A-Za-z_][A-Za-z0-9_]*)/g, (match, modulePath, typeName) => {
50
+ // If baseDir is provided and the path is relative, resolve it to an absolute path
51
+ if (baseDir && (modulePath.startsWith('./') || modulePath.startsWith('../'))) {
52
+ const absolutePath = path.resolve(baseDir, modulePath);
53
+ return `import("${absolutePath}").${typeName}`;
54
+ }
55
+ return `import("${modulePath}").${typeName}`;
56
+ });
57
+ }
58
+ function buildTypeScriptSource(types, scope, baseDir) {
59
+ const namespace = `M${Math.random().toString(36).substring(2, 15)}`;
60
+ // Apply import syntax transformation to all type values
61
+ const declarations = Array.from(types.entries())
62
+ .map(([key, value]) => {
63
+ const resolvedType = replaceImportSyntax(value, baseDir);
64
+ return `declare let ${key}: ${resolvedType};`;
65
+ })
66
+ .join("\n");
67
+ // Recursively build checks from scope
68
+ const buildChecks = (currentScope, indent = "") => {
69
+ let result = "";
70
+ // Add expressions in current scope
71
+ for (const expr of currentScope.expressions) {
72
+ result += `${indent}(${expr});\n`;
73
+ }
74
+ // Add for loops with their nested scopes
75
+ for (const forLoop of currentScope.forLoops) {
76
+ result += `${indent}for (const ${forLoop.itemName} of ${forLoop.itemsExpression}) {\n`;
77
+ result += buildChecks(forLoop.scope, indent + " ");
78
+ result += `${indent}}\n`;
79
+ }
80
+ return result;
81
+ };
82
+ const checks = buildChecks(scope);
83
+ return `namespace ${namespace} {\n${declarations}\n${checks}}`;
84
+ }
85
+ // Helper to check if an element is nested within another :types element
86
+ function hasTypesAncestor(element) {
87
+ let parent = element.parentElement;
88
+ while (parent) {
89
+ if (getAttributeOrDataset(parent, "types", ":")) {
90
+ return true;
91
+ }
92
+ parent = parent.parentElement;
93
+ }
94
+ return false;
95
+ }
96
+ // Compute type of for-loop variable based on the items expression
97
+ function computeForLoopVariableType(itemsType) {
98
+ // Handle array types: string[] -> string, Array<T> -> T
99
+ if (itemsType.endsWith("[]")) {
100
+ return itemsType.slice(0, -2);
101
+ }
102
+ if (itemsType.match(/^Array<(.+)>$/)) {
103
+ return itemsType.match(/^Array<(.+)>$/)[1];
104
+ }
105
+ // For complex types, try to infer element type
106
+ // This is a simple heuristic - for more complex cases, TypeScript would need to resolve the type
107
+ return "any";
108
+ }
109
+ // Find for-loop ancestors of an element and compute their variable types
110
+ function getForLoopContext(element, typesMap) {
111
+ const forLoopTypes = new Map();
112
+ let current = element.parentElement;
113
+ while (current) {
114
+ const forAttr = getAttributeOrDataset(current, "for", ":");
115
+ if (forAttr) {
116
+ const parts = forAttr.split(" in ");
117
+ const itemName = parts[0].trim();
118
+ const itemsExpression = parts[1].trim();
119
+ // Try to determine the type of the items expression
120
+ // First check if it's a simple variable name in our types map
121
+ if (typesMap.has(itemsExpression)) {
122
+ const itemsType = typesMap.get(itemsExpression);
123
+ const itemType = computeForLoopVariableType(itemsType);
124
+ forLoopTypes.set(itemName, itemType);
125
+ }
126
+ else if (forLoopTypes.has(itemsExpression)) {
127
+ // Check if it's a for-loop variable from an outer loop (for shadowing)
128
+ const itemsType = forLoopTypes.get(itemsExpression);
129
+ const itemType = computeForLoopVariableType(itemsType);
130
+ forLoopTypes.set(itemName, itemType);
131
+ }
132
+ else {
133
+ // Try to infer from more complex expressions like user.scores
134
+ // For now, use a simple heuristic
135
+ const match = itemsExpression.match(/^(\w+)\.(\w+)$/);
136
+ if (match && forLoopTypes.has(match[1])) {
137
+ // e.g., user.scores where user is already in forLoopTypes
138
+ const parentType = forLoopTypes.get(match[1]);
139
+ const propertyName = match[2];
140
+ // Extract property type from parent type (simple heuristic)
141
+ const propMatch = parentType.match(new RegExp(`${propertyName}:\\s*([^,}]+)`));
142
+ if (propMatch) {
143
+ const propType = propMatch[1].trim();
144
+ const itemType = computeForLoopVariableType(propType);
145
+ forLoopTypes.set(itemName, itemType);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ // Stop if we hit a :types boundary (don't cross into parent :types scopes)
151
+ if (current !== element && getAttributeOrDataset(current, "types", ":")) {
152
+ break;
153
+ }
154
+ current = current.parentElement;
155
+ }
156
+ return forLoopTypes;
157
+ }
158
+ // Recursively process a :types element and its nested :types descendants
159
+ async function processTypesElement(element, parentTypes, options, processedElements) {
160
+ // Skip if already processed
161
+ if (processedElements.has(element)) {
162
+ return [];
163
+ }
164
+ processedElements.add(element);
165
+ const allDiagnostics = [];
166
+ const typesAttr = getAttributeOrDataset(element, "types", ":");
167
+ if (!typesAttr)
168
+ return allDiagnostics;
169
+ try {
170
+ // Parse types for this element
171
+ const elementTypes = new Map(Object.entries(JSON.parse(typesAttr)));
172
+ // Merge with parent types (element types override parent types)
173
+ const mergedTypes = new Map([...parentTypes, ...elementTypes]);
174
+ // Get expressions for this element (excluding nested :types descendants)
175
+ const expressions = getExpressionsExcludingNestedTypes(element);
176
+ // Type check expressions in this scope
177
+ const baseDir = options.filePath ? path.dirname(path.resolve(options.filePath)) : undefined;
178
+ const source = buildTypeScriptSource(mergedTypes, expressions, baseDir);
179
+ const diagnostics = await getDiagnostics(source, options);
180
+ allDiagnostics.push(...diagnostics);
181
+ // Find and process nested :types elements
182
+ const nestedTypesElements = findDirectNestedTypesElements(element);
183
+ for (const nestedElement of nestedTypesElements) {
184
+ // Compute for-loop context for the nested element
185
+ const forLoopContext = getForLoopContext(nestedElement, mergedTypes);
186
+ // Merge for-loop variables with parent types (for-loop variables take precedence)
187
+ const nestedParentTypes = new Map([...mergedTypes, ...forLoopContext]);
188
+ const nestedDiagnostics = await processTypesElement(nestedElement, nestedParentTypes, options, processedElements);
189
+ allDiagnostics.push(...nestedDiagnostics);
190
+ }
191
+ }
192
+ catch (e) {
193
+ console.error("Error parsing :types attribute:", e);
194
+ }
195
+ return allDiagnostics;
196
+ }
197
+ // Find nested :types elements that are direct descendants (not grandchildren through other :types)
198
+ function findDirectNestedTypesElements(element) {
199
+ const result = [];
200
+ const walker = element.ownerDocument.createTreeWalker(element, 1); // 1 = SHOW_ELEMENT
201
+ while (walker.nextNode()) {
202
+ const node = walker.currentNode;
203
+ if (node === element)
204
+ continue;
205
+ const typesAttr = getAttributeOrDataset(node, "types", ":");
206
+ if (typesAttr) {
207
+ // Check if this is a direct nested :types (not nested within another :types first)
208
+ let parent = node.parentElement;
209
+ let foundIntermediateTypes = false;
210
+ while (parent && parent !== element) {
211
+ if (getAttributeOrDataset(parent, "types", ":")) {
212
+ foundIntermediateTypes = true;
213
+ break;
214
+ }
215
+ parent = parent.parentElement;
216
+ }
217
+ if (!foundIntermediateTypes) {
218
+ result.push(node);
219
+ }
220
+ }
221
+ }
222
+ return result;
223
+ }
224
+ // Get expressions excluding those in nested :types elements
225
+ function getExpressionsExcludingNestedTypes(root) {
226
+ const scope = { expressions: [], forLoops: [] };
227
+ const processedForElements = new Set();
228
+ const processElement = (element, currentScope) => {
229
+ // Stop if we encounter a nested :types element
230
+ if (element !== root && getAttributeOrDataset(element, "types", ":")) {
231
+ return;
232
+ }
233
+ // Check if this element has a :for attribute
234
+ const forAttr = getAttributeOrDataset(element, "for", ":");
235
+ if (forAttr) {
236
+ if (processedForElements.has(element))
237
+ return;
238
+ processedForElements.add(element);
239
+ const parts = forAttr.split(" in ");
240
+ const itemName = parts[0].trim();
241
+ const itemsExpression = parts[1].trim();
242
+ currentScope.expressions.push(itemsExpression);
243
+ const forScope = { expressions: [], forLoops: [] };
244
+ processDescendants(element, forScope);
245
+ currentScope.forLoops.push({ itemName, itemsExpression, scope: forScope });
246
+ }
247
+ else {
248
+ // Process attributes
249
+ for (const attr of Array.from(element.attributes)) {
250
+ if ((attr.name.startsWith(":") || attr.name.startsWith("data-")) &&
251
+ attr.name !== ":types" &&
252
+ attr.name !== "data-types") {
253
+ currentScope.expressions.push(attr.value);
254
+ }
255
+ }
256
+ // Process children
257
+ for (const child of Array.from(element.childNodes)) {
258
+ if (child.nodeType === 1) {
259
+ processElement(child, currentScope);
260
+ }
261
+ else if (child.nodeType === 3) {
262
+ processTextNode(child, currentScope);
263
+ }
264
+ }
265
+ }
266
+ };
267
+ const processDescendants = (element, currentScope) => {
268
+ for (const child of Array.from(element.childNodes)) {
269
+ if (child.nodeType === 1) {
270
+ processElement(child, currentScope);
271
+ }
272
+ else if (child.nodeType === 3) {
273
+ processTextNode(child, currentScope);
274
+ }
275
+ }
276
+ };
277
+ const processTextNode = (textNode, currentScope) => {
278
+ const text = textNode.nodeValue;
279
+ if (text) {
280
+ const matches = text.matchAll(/{{(.*?)}}/g);
281
+ for (const match of matches) {
282
+ currentScope.expressions.push(match[1].trim());
283
+ }
284
+ }
285
+ };
286
+ // Start processing from root's children
287
+ for (const child of Array.from(root.childNodes)) {
288
+ if (child.nodeType === 1) {
289
+ processElement(child, scope);
290
+ }
291
+ else if (child.nodeType === 3) {
292
+ processTextNode(child, scope);
293
+ }
294
+ }
295
+ return scope;
296
+ }
297
+ export async function typeCheck(html, options) {
298
+ const dom = new JSDOM(html);
299
+ const allTypeNodes = dom.window.document.querySelectorAll("[\\:types], [data-types]");
300
+ const allDiagnostics = [];
301
+ const processedElements = new Set();
302
+ // Find top-level :types elements (not nested within other :types)
303
+ const topLevelTypeNodes = Array.from(allTypeNodes).filter((node) => !hasTypesAncestor(node));
304
+ // Process each top-level :types element and its nested descendants
305
+ for (const node of topLevelTypeNodes) {
306
+ const diagnostics = await processTypesElement(node, new Map(), options, processedElements);
307
+ allDiagnostics.push(...diagnostics);
308
+ }
309
+ return allDiagnostics;
310
+ }
311
+ //# sourceMappingURL=type_checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type_checker.js","sourceRoot":"","sources":["../src/type_checker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAOlD,KAAK,UAAU,cAAc,CAC3B,OAAe,EACf,OAAyB;IAEzB,oEAAoE;IACpE,oFAAoF;IACpF,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAChG,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAC5B,EAAE,CAAC,MAAM,EAAE,EACX,mBAAmB,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CACpE,CAAC;IAEF,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAE1C,MAAM,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC;QACjC,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,gBAAgB,EAAE,OAAO,CAAC,MAAM;QAChC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM;QAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM;QAC5B,gBAAgB,EAAE,EAAE,CAAC,oBAAoB,CAAC,OAAO;QACjD,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE,IAAI;QAClB,mBAAmB,EAAE,KAAK;QAC1B,0BAA0B,EAAE,IAAI;QAChC,iBAAiB,EAAE,IAAI;QACvB,4BAA4B,EAAE,IAAI;KACnC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,EAAE;QAC/C,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,gBAAgB,EAAE,OAAO,CAAC,MAAM;QAChC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM;QAC9B,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM;QAC5B,gBAAgB,EAAE,EAAE,CAAC,oBAAoB,CAAC,OAAO;QACjD,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE,IAAI;QAClB,mBAAmB,EAAE,KAAK;QAC1B,0BAA0B,EAAE,IAAI;QAChC,iBAAiB,EAAE,IAAI;QACvB,4BAA4B,EAAE,IAAI;KACnC,EAAE,IAAI,CAAC,CAAC;IAET,MAAM,cAAc,GAAG,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAEzD,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAE9B,wGAAwG;IACxG,OAAO,cAAc,CAAC,MAAM,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAChF,CAAC;AACJ,CAAC;AAWD,6EAA6E;AAC7E,+CAA+C;AAC/C,SAAS,mBAAmB,CAAC,UAAkB,EAAE,OAAgB;IAC/D,OAAO,UAAU,CAAC,OAAO,CAAC,2CAA2C,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;QACrG,kFAAkF;QAClF,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YACvD,OAAO,WAAW,YAAY,MAAM,QAAQ,EAAE,CAAC;QACjD,CAAC;QACD,OAAO,WAAW,UAAU,MAAM,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,KAA0B,EAAE,KAAsB,EAAE,OAAgB;IACjG,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAEpE,wDAAwD;IACxD,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;SAC7C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACpB,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzD,OAAO,eAAe,GAAG,KAAK,YAAY,GAAG,CAAC;IAChD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,sCAAsC;IACtC,MAAM,WAAW,GAAG,CAAC,YAA6B,EAAE,SAAiB,EAAE,EAAU,EAAE;QACjF,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,mCAAmC;QACnC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,MAAM,CAAC;QACpC,CAAC;QAED,yCAAyC;QACzC,KAAK,MAAM,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,cAAc,OAAO,CAAC,QAAQ,OAAO,OAAO,CAAC,eAAe,OAAO,CAAC;YACvF,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC;QAC3B,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,aAAa,SAAS,OAAO,YAAY,KAAK,MAAM,GAAG,CAAC;AACjE,CAAC;AAED,wEAAwE;AACxE,SAAS,gBAAgB,CAAC,OAAgB;IACxC,IAAI,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IACnC,OAAO,MAAM,EAAE,CAAC;QACd,IAAI,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kEAAkE;AAClE,SAAS,0BAA0B,CAAC,SAAiB;IACnD,wDAAwD;IACxD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,SAAS,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC,KAAK,CAAC,eAAe,CAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,+CAA+C;IAC/C,iGAAiG;IACjG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,yEAAyE;AACzE,SAAS,iBAAiB,CAAC,OAAgB,EAAE,QAA6B;IACxE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,IAAI,OAAO,GAAmB,OAAO,CAAC,aAAa,CAAC;IAEpD,OAAO,OAAO,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3D,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAExC,oDAAoD;YACpD,8DAA8D;YAC9D,IAAI,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;gBAClC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAE,CAAC;gBACjD,MAAM,QAAQ,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC;gBACvD,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACvC,CAAC;iBAAM,IAAI,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC7C,uEAAuE;gBACvE,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,eAAe,CAAE,CAAC;gBACrD,MAAM,QAAQ,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC;gBACvD,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,8DAA8D;gBAC9D,kCAAkC;gBAClC,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;gBACtD,IAAI,KAAK,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACxC,0DAA0D;oBAC1D,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC;oBAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC9B,4DAA4D;oBAC5D,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY,eAAe,CAAC,CAAC,CAAC;oBAC/E,IAAI,SAAS,EAAE,CAAC;wBACd,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;wBACrC,MAAM,QAAQ,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;wBACtD,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBACvC,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,2EAA2E;QAC3E,IAAI,OAAO,KAAK,OAAO,IAAI,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;YACxE,MAAM;QACR,CAAC;QAED,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IAClC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,yEAAyE;AACzE,KAAK,UAAU,mBAAmB,CAChC,OAAgB,EAChB,WAAgC,EAChC,OAAyB,EACzB,iBAA+B;IAE/B,4BAA4B;IAC5B,IAAI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAE/B,MAAM,cAAc,GAAoB,EAAE,CAAC;IAC3C,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS;QAAE,OAAO,cAAc,CAAC;IAEtC,IAAI,CAAC;QACH,+BAA+B;QAC/B,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAA8B,CAAC,CACnE,CAAC;QAEF,gEAAgE;QAChE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC;QAE/D,yEAAyE;QACzE,MAAM,WAAW,GAAG,kCAAkC,CAAC,OAAO,CAAC,CAAC;QAEhE,uCAAuC;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,MAAM,MAAM,GAAG,qBAAqB,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACxE,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1D,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;QAEpC,0CAA0C;QAC1C,MAAM,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,CAAC;QACnE,KAAK,MAAM,aAAa,IAAI,mBAAmB,EAAE,CAAC;YAChD,kDAAkD;YAClD,MAAM,cAAc,GAAG,iBAAiB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;YAErE,kFAAkF;YAClF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC;YAEvE,MAAM,iBAAiB,GAAG,MAAM,mBAAmB,CACjD,aAAa,EACb,iBAAiB,EACjB,OAAO,EACP,iBAAiB,CAClB,CAAC;YACF,cAAc,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,mGAAmG;AACnG,SAAS,6BAA6B,CAAC,OAAgB;IACrD,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB;IAEtF,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,WAAsB,CAAC;QAC3C,IAAI,IAAI,KAAK,OAAO;YAAE,SAAS;QAE/B,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,SAAS,EAAE,CAAC;YACd,mFAAmF;YACnF,IAAI,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC;YAChC,IAAI,sBAAsB,GAAG,KAAK,CAAC;YACnC,OAAO,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACpC,IAAI,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;oBAChD,sBAAsB,GAAG,IAAI,CAAC;oBAC9B,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4DAA4D;AAC5D,SAAS,kCAAkC,CAAC,IAAa;IACvD,MAAM,KAAK,GAAoB,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACjE,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAW,CAAC;IAEhD,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAE,YAA6B,EAAE,EAAE;QACzE,+CAA+C;QAC/C,IAAI,OAAO,KAAK,IAAI,IAAI,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;YACrE,OAAO;QACT,CAAC;QAED,6CAA6C;QAC7C,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3D,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO;YAC9C,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAElC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAExC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAE/C,MAAM,QAAQ,GAAoB,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;YACpE,kBAAkB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACtC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,qBAAqB;YACrB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClD,IACE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;oBAC5D,IAAI,CAAC,IAAI,KAAK,QAAQ;oBACtB,IAAI,CAAC,IAAI,KAAK,YAAY,EAC1B,CAAC;oBACD,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC5C,CAAC;YACH,CAAC;YAED,mBAAmB;YACnB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnD,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACzB,cAAc,CAAC,KAAgB,EAAE,YAAY,CAAC,CAAC;gBACjD,CAAC;qBAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBAChC,eAAe,CAAC,KAAa,EAAE,YAAY,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,kBAAkB,GAAG,CAAC,OAAgB,EAAE,YAA6B,EAAE,EAAE;QAC7E,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACnD,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACzB,cAAc,CAAC,KAAgB,EAAE,YAAY,CAAC,CAAC;YACjD,CAAC;iBAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAChC,eAAe,CAAC,KAAa,EAAE,YAAY,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,CAAC,QAAc,EAAE,YAA6B,EAAE,EAAE;QACxE,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC;QAChC,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAC5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,wCAAwC;IACxC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAChD,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACzB,cAAc,CAAC,KAAgB,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAChC,eAAe,CAAC,KAAa,EAAE,KAAK,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,OAAyB;IACrE,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;IAE5B,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC;IAEtF,MAAM,cAAc,GAAoB,EAAE,CAAC;IAC3C,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAW,CAAC;IAE7C,kEAAkE;IAClE,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7F,mEAAmE;IACnE,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAC3F,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -41,8 +41,8 @@ For example, a basic form might look like this
41
41
 
42
42
  In the code above, the `:on:submit` tag simply prints 'submitted' to the console. Note that `Mancha`
43
43
  automatically prevents the form submission from refreshing the page by calling
44
- `event.prevenDefault()`. You can also trigger this behavior with other events by adding a `:prevent`
45
- attribute to the element. To provide more complex handlers, you can define callbacks as a function:
44
+ `event.preventDefault()`. You can also trigger this behavior with other events by adding a `.prevent`
45
+ modifier to the event attribute, for example `:on:click.prevent`. To provide more complex handlers, you can define callbacks as a function:
46
46
 
47
47
  ```html
48
48
  <body :data="{ name: null, message: null }">
@@ -279,3 +279,282 @@ describe("My Component", () => {
279
279
  });
280
280
  ```
281
281
 
282
+ ## Type Checking (Experimental)
283
+
284
+ **⚠️ This feature is experimental and may change in future versions.**
285
+
286
+ `mancha` includes an experimental type checker that can validate your template expressions using TypeScript. This helps catch type errors during development before they become runtime errors.
287
+
288
+ ### Basic Type Checking
289
+
290
+ Use the `:types` attribute to declare types for variables in your templates:
291
+
292
+ ```html
293
+ <div :types='{"name": "string", "age": "number"}'>
294
+ <span>{{ name.toUpperCase() }}</span>
295
+ <span>{{ age.toFixed(0) }}</span>
296
+ </div>
297
+ ```
298
+
299
+ The type checker will validate that:
300
+ - `name.toUpperCase()` is valid (string has toUpperCase method)
301
+ - `age.toFixed(0)` is valid (number has toFixed method)
302
+ - Using `name.toFixed()` would be an error (string doesn't have toFixed)
303
+
304
+ ### Running the Type Checker
305
+
306
+ ```bash
307
+ # Check a single file
308
+ npx mancha check src/index.html
309
+
310
+ # Check with strict mode
311
+ npx mancha check src/index.html --strict
312
+ ```
313
+
314
+ ### Stripping Types in Production
315
+
316
+ The `:types` attributes are only used for static analysis and have no runtime behavior. However, you may want to remove them from your production HTML to reduce file size and avoid exposing type information:
317
+
318
+ ```bash
319
+ # Render with types stripped
320
+ npx mancha render src/index.html --output public/index.html --strip-types
321
+
322
+ # Render without stripping (default)
323
+ npx mancha render src/index.html --output public/index.html
324
+ ```
325
+
326
+ The `--strip-types` flag removes all `:types` and `data-types` attributes from the rendered output.
327
+
328
+ ### Type Checking with For-Loops
329
+
330
+ The type checker understands `:for` loops and infers the item type from the array:
331
+
332
+ ```html
333
+ <div :types='{"users": "{ name: string, age: number }[]"}'>
334
+ <ul :for="user in users">
335
+ <!-- 'user' is automatically typed as { name: string, age: number } -->
336
+ <li>{{ user.name.toUpperCase() }}</li>
337
+ <li>{{ user.age.toFixed(0) }}</li>
338
+ </ul>
339
+ </div>
340
+ ```
341
+
342
+ ### Nested Scopes
343
+
344
+ Child scopes inherit types from parent scopes:
345
+
346
+ ```html
347
+ <div :types='{"name": "string", "age": "number"}'>
348
+ <span>{{ name.toUpperCase() }}</span>
349
+
350
+ <div :types='{"city": "string"}'>
351
+ <!-- This scope has access to: name, age, and city -->
352
+ <span>{{ name.toLowerCase() }}</span>
353
+ <span>{{ city.toUpperCase() }}</span>
354
+ <span>{{ age.toFixed(0) }}</span>
355
+ </div>
356
+ </div>
357
+ ```
358
+
359
+ Child scopes can override parent types:
360
+
361
+ ```html
362
+ <div :types='{"value": "string"}'>
363
+ <span>{{ value.toUpperCase() }}</span>
364
+
365
+ <div :types='{"value": "number"}'>
366
+ <!-- 'value' is now number, not string -->
367
+ <span>{{ value.toFixed(2) }}</span>
368
+ </div>
369
+
370
+ <!-- Back to string scope -->
371
+ <span>{{ value.toLowerCase() }}</span>
372
+ </div>
373
+ ```
374
+
375
+ ### Importing Types (Experimental)
376
+
377
+ **⚠️ This feature is highly experimental and the syntax may change.**
378
+
379
+ You can import TypeScript types from external files using the `@import:` syntax:
380
+
381
+ ```typescript
382
+ // types/user.ts
383
+ export interface User {
384
+ id: number;
385
+ name: string;
386
+ email: string;
387
+ isAdmin: boolean;
388
+ }
389
+
390
+ export interface Product {
391
+ id: number;
392
+ name: string;
393
+ price: number;
394
+ }
395
+ ```
396
+
397
+ ```html
398
+ <!-- Import a single type -->
399
+ <div :types='{"user": "@import:./types/user.ts:User"}'>
400
+ <span>{{ user.name.toUpperCase() }}</span>
401
+ <span>{{ user.email.toLowerCase() }}</span>
402
+ <span :show="user.isAdmin">Admin Badge</span>
403
+ </div>
404
+ ```
405
+
406
+ #### Import Syntax
407
+
408
+ The format is: `@import:MODULE_PATH:TYPE_NAME`
409
+
410
+ - **MODULE_PATH**:
411
+ - Starts with `.` → relative path (e.g., `./types/user.ts`)
412
+ - No `.` → library import (e.g., `moment`)
413
+ - **TYPE_NAME**: The exported type/interface name
414
+
415
+ #### Arrays of Imported Types
416
+
417
+ ```html
418
+ <div :types='{"users": "@import:./types/user.ts:User[]"}'>
419
+ <ul :for="user in users">
420
+ <li>{{ user.name }} - {{ user.email }}</li>
421
+ </ul>
422
+ </div>
423
+ ```
424
+
425
+ #### Multiple Imports
426
+
427
+ ```html
428
+ <div :types='{
429
+ "user": "@import:./types/user.ts:User",
430
+ "product": "@import:./types/user.ts:Product",
431
+ "count": "number"
432
+ }'>
433
+ <span>{{ user.name }}</span>
434
+ <span>{{ product.name }} - ${{ product.price.toFixed(2) }}</span>
435
+ <span>Total: {{ count }}</span>
436
+ </div>
437
+ ```
438
+
439
+ #### Imports in Complex Types
440
+
441
+ Use imports anywhere you'd use a type:
442
+
443
+ ```html
444
+ <!-- In object types -->
445
+ <div :types='{"response": "{ data: @import:./types/user.ts:User[], total: number }"}'>
446
+ <span>Total users: {{ response.total }}</span>
447
+ <ul :for="user in response.data">
448
+ <li>{{ user.name }}</li>
449
+ </ul>
450
+ </div>
451
+
452
+ <!-- With generics -->
453
+ <div :types='{"response": "@import:./api.ts:ApiResponse<@import:./types/user.ts:User>"}'>
454
+ <span>{{ response.data.name }}</span>
455
+ <span>Status: {{ response.status }}</span>
456
+ </div>
457
+
458
+ <!-- With unions -->
459
+ <div :types='{"user": "@import:./types/user.ts:User | null"}'>
460
+ <span :show="user !== null">{{ user.name }}</span>
461
+ <span :show="user === null">Not logged in</span>
462
+ </div>
463
+ ```
464
+
465
+ #### Nested Scopes with Imports
466
+
467
+ Imports are inherited by nested scopes:
468
+
469
+ ```html
470
+ <div :types='{"user": "@import:./types/user.ts:User"}'>
471
+ <span>{{ user.name }}</span>
472
+
473
+ <div :types='{"product": "@import:./types/user.ts:Product"}'>
474
+ <!-- Has access to both User and Product types -->
475
+ <span>{{ user.name }} bought {{ product.name }}</span>
476
+ </div>
477
+ </div>
478
+ ```
479
+
480
+ #### Complex Example
481
+
482
+ ```html
483
+ <div :types='{"orders": "@import:./types/orders.ts:Order[]"}'>
484
+ <div :for="order in orders">
485
+ <h2>Order #{{ order.id }}</h2>
486
+ <p>Customer: {{ order.customer.name }}</p>
487
+
488
+ <div :types='{"selectedProduct": "@import:./types/products.ts:Product"}'>
489
+ <ul :for="item in order.items">
490
+ <li>
491
+ {{ item.product.name }} x {{ item.quantity }}
492
+ = ${{ (item.product.price * item.quantity).toFixed(2) }}
493
+ </li>
494
+ </ul>
495
+
496
+ <div :show="selectedProduct">
497
+ <h3>Selected: {{ selectedProduct.name }}</h3>
498
+ <p>${{ selectedProduct.price.toFixed(2) }}</p>
499
+ </div>
500
+ </div>
501
+
502
+ <p>Total: ${{ order.total.toFixed(2) }}</p>
503
+ </div>
504
+ </div>
505
+ ```
506
+
507
+ ### Best Practices
508
+
509
+ 1. **Start Simple**: Add types gradually, starting with the most critical paths
510
+ 2. **Use Strict Mode**: Enable strict mode in your TypeScript config for better type safety
511
+ 3. **Import Shared Types**: Keep commonly used types in separate files and import them
512
+ 4. **Document Complex Types**: Add comments for complex object structures
513
+ 5. **Test Your Types**: Run the type checker in your CI/CD pipeline
514
+
515
+ ### Limitations
516
+
517
+ - The type checker analyzes templates statically, not at runtime
518
+ - Some dynamic JavaScript patterns may not be fully supported
519
+ - Import paths must be resolvable by TypeScript
520
+ - The import syntax is experimental and may change
521
+
522
+ ### Examples
523
+
524
+ #### Form Validation
525
+
526
+ ```html
527
+ <div :types='{
528
+ "formData": "{ name: string, email: string, age: number }",
529
+ "errors": "{ name?: string, email?: string, age?: string }"
530
+ }'>
531
+ <form>
532
+ <input type="text" :bind="formData.name" />
533
+ <span :show="errors.name" class="error">{{ errors.name }}</span>
534
+
535
+ <input type="email" :bind="formData.email" />
536
+ <span :show="errors.email" class="error">{{ errors.email }}</span>
537
+
538
+ <input type="number" :bind="formData.age" />
539
+ <span :show="errors.age" class="error">{{ errors.age }}</span>
540
+ </form>
541
+ </div>
542
+ ```
543
+
544
+ #### API Response Handling
545
+
546
+ ```html
547
+ <div :types='{
548
+ "response": "@import:./api.ts:ApiResponse<@import:./types/user.ts:User>",
549
+ "loading": "boolean",
550
+ "error": "string | null"
551
+ }'>
552
+ <div :show="loading">Loading...</div>
553
+ <div :show="error">Error: {{ error }}</div>
554
+ <div :show="!loading && !error && response">
555
+ <h1>{{ response.data.name }}</h1>
556
+ <p>{{ response.data.email }}</p>
557
+ </div>
558
+ </div>
559
+ ```
560
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mancha",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Javscript HTML rendering engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -10,9 +10,10 @@
10
10
  "clean": "rm -rf dist/",
11
11
  "build": "npx gulp build",
12
12
  "prepare": "npm run build",
13
- "test_node": "mocha dist/*.test.js",
14
- "test_browser": "web-test-runner dist/*browser.test.js --node-resolve --compatibility all --playwright --browsers chromium --no-sandbox",
15
- "test": "npm run test_node && npm run test_browser",
13
+ "pretest": "npm run build",
14
+ "test:node": "mocha dist/*.test.js",
15
+ "test:browser": "web-test-runner dist/*browser.test.js --node-resolve --compatibility all --playwright --browsers chromium --no-sandbox",
16
+ "test": "npm run test:node && npm run test:browser",
16
17
  "check_size": "brotli -c dist/mancha.js | wc -c",
17
18
  "cli": "node dist/cli.js"
18
19
  },
@@ -35,20 +36,22 @@
35
36
  "dom-serializer": "^2.0.0",
36
37
  "htmlparser2": "^9.1.0",
37
38
  "jexpr": "^1.0.0-pre.9",
38
- "jsdom": "^24.1.3",
39
+ "jsdom": "^27.0.1",
40
+ "mancha": "file:",
39
41
  "safevalues": "^0.6.0"
40
42
  },
41
43
  "devDependencies": {
42
- "@types/chai": "^5.0.1",
44
+ "@types/chai": "^5.2.3",
43
45
  "@types/chai-as-promised": "^8.0.1",
44
46
  "@types/jsdom": "^21.1.6",
45
47
  "@types/mocha": "^10.0.10",
46
48
  "@types/node": "^20.12.11",
47
49
  "@types/path-browserify": "^1.0.1",
50
+ "@types/trusted-types": "^2.0.7",
48
51
  "@types/yargs": "^17.0.29",
49
52
  "@web/test-runner": "^0.19.0",
50
53
  "@web/test-runner-playwright": "^0.11.1",
51
- "chai": "^5.1.2",
54
+ "chai": "^5.3.3",
52
55
  "chai-as-promised": "^8.0.1",
53
56
  "css-loader": "^7.1.2",
54
57
  "csso": "^5.0.5",
@@ -60,7 +63,7 @@
60
63
  "terser-webpack-plugin": "^5.3.10",
61
64
  "ts-node": "^10.9.2",
62
65
  "tsec": "^0.2.8",
63
- "typescript": "^5.4.5",
66
+ "typescript": "^5.9.3",
64
67
  "webpack": "^5.97.1",
65
68
  "webpack-cli": "^5.1.4",
66
69
  "yargs": "^17.7.2"