coffeehaml 0.6.0 → 0.7.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.
Files changed (66) hide show
  1. package/README.md +21 -5
  2. package/dist/ast.js +334 -123
  3. package/dist/cli.js +363 -291
  4. package/dist/compiler.js +126 -89
  5. package/dist/emitter.js +771 -736
  6. package/dist/errors.js +5 -2
  7. package/dist/expressions.js +211 -154
  8. package/dist/index.js +22 -4
  9. package/dist/lexer.js +536 -340
  10. package/dist/parser.js +533 -432
  11. package/dist/prettier/coffeescript-formatter.js +309 -0
  12. package/dist/prettier/index.js +105 -0
  13. package/dist/prettier/options.js +232 -0
  14. package/dist/prettier/printer.js +559 -0
  15. package/dist/src/ast.js +338 -0
  16. package/dist/src/cli.js +430 -0
  17. package/dist/src/compiler.js +132 -0
  18. package/dist/src/emitter.js +791 -0
  19. package/dist/src/errors.js +5 -0
  20. package/dist/src/expressions.js +223 -0
  21. package/dist/src/index.js +22 -0
  22. package/dist/src/lexer.js +504 -0
  23. package/dist/src/parser.js +575 -0
  24. package/dist/src/types.js +122 -0
  25. package/dist/src/vite-plugin.js +78 -0
  26. package/dist/types.js +122 -12
  27. package/dist/vite-plugin.js +78 -63
  28. package/docs/examples.md +201 -1
  29. package/docs/grammar.md +90 -1
  30. package/docs/index.html +260 -37
  31. package/package.json +12 -10
  32. package/syntaxes/CoffeeHaml.tmLanguage +815 -868
  33. package/syntaxes/coffeehaml.tmLanguage.json +352 -161
  34. package/dist/ast.d.ts +0 -117
  35. package/dist/ast.d.ts.map +0 -1
  36. package/dist/ast.js.map +0 -1
  37. package/dist/cli.d.ts +0 -3
  38. package/dist/cli.d.ts.map +0 -1
  39. package/dist/cli.js.map +0 -1
  40. package/dist/compiler.d.ts +0 -10
  41. package/dist/compiler.d.ts.map +0 -1
  42. package/dist/compiler.js.map +0 -1
  43. package/dist/emitter.d.ts +0 -4
  44. package/dist/emitter.d.ts.map +0 -1
  45. package/dist/emitter.js.map +0 -1
  46. package/dist/errors.d.ts +0 -3
  47. package/dist/errors.d.ts.map +0 -1
  48. package/dist/errors.js.map +0 -1
  49. package/dist/expressions.d.ts +0 -22
  50. package/dist/expressions.d.ts.map +0 -1
  51. package/dist/expressions.js.map +0 -1
  52. package/dist/index.d.ts +0 -5
  53. package/dist/index.d.ts.map +0 -1
  54. package/dist/index.js.map +0 -1
  55. package/dist/lexer.d.ts +0 -30
  56. package/dist/lexer.d.ts.map +0 -1
  57. package/dist/lexer.js.map +0 -1
  58. package/dist/parser.d.ts +0 -10
  59. package/dist/parser.d.ts.map +0 -1
  60. package/dist/parser.js.map +0 -1
  61. package/dist/types.d.ts +0 -76
  62. package/dist/types.d.ts.map +0 -1
  63. package/dist/types.js.map +0 -1
  64. package/dist/vite-plugin.d.ts +0 -7
  65. package/dist/vite-plugin.d.ts.map +0 -1
  66. package/dist/vite-plugin.js.map +0 -1
@@ -0,0 +1,504 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ // lexer.coffee — CoffeeHaml tokenizer
3
+ // Converts raw .chaml source into a token stream with INDENT/DEDENT
4
+ var countIndent, dedentToken, extractBracketed, indentToken, isHamlConstruct, parseModifiersAndAttrs, tokenizeLine;
5
+
6
+ import {
7
+ SourceLocation
8
+ } from './types.js';
9
+
10
+ // ─── Token Types ───────────────────────────────────────────
11
+ export var TokenType = {
12
+ TAG: 'TAG',
13
+ CLASS: 'CLASS',
14
+ ID: 'ID',
15
+ ATTRS_PAREN: 'ATTRS_PAREN',
16
+ ATTRS_BRACE: 'ATTRS_BRACE',
17
+ OUTPUT: 'OUTPUT',
18
+ OUTPUT_UNESC: 'OUTPUT_UNESC',
19
+ CONTROL: 'CONTROL',
20
+ COMMENT: 'COMMENT',
21
+ HTML_COMMENT: 'HTML_COMMENT',
22
+ FILTER: 'FILTER',
23
+ DOCTYPE: 'DOCTYPE',
24
+ TEXT: 'TEXT',
25
+ PROLOGUE: 'PROLOGUE',
26
+ INDENT: 'INDENT',
27
+ DEDENT: 'DEDENT',
28
+ NEWLINE: 'NEWLINE',
29
+ SELF_CLOSE: 'SELF_CLOSE'
30
+ };
31
+
32
+ // ─── Token (plain object convention) ───────────────────────
33
+ // { type, value, location, indent? }
34
+
35
+ // ─── Main entry ────────────────────────────────────────────
36
+ export var tokenize = function(source, filename = null) {
37
+ var content, currentIndent, i, inPrologue, indent, indentStack, len, line, lineIndex, lineLength, lineStartOffset, lineTokens, lines, offset, rawLine, tokens;
38
+ tokens = [];
39
+ lines = source.split('\n');
40
+ indentStack = [0];
41
+ offset = 0;
42
+ lineIndex = 0;
43
+ inPrologue = true;
44
+ for (i = 0, len = lines.length; i < len; i++) {
45
+ rawLine = lines[i];
46
+ line = rawLine.replace(/\r$/, '');
47
+ lineLength = rawLine.length + 1;
48
+ // Skip empty lines
49
+ if (line.trim() === '') {
50
+ offset += lineLength;
51
+ lineIndex++;
52
+ continue;
53
+ }
54
+ indent = countIndent(line);
55
+ content = line.slice(indent);
56
+ if (content === '') {
57
+ offset += lineLength;
58
+ lineIndex++;
59
+ continue;
60
+ }
61
+ // Prologue detection: non-indented JS before first HAML construct
62
+ if (inPrologue && indent === 0 && !isHamlConstruct(content)) {
63
+ tokens.push({
64
+ type: TokenType.PROLOGUE,
65
+ value: content,
66
+ location: {
67
+ start: {
68
+ line: lineIndex,
69
+ column: indent
70
+ },
71
+ end: {
72
+ line: lineIndex,
73
+ column: indent + content.length
74
+ },
75
+ offset: offset + indent,
76
+ length: lineLength - indent,
77
+ file: filename
78
+ }
79
+ });
80
+ offset += lineLength;
81
+ lineIndex++;
82
+ continue;
83
+ }
84
+ inPrologue = false;
85
+ // Handle indentation changes
86
+ currentIndent = indentStack[indentStack.length - 1];
87
+ if (indent > currentIndent) {
88
+ indentStack.push(indent);
89
+ tokens.push(indentToken(indent, offset, indent, filename, lineIndex));
90
+ } else if (indent < currentIndent) {
91
+ while (indentStack.length > 1 && indent < indentStack[indentStack.length - 1]) {
92
+ indentStack.pop();
93
+ tokens.push(dedentToken(offset, filename, lineIndex));
94
+ }
95
+ if (indent !== indentStack[indentStack.length - 1]) {
96
+ indentStack.push(indent);
97
+ tokens.push(indentToken(indent, offset, indent, filename, lineIndex));
98
+ }
99
+ }
100
+ // Parse line content
101
+ lineStartOffset = offset + indent;
102
+ lineTokens = tokenizeLine(content, lineStartOffset, lineLength - indent, filename, lineIndex);
103
+ tokens.push(...lineTokens);
104
+ offset += lineLength;
105
+ lineIndex++;
106
+ }
107
+ // Emit remaining DEDENT tokens at EOF
108
+ while (indentStack.length > 1) {
109
+ indentStack.pop();
110
+ tokens.push(dedentToken(offset, filename, lineIndex));
111
+ }
112
+ return tokens;
113
+ };
114
+
115
+ // ─── Line Tokenization ─────────────────────────────────────
116
+ tokenizeLine = function(content, lineStartOffset, _lineLength, filename = null, lineIndex = 0) {
117
+ var end, expr, filterContent, filterName, firstChar, loc, modResult, pos, remaining, spaceIdx, tagName, tokens, trimmedOffset, val;
118
+ tokens = [];
119
+ pos = 0;
120
+ loc = function(start, end) {
121
+ return {
122
+ start: {
123
+ line: lineIndex != null ? lineIndex : 0,
124
+ column: lineStartOffset + start
125
+ },
126
+ end: {
127
+ line: lineIndex != null ? lineIndex : 0,
128
+ column: lineStartOffset + end
129
+ },
130
+ offset: lineStartOffset + start,
131
+ length: end - start,
132
+ file: filename
133
+ };
134
+ };
135
+ firstChar = content[0];
136
+ // %tag
137
+ if (firstChar === '%') {
138
+ end = 1;
139
+ while (end < content.length && /[\w-]/.test(content[end])) {
140
+ end++;
141
+ }
142
+ tagName = content.slice(1, end);
143
+ tokens.push({
144
+ type: TokenType.TAG,
145
+ value: tagName,
146
+ location: loc(0, end)
147
+ });
148
+ pos = end;
149
+ // Self-closing /
150
+ if (content[pos] === '/') {
151
+ tokens.push({
152
+ type: TokenType.SELF_CLOSE,
153
+ value: '/',
154
+ location: loc(pos, pos + 1)
155
+ });
156
+ pos++;
157
+ }
158
+ modResult = parseModifiersAndAttrs(content, pos, lineStartOffset, filename, lineIndex);
159
+ tokens.push(...modResult.tokens);
160
+ pos = modResult.pos;
161
+ remaining = content.slice(pos).trimStart();
162
+ trimmedOffset = content.length - remaining.length;
163
+ if (remaining) {
164
+ if (remaining.startsWith('= ') || remaining === '=') {
165
+ tokens.push({
166
+ type: TokenType.OUTPUT,
167
+ value: remaining.slice(1).trimStart(),
168
+ location: loc(trimmedOffset + 1, content.length)
169
+ });
170
+ } else if (remaining.startsWith('!= ')) {
171
+ tokens.push({
172
+ type: TokenType.OUTPUT_UNESC,
173
+ value: remaining.slice(2).trimStart(),
174
+ location: loc(trimmedOffset + 2, content.length)
175
+ });
176
+ } else if (!(remaining.startsWith('{') || remaining.startsWith('('))) {
177
+ tokens.push({
178
+ type: TokenType.TEXT,
179
+ value: remaining,
180
+ location: loc(trimmedOffset, content.length)
181
+ });
182
+ }
183
+ }
184
+ return tokens;
185
+ }
186
+ // .class / #id (implicit div) — only if followed by valid identifier
187
+ if ((firstChar === '.' || firstChar === '#') && /^[.#][\w-]/.test(content)) {
188
+ modResult = parseModifiersAndAttrs(content, 0, lineStartOffset, filename, lineIndex);
189
+ tokens.push(...modResult.tokens);
190
+ pos = modResult.pos;
191
+ remaining = content.slice(pos).trimStart();
192
+ trimmedOffset = content.length - remaining.length;
193
+ if (remaining) {
194
+ if (remaining.startsWith('= ') || remaining === '=') {
195
+ tokens.push({
196
+ type: TokenType.OUTPUT,
197
+ value: remaining.slice(1).trimStart(),
198
+ location: loc(trimmedOffset + 1, content.length)
199
+ });
200
+ } else if (remaining.startsWith('!= ')) {
201
+ tokens.push({
202
+ type: TokenType.OUTPUT_UNESC,
203
+ value: remaining.slice(2).trimStart(),
204
+ location: loc(trimmedOffset + 2, content.length)
205
+ });
206
+ } else if (!(remaining.startsWith('{') || remaining.startsWith('('))) {
207
+ tokens.push({
208
+ type: TokenType.TEXT,
209
+ value: remaining,
210
+ location: loc(trimmedOffset, content.length)
211
+ });
212
+ }
213
+ }
214
+ return tokens;
215
+ }
216
+ // = expression
217
+ if (firstChar === '=' && content[1] !== '=') {
218
+ expr = content.slice(1).trimStart();
219
+ return [
220
+ {
221
+ type: TokenType.OUTPUT,
222
+ value: expr,
223
+ location: loc(1,
224
+ content.length)
225
+ }
226
+ ];
227
+ }
228
+ // != expression
229
+ if (firstChar === '!' && content[1] === '=') {
230
+ expr = content.slice(2).trimStart();
231
+ return [
232
+ {
233
+ type: TokenType.OUTPUT_UNESC,
234
+ value: expr,
235
+ location: loc(2,
236
+ content.length)
237
+ }
238
+ ];
239
+ }
240
+ // - control / comment
241
+ if (firstChar === '-') {
242
+ if (content[1] === '#') {
243
+ return [
244
+ {
245
+ type: TokenType.COMMENT,
246
+ value: content.slice(2).trimStart(),
247
+ location: loc(2,
248
+ content.length)
249
+ }
250
+ ];
251
+ }
252
+ expr = content.slice(1).trimStart();
253
+ return [
254
+ {
255
+ type: TokenType.CONTROL,
256
+ value: expr,
257
+ location: loc(1,
258
+ content.length)
259
+ }
260
+ ];
261
+ }
262
+ // / HTML comment
263
+ if (firstChar === '/') {
264
+ return [
265
+ {
266
+ type: TokenType.HTML_COMMENT,
267
+ value: content.slice(1).trimStart(),
268
+ location: loc(1,
269
+ content.length)
270
+ }
271
+ ];
272
+ }
273
+ // :filter
274
+ if (firstChar === ':') {
275
+ spaceIdx = content.search(/\s/);
276
+ filterName = spaceIdx === -1 ? content.slice(1) : content.slice(1, spaceIdx);
277
+ filterContent = spaceIdx === -1 ? '' : content.slice(spaceIdx + 1);
278
+ return [
279
+ {
280
+ type: TokenType.FILTER,
281
+ value: `${filterName}\n${filterContent}`,
282
+ location: loc(0,
283
+ content.length)
284
+ }
285
+ ];
286
+ }
287
+ // !!! doctype
288
+ if (content.startsWith('!!!')) {
289
+ val = content.slice(3).trimStart();
290
+ return [
291
+ {
292
+ type: TokenType.DOCTYPE,
293
+ value: val || 'html',
294
+ location: loc(0,
295
+ content.length)
296
+ }
297
+ ];
298
+ }
299
+ return [
300
+ {
301
+ // Plain text
302
+ type: TokenType.TEXT,
303
+ value: content,
304
+ location: loc(0,
305
+ content.length)
306
+ }
307
+ ];
308
+ };
309
+
310
+ // ─── Modifiers and Attributes ──────────────────────────────
311
+ parseModifiersAndAttrs = function(content, startPos, lineStartOffset, filename = null, lineIndex = 0) {
312
+ var block, ch, className, end, idName, loc, pos, tokens;
313
+ tokens = [];
314
+ pos = startPos;
315
+ loc = function(start, end) {
316
+ return {
317
+ start: {
318
+ line: lineIndex != null ? lineIndex : 0,
319
+ column: lineStartOffset + start
320
+ },
321
+ end: {
322
+ line: lineIndex != null ? lineIndex : 0,
323
+ column: lineStartOffset + end
324
+ },
325
+ offset: lineStartOffset + start,
326
+ length: end - start,
327
+ file: filename
328
+ };
329
+ };
330
+ while (pos < content.length) {
331
+ ch = content[pos];
332
+ if (ch === ' ' || ch === '\t') {
333
+ pos++;
334
+ continue;
335
+ }
336
+ // .class
337
+ if (ch === '.') {
338
+ end = pos + 1;
339
+ while (end < content.length && /[\w-]/.test(content[end])) {
340
+ end++;
341
+ }
342
+ className = content.slice(pos + 1, end);
343
+ tokens.push({
344
+ type: TokenType.CLASS,
345
+ value: className,
346
+ location: loc(pos, end)
347
+ });
348
+ pos = end;
349
+ continue;
350
+ }
351
+ // #id
352
+ if (ch === '#') {
353
+ end = pos + 1;
354
+ while (end < content.length && /[\w-]/.test(content[end])) {
355
+ end++;
356
+ }
357
+ idName = content.slice(pos + 1, end);
358
+ tokens.push({
359
+ type: TokenType.ID,
360
+ value: idName,
361
+ location: loc(pos, end)
362
+ });
363
+ pos = end;
364
+ continue;
365
+ }
366
+ // {attribute block}
367
+ if (ch === '{') {
368
+ block = extractBracketed(content, pos, '{', '}');
369
+ if (block !== null) {
370
+ tokens.push({
371
+ type: TokenType.ATTRS_BRACE,
372
+ value: block,
373
+ location: loc(pos, pos + block.length + 2)
374
+ });
375
+ pos += block.length + 2;
376
+ } else {
377
+ tokens.push({
378
+ type: TokenType.ATTRS_BRACE,
379
+ value: content.slice(pos + 1),
380
+ location: loc(pos, content.length)
381
+ });
382
+ pos = content.length;
383
+ }
384
+ continue;
385
+ }
386
+ // (attribute block)
387
+ if (ch === '(') {
388
+ block = extractBracketed(content, pos, '(', ')');
389
+ if (block !== null) {
390
+ tokens.push({
391
+ type: TokenType.ATTRS_PAREN,
392
+ value: block,
393
+ location: loc(pos, pos + block.length + 2)
394
+ });
395
+ pos += block.length + 2;
396
+ } else {
397
+ tokens.push({
398
+ type: TokenType.ATTRS_PAREN,
399
+ value: content.slice(pos + 1),
400
+ location: loc(pos, content.length)
401
+ });
402
+ pos = content.length;
403
+ }
404
+ continue;
405
+ }
406
+ // Anything else → stop
407
+ break;
408
+ }
409
+ return {tokens, pos};
410
+ };
411
+
412
+ // ─── Helpers ───────────────────────────────────────────────
413
+ isHamlConstruct = function(content) {
414
+ var fc;
415
+ fc = content[0];
416
+ return (fc === '%' || fc === '.' || fc === '#' || fc === '=' || fc === '-' || fc === '/' || fc === ':') || (fc === '!' && content[1] === '=') || content.startsWith('!!!');
417
+ };
418
+
419
+ countIndent = function(line) {
420
+ var ch, count, i, len;
421
+ count = 0;
422
+ for (i = 0, len = line.length; i < len; i++) {
423
+ ch = line[i];
424
+ if (ch === ' ') {
425
+ count++;
426
+ } else if (ch === '\t') {
427
+ count += 2;
428
+ } else {
429
+ break;
430
+ }
431
+ }
432
+ return count;
433
+ };
434
+
435
+ indentToken = function(_indent, offset, level, filename = null, line = 0) {
436
+ return {
437
+ type: TokenType.INDENT,
438
+ value: '',
439
+ indent: level,
440
+ location: {
441
+ start: {
442
+ line: line != null ? line : 0,
443
+ column: 0
444
+ },
445
+ end: {
446
+ line: line != null ? line : 0,
447
+ column: 0
448
+ },
449
+ offset,
450
+ length: 0,
451
+ file: filename
452
+ }
453
+ };
454
+ };
455
+
456
+ dedentToken = function(offset, filename = null, line = 0) {
457
+ return {
458
+ type: TokenType.DEDENT,
459
+ value: '',
460
+ indent: void 0,
461
+ location: {
462
+ start: {
463
+ line: line != null ? line : 0,
464
+ column: 0
465
+ },
466
+ end: {
467
+ line: line != null ? line : 0,
468
+ column: 0
469
+ },
470
+ offset,
471
+ length: 0,
472
+ file: filename
473
+ }
474
+ };
475
+ };
476
+
477
+ extractBracketed = function(content, start, open, close) {
478
+ var depth, pos, q, ref;
479
+ depth = 0;
480
+ pos = start;
481
+ while (pos < content.length) {
482
+ if (content[pos] === open) {
483
+ depth++;
484
+ } else if (content[pos] === close) {
485
+ depth--;
486
+ if (depth === 0) {
487
+ return content.slice(start + 1, pos);
488
+ }
489
+ }
490
+ // Skip string literals
491
+ if ((ref = content[pos]) === '"' || ref === "'") {
492
+ q = content[pos];
493
+ pos++;
494
+ while (pos < content.length && content[pos] !== q) {
495
+ if (content[pos] === '\\') {
496
+ pos++;
497
+ }
498
+ pos++;
499
+ }
500
+ }
501
+ pos++;
502
+ }
503
+ return null;
504
+ };