coffeehaml 0.8.0 → 0.10.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/README.md +5 -3
- package/dist/ast.js +26 -3
- package/dist/emitter.js +47 -18
- package/dist/lexer.js +22 -9
- package/dist/parser.js +14 -2
- package/dist/prettier/printer.js +9 -1
- package/docs/grammar.md +44 -13
- package/package.json +1 -1
- package/syntaxes/CoffeeHaml.tmLanguage +115 -1
- package/syntaxes/coffeehaml.tmLanguage.json +72 -0
package/README.md
CHANGED
|
@@ -152,8 +152,9 @@ console.log(result.code);
|
|
|
152
152
|
| `- code` | Arbitrary CoffeeScript statements; continuation via indented lines |
|
|
153
153
|
| `- for x in xs` | Loop → `.map()` |
|
|
154
154
|
| `- else`, `- else if` | Chain conditionals |
|
|
155
|
-
| `---` … `---` | Fenced CoffeeScript block —
|
|
155
|
+
| `---` … `---` | Fenced CoffeeScript block — render-time statements (multiline `-`) |
|
|
156
156
|
| `===` … `===` | Fenced yield block — CoffeeScript whose final value renders (escaped) |
|
|
157
|
+
| `~~~` … `~~~` | Fenced preamble block — module-scope code (imports, helpers, setup) |
|
|
157
158
|
| `-# comment` | Haml comment (stripped) |
|
|
158
159
|
| `/ comment` | HTML comment |
|
|
159
160
|
| `.wrapper` | Implicit div |
|
|
@@ -175,7 +176,7 @@ Full grammar: [`docs/grammar.md`](docs/grammar.md)
|
|
|
175
176
|
|
|
176
177
|
## Status
|
|
177
178
|
|
|
178
|
-
**v0.
|
|
179
|
+
**v0.10.0 — production beta.** The compiler pipeline (Lexer → Parser → Emitter)
|
|
179
180
|
is complete. The [Prettier plugin](src/prettier/) provides 16 deactivatable
|
|
180
181
|
formatting options including attribute style preservation, statement merging,
|
|
181
182
|
blank line handling, and CoffeeScript code formatting.
|
|
@@ -196,8 +197,9 @@ blank line handling, and CoffeeScript code formatting.
|
|
|
196
197
|
| Haml/HTML comments | ✅ |
|
|
197
198
|
| `:filter` blocks | ✅ |
|
|
198
199
|
| Prologue passthrough (`import`, `@decorator`) | ✅ |
|
|
199
|
-
| `---`
|
|
200
|
+
| `---` render-time CoffeeScript block | ✅ |
|
|
200
201
|
| `===` yield block (fenced output) | ✅ |
|
|
202
|
+
| `~~~` module-scope preamble block | ✅ |
|
|
201
203
|
| Component / HOC wrapping (`wrap: 'observer'`) | ✅ |
|
|
202
204
|
| Vite plugin with HMR | ✅ |
|
|
203
205
|
| CLI `--wrap` flag | ✅ |
|
package/dist/ast.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from './types.js';
|
|
7
7
|
|
|
8
8
|
// ─── Node Kinds ────────────────────────────────────────────
|
|
9
|
-
export var NodeKind = ['Document', 'Element', 'ImplicitDiv', 'Text', 'Output', 'ControlFlow', 'Comment', 'Filter', 'CoffeeBlock', 'CoffeeYield', 'Doctype'];
|
|
9
|
+
export var NodeKind = ['Document', 'Element', 'ImplicitDiv', 'Text', 'Output', 'ControlFlow', 'Comment', 'Filter', 'CoffeeBlock', 'CoffeeYield', 'CoffeePreamble', 'Doctype'];
|
|
10
10
|
|
|
11
11
|
export var ControlFlowKind = ['if', 'unless', 'for', 'while', 'else', 'statement'];
|
|
12
12
|
|
|
@@ -322,8 +322,9 @@ export var Filter = (function() {
|
|
|
322
322
|
export var CoffeeBlock = (function() {
|
|
323
323
|
// ─── CoffeeBlock ───────────────────────────────────────────
|
|
324
324
|
// A fenced raw CoffeeScript block (--- ... ---). Its content is a
|
|
325
|
-
// multi-line CoffeeScript program
|
|
326
|
-
//
|
|
325
|
+
// multi-line CoffeeScript program compiled to render-time statements — the
|
|
326
|
+
// multiline counterpart to `-`. It runs on every render and produces no
|
|
327
|
+
// output.
|
|
327
328
|
class CoffeeBlock extends Node {
|
|
328
329
|
constructor(content, location = null) {
|
|
329
330
|
super(location);
|
|
@@ -340,6 +341,28 @@ export var CoffeeBlock = (function() {
|
|
|
340
341
|
|
|
341
342
|
}).call(this);
|
|
342
343
|
|
|
344
|
+
export var CoffeePreamble = (function() {
|
|
345
|
+
// ─── CoffeePreamble ────────────────────────────────────────
|
|
346
|
+
// A fenced CoffeeScript preamble block (~~~ ... ~~~). The body is a
|
|
347
|
+
// multi-line CoffeeScript program compiled to statements hoisted to module
|
|
348
|
+
// scope — imports, helpers, and once-only setup. It runs once at import and
|
|
349
|
+
// produces no output.
|
|
350
|
+
class CoffeePreamble extends Node {
|
|
351
|
+
constructor(content, location = null) {
|
|
352
|
+
super(location);
|
|
353
|
+
this.content = content;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
CoffeePreamble.prototype.kind = 'CoffeePreamble';
|
|
359
|
+
|
|
360
|
+
CoffeePreamble.prototype.content = '';
|
|
361
|
+
|
|
362
|
+
return CoffeePreamble;
|
|
363
|
+
|
|
364
|
+
}).call(this);
|
|
365
|
+
|
|
343
366
|
export var CoffeeYield = (function() {
|
|
344
367
|
// ─── CoffeeYield ───────────────────────────────────────────
|
|
345
368
|
// A fenced CoffeeScript yield block (=== ... ===). The body is a
|
package/dist/emitter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
2
|
// emitter.coffee — AST → JSX/JS code generation
|
|
3
|
-
var EmitState, VOID_ELEMENTS, attrNameToJs, buildAttributes,
|
|
3
|
+
var EmitState, VOID_ELEMENTS, attrNameToJs, buildAttributes, collectCoffeePreamble, compileComponentBody, emitArrowOutput, emitArrowOutputToJs, emitChildToJs, emitCoffeeBlock, emitCoffeeYield, emitComment, emitConditional, emitControlFlow, emitControlFlowToJs, emitDoctype, emitElement, emitElseBranch, emitFilter, emitFilterToJs, emitImplicitDiv, emitLoop, emitNode, emitNodes, emitOutput, emitStatement, emitText, escapeString, mapSourceLocation, splitJsStatements, tagToJs;
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
Document,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Filter,
|
|
14
14
|
CoffeeBlock,
|
|
15
15
|
CoffeeYield,
|
|
16
|
+
CoffeePreamble,
|
|
16
17
|
Doctype,
|
|
17
18
|
Node,
|
|
18
19
|
Expression
|
|
@@ -39,6 +40,11 @@ EmitState = (function() {
|
|
|
39
40
|
class EmitState {
|
|
40
41
|
constructor(options1 = {}) {
|
|
41
42
|
this.options = options1;
|
|
43
|
+
// `hoisted` and `warnings` must be instance-level (not prototype-level):
|
|
44
|
+
// CoffeeScript emits class-body arrays as shared prototype properties, so
|
|
45
|
+
// every EmitState would otherwise mutate the same accumulator across calls.
|
|
46
|
+
this.hoisted = [];
|
|
47
|
+
this.warnings = [];
|
|
42
48
|
if (this.options.sourceMap && this.options.filename) {
|
|
43
49
|
this.sourceMapGenerator = new SourceMapGenerator({
|
|
44
50
|
file: this.options.filename
|
|
@@ -113,7 +119,7 @@ EmitState = (function() {
|
|
|
113
119
|
|
|
114
120
|
// ─── Public API ────────────────────────────────────────────
|
|
115
121
|
export var emit = function(ast, options = {}) {
|
|
116
|
-
var
|
|
122
|
+
var bodyExpr, hocs, inner, j, k, len, len1, line, name, p, preambles, ref, result, state, wrap, wrapped;
|
|
117
123
|
state = new EmitState(options);
|
|
118
124
|
state.emitLine(`import { jsx, jsxs, Fragment } from \"${options.jsxRuntime || 'react/jsx-runtime'}\";`);
|
|
119
125
|
// Emit prologue lines
|
|
@@ -125,14 +131,15 @@ export var emit = function(ast, options = {}) {
|
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
state.emitLine();
|
|
128
|
-
// Emit fenced
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// Emit fenced `~~~ ... ~~~` blocks at module scope. Imports, helpers and
|
|
135
|
+
// setup code must live outside any component wrapper (and cannot rerun per
|
|
136
|
+
// render), so they are collected here and emitted before the body. This is
|
|
137
|
+
// the once-at-import region; `---` is the render-time counterpart.
|
|
138
|
+
preambles = collectCoffeePreamble(ast.children, []);
|
|
139
|
+
if (preambles.length > 0) {
|
|
140
|
+
for (k = 0, len1 = preambles.length; k < len1; k++) {
|
|
141
|
+
p = preambles[k];
|
|
142
|
+
state.emitLine(compileStatement(p.content, p.location));
|
|
136
143
|
}
|
|
137
144
|
state.emitLine();
|
|
138
145
|
}
|
|
@@ -246,9 +253,10 @@ splitJsStatements = function(body) {
|
|
|
246
253
|
|
|
247
254
|
// ─── Node Emitters ─────────────────────────────────────────
|
|
248
255
|
|
|
249
|
-
// Recursively collect fenced
|
|
250
|
-
//
|
|
251
|
-
|
|
256
|
+
// Recursively collect fenced `~~~ ... ~~~` preamble blocks, removing them
|
|
257
|
+
// from the render tree in place (they contribute no markup). Their statements
|
|
258
|
+
// are hoisted to module scope — imports, helpers, and once-only setup.
|
|
259
|
+
collectCoffeePreamble = function(nodes, acc) {
|
|
252
260
|
var i, node, ref;
|
|
253
261
|
if (!(nodes != null ? nodes.length : void 0)) {
|
|
254
262
|
return acc;
|
|
@@ -256,16 +264,16 @@ collectCoffeeBlocks = function(nodes, acc) {
|
|
|
256
264
|
i = 0;
|
|
257
265
|
while (i < nodes.length) {
|
|
258
266
|
node = nodes[i];
|
|
259
|
-
if (node instanceof
|
|
267
|
+
if (node instanceof CoffeePreamble) {
|
|
260
268
|
acc.push(node);
|
|
261
269
|
nodes.splice(i, 1);
|
|
262
270
|
continue;
|
|
263
271
|
}
|
|
264
272
|
if (((ref = node.children) != null ? ref.length : void 0) > 0) {
|
|
265
|
-
|
|
273
|
+
collectCoffeePreamble(node.children, acc);
|
|
266
274
|
}
|
|
267
275
|
if (node.next != null) {
|
|
268
|
-
|
|
276
|
+
collectCoffeePreamble([node.next], acc);
|
|
269
277
|
}
|
|
270
278
|
i++;
|
|
271
279
|
}
|
|
@@ -289,7 +297,10 @@ emitNodes = function(nodes, state, isRoot = false) {
|
|
|
289
297
|
};
|
|
290
298
|
|
|
291
299
|
emitNode = function(node, state, isRoot) {
|
|
292
|
-
if (node instanceof CoffeeBlock) {
|
|
300
|
+
if (node instanceof CoffeeBlock) {
|
|
301
|
+
return emitCoffeeBlock(node, state);
|
|
302
|
+
}
|
|
303
|
+
if (node instanceof CoffeePreamble) { // hoisted to module scope above
|
|
293
304
|
return;
|
|
294
305
|
}
|
|
295
306
|
if (node instanceof CoffeeYield) {
|
|
@@ -443,7 +454,7 @@ emitImplicitDiv = function(div, state) {
|
|
|
443
454
|
|
|
444
455
|
// ─── Child Emission ────────────────────────────────────────
|
|
445
456
|
emitChildToJs = function(node, state) {
|
|
446
|
-
var allAttrs, attrResult, attrs, attrsJs, c, childParts, childrenAttr, el, exprSource, fn, hasChildren, keyArg, tagJs, textContinuations;
|
|
457
|
+
var allAttrs, attrResult, attrs, attrsJs, c, childParts, childrenAttr, el, exprSource, fn, hasChildren, js, keyArg, tagJs, textContinuations;
|
|
447
458
|
if (node instanceof Text) {
|
|
448
459
|
return escapeString(node.value);
|
|
449
460
|
}
|
|
@@ -503,6 +514,10 @@ emitChildToJs = function(node, state) {
|
|
|
503
514
|
if (node instanceof Filter) {
|
|
504
515
|
return emitFilterToJs(node, state);
|
|
505
516
|
}
|
|
517
|
+
if (node instanceof CoffeeBlock) {
|
|
518
|
+
js = compileStatement(node.content, node.location);
|
|
519
|
+
return `/* - ${js} */ null`;
|
|
520
|
+
}
|
|
506
521
|
if (node instanceof CoffeeYield) {
|
|
507
522
|
return compileYield(node.content, node.location);
|
|
508
523
|
}
|
|
@@ -546,6 +561,20 @@ emitStatement = function(cf, remaining, state) {
|
|
|
546
561
|
}
|
|
547
562
|
};
|
|
548
563
|
|
|
564
|
+
// Fenced `--- ... ---` block: the multiline counterpart to `-`. Compiles the
|
|
565
|
+
// body to statements that run at render time, producing no output. In a
|
|
566
|
+
// wrapped component they hoist alongside `-` statements (same IIFE path); in
|
|
567
|
+
// unwrapped mode they emit in place.
|
|
568
|
+
emitCoffeeBlock = function(node, state) {
|
|
569
|
+
var js;
|
|
570
|
+
js = compileStatement(node.content, node.location);
|
|
571
|
+
if (state.directEmit) {
|
|
572
|
+
return state.emitLine(js + ';');
|
|
573
|
+
} else {
|
|
574
|
+
return state.hoisted.push(js);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
549
578
|
emitLoop = function(cf, remaining, state) {
|
|
550
579
|
var cond, expr, forMatch, iterable, vars;
|
|
551
580
|
if (cf.controlKind === 'for') {
|
package/dist/lexer.js
CHANGED
|
@@ -23,6 +23,7 @@ export var TokenType = {
|
|
|
23
23
|
FILTER: 'FILTER',
|
|
24
24
|
COFFEE_BLOCK: 'COFFEE_BLOCK',
|
|
25
25
|
COFFEE_YIELD: 'COFFEE_YIELD',
|
|
26
|
+
COFFEE_PREAMBLE: 'COFFEE_PREAMBLE',
|
|
26
27
|
DOCTYPE: 'DOCTYPE',
|
|
27
28
|
TEXT: 'TEXT',
|
|
28
29
|
PROLOGUE: 'PROLOGUE',
|
|
@@ -104,15 +105,18 @@ export var tokenize = function(source, filename = null) {
|
|
|
104
105
|
i++;
|
|
105
106
|
continue;
|
|
106
107
|
}
|
|
107
|
-
// ─── Multiline CoffeeScript fences: ---
|
|
108
|
-
// A line consisting solely of
|
|
109
|
-
// area that escapes HAML indentation. `---` compiles the
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// the
|
|
113
|
-
//
|
|
108
|
+
// ─── Multiline CoffeeScript fences: --- / === / ~~~ ───
|
|
109
|
+
// A line consisting solely of `---`, `===` or `~~~` opens a raw
|
|
110
|
+
// CoffeeScript area that escapes HAML indentation. `---` compiles the
|
|
111
|
+
// body to render-time statements (COFFEE_BLOCK — the multiline
|
|
112
|
+
// counterpart to `-`); `===` yields the body's final value as content
|
|
113
|
+
// (COFFEE_YIELD — the multiline counterpart to `=`); `~~~` compiles the
|
|
114
|
+
// body to code hoisted to module scope, running once at import
|
|
115
|
+
// (COFFEE_PREAMBLE). The body runs until the matching closing fence line;
|
|
116
|
+
// it is dedented to column 0 and tokenized as a single token (no
|
|
117
|
+
// INDENT/DEDENT inside).
|
|
114
118
|
fence = content.trim();
|
|
115
|
-
if (fence === '---' || fence === '===') {
|
|
119
|
+
if (fence === '---' || fence === '===' || fence === '~~~') {
|
|
116
120
|
inPrologue = false;
|
|
117
121
|
fenceIndent = indent;
|
|
118
122
|
fenceStartLine = lineIndex;
|
|
@@ -152,7 +156,16 @@ export var tokenize = function(source, filename = null) {
|
|
|
152
156
|
i++;
|
|
153
157
|
}
|
|
154
158
|
tokens.push({
|
|
155
|
-
type: (
|
|
159
|
+
type: (function() {
|
|
160
|
+
switch (fence) {
|
|
161
|
+
case '===':
|
|
162
|
+
return TokenType.COFFEE_YIELD;
|
|
163
|
+
case '~~~':
|
|
164
|
+
return TokenType.COFFEE_PREAMBLE;
|
|
165
|
+
default:
|
|
166
|
+
return TokenType.COFFEE_BLOCK;
|
|
167
|
+
}
|
|
168
|
+
})(),
|
|
156
169
|
value: (dedentLines(body)).join('\n'),
|
|
157
170
|
location: {
|
|
158
171
|
start: {
|
package/dist/parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
2
|
// parser.coffee — recursive descent parser
|
|
3
3
|
// Converts token stream into AST
|
|
4
|
-
var ParserState, cleanAttrName, findColon, findEquals, isElse, parseAttributeBlock, parseBareAttributes, parseBlock, parseCoffeeBlock, parseCoffeeYield, parseComment, parseControlFlow, parseControlKind, parseDoctype, parseElement, parseFilter, parseHtmlComment, parseImplicitDiv, parseNode, parseOutput, parseText, splitAttributePairs, splitBareAttributePairs, stripControlKeyword;
|
|
4
|
+
var ParserState, cleanAttrName, findColon, findEquals, isElse, parseAttributeBlock, parseBareAttributes, parseBlock, parseCoffeeBlock, parseCoffeePreamble, parseCoffeeYield, parseComment, parseControlFlow, parseControlKind, parseDoctype, parseElement, parseFilter, parseHtmlComment, parseImplicitDiv, parseNode, parseOutput, parseText, splitAttributePairs, splitBareAttributePairs, stripControlKeyword;
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
TokenType
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Filter,
|
|
19
19
|
CoffeeBlock,
|
|
20
20
|
CoffeeYield,
|
|
21
|
+
CoffeePreamble,
|
|
21
22
|
Doctype,
|
|
22
23
|
Expression,
|
|
23
24
|
Attribute,
|
|
@@ -81,7 +82,7 @@ ParserState = (function() {
|
|
|
81
82
|
var t;
|
|
82
83
|
while (this.current()) {
|
|
83
84
|
t = this.current().type;
|
|
84
|
-
if (t === TokenType.TAG || t === TokenType.CLASS || t === TokenType.ID || t === TokenType.OUTPUT || t === TokenType.OUTPUT_UNESC || t === TokenType.CONTROL || t === TokenType.FILTER || t === TokenType.COFFEE_BLOCK || t === TokenType.COFFEE_YIELD || t === TokenType.COMMENT || t === TokenType.HTML_COMMENT || t === TokenType.DOCTYPE || t === TokenType.INDENT || t === TokenType.DEDENT) {
|
|
85
|
+
if (t === TokenType.TAG || t === TokenType.CLASS || t === TokenType.ID || t === TokenType.OUTPUT || t === TokenType.OUTPUT_UNESC || t === TokenType.CONTROL || t === TokenType.FILTER || t === TokenType.COFFEE_BLOCK || t === TokenType.COFFEE_YIELD || t === TokenType.COFFEE_PREAMBLE || t === TokenType.COMMENT || t === TokenType.HTML_COMMENT || t === TokenType.DOCTYPE || t === TokenType.INDENT || t === TokenType.DEDENT) {
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
this.advance();
|
|
@@ -174,6 +175,8 @@ parseNode = function(state) {
|
|
|
174
175
|
return parseCoffeeBlock(state);
|
|
175
176
|
case TokenType.COFFEE_YIELD:
|
|
176
177
|
return parseCoffeeYield(state);
|
|
178
|
+
case TokenType.COFFEE_PREAMBLE:
|
|
179
|
+
return parseCoffeePreamble(state);
|
|
177
180
|
case TokenType.DOCTYPE:
|
|
178
181
|
return parseDoctype(state);
|
|
179
182
|
case TokenType.TEXT:
|
|
@@ -486,6 +489,15 @@ parseCoffeeYield = function(state) {
|
|
|
486
489
|
return new CoffeeYield(token.value, token.location);
|
|
487
490
|
};
|
|
488
491
|
|
|
492
|
+
parseCoffeePreamble = function(state) {
|
|
493
|
+
var token;
|
|
494
|
+
token = state.expect(TokenType.COFFEE_PREAMBLE);
|
|
495
|
+
if (!token) {
|
|
496
|
+
return new CoffeePreamble('');
|
|
497
|
+
}
|
|
498
|
+
return new CoffeePreamble(token.value, token.location);
|
|
499
|
+
};
|
|
500
|
+
|
|
489
501
|
// ─── Doctype ───────────────────────────────────────────────
|
|
490
502
|
parseDoctype = function(state) {
|
|
491
503
|
var token;
|
package/dist/prettier/printer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
2
|
// printer.coffee — AST → Prettier Doc IR
|
|
3
3
|
// Core formatting engine for CoffeeHaml Prettier plugin
|
|
4
|
-
var MergedStatements, VOID_ELEMENTS, _prettier, _require, analyzeBlankLines, defaultOpts, detectStyle, formatAttributes, formatTag, group, hardline, indent, isSingleInlineChild, isVoidElement, join, joinChildrenWithBlanks, line, mergeAllStatements, opts, printCoffeeBlock, printCoffeeYield, printComment, printControlFlow, printDoc, printDoctype, printElement, printFence, printFilter, printFlow, printImplicitDiv, printMergedFlow, printNode, printOutput, reflowCommentText;
|
|
4
|
+
var MergedStatements, VOID_ELEMENTS, _prettier, _require, analyzeBlankLines, defaultOpts, detectStyle, formatAttributes, formatTag, group, hardline, indent, isSingleInlineChild, isVoidElement, join, joinChildrenWithBlanks, line, mergeAllStatements, opts, printCoffeeBlock, printCoffeePreamble, printCoffeeYield, printComment, printControlFlow, printDoc, printDoctype, printElement, printFence, printFilter, printFlow, printImplicitDiv, printMergedFlow, printNode, printOutput, reflowCommentText;
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
Document,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Filter,
|
|
15
15
|
CoffeeBlock,
|
|
16
16
|
CoffeeYield,
|
|
17
|
+
CoffeePreamble,
|
|
17
18
|
Doctype,
|
|
18
19
|
Node,
|
|
19
20
|
Expression,
|
|
@@ -233,6 +234,9 @@ printNode = function(path, o, printFn) {
|
|
|
233
234
|
if (node instanceof CoffeeYield) {
|
|
234
235
|
return printCoffeeYield(path, o, printFn);
|
|
235
236
|
}
|
|
237
|
+
if (node instanceof CoffeePreamble) {
|
|
238
|
+
return printCoffeePreamble(path, o, printFn);
|
|
239
|
+
}
|
|
236
240
|
if (node instanceof Doctype) {
|
|
237
241
|
return printDoctype(path, o);
|
|
238
242
|
}
|
|
@@ -512,6 +516,10 @@ printCoffeeYield = function(path, o, _printFn) {
|
|
|
512
516
|
return printFence('===', path.node.content);
|
|
513
517
|
};
|
|
514
518
|
|
|
519
|
+
printCoffeePreamble = function(path, o, _printFn) {
|
|
520
|
+
return printFence('~~~', path.node.content);
|
|
521
|
+
};
|
|
522
|
+
|
|
515
523
|
// ─── Doctype ───────────────────────────────────────────────
|
|
516
524
|
printDoctype = function(path, o) {
|
|
517
525
|
var node;
|
package/docs/grammar.md
CHANGED
|
@@ -302,28 +302,28 @@ CoffeeBlock := '---' Newline
|
|
|
302
302
|
CoffeeBody := Raw CoffeeScript lines, dedented to column 0.
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
taken verbatim (dedented by its minimum
|
|
307
|
-
executable JavaScript via the CoffeeScript
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
indentation tree.
|
|
305
|
+
The `---` fence is the multiline counterpart to `-` (statement). The body
|
|
306
|
+
between the two `---` lines is taken verbatim (dedented by its minimum
|
|
307
|
+
indentation) and compiled to executable JavaScript via the CoffeeScript
|
|
308
|
+
bridge. The resulting statements run at **render time**, produce no markup,
|
|
309
|
+
and behave exactly like a block of `-` lines.
|
|
311
310
|
|
|
312
311
|
```haml
|
|
313
312
|
---
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
total = price * quantity
|
|
314
|
+
tax = total * 0.08
|
|
316
315
|
---
|
|
317
|
-
%p=
|
|
316
|
+
%p= total + tax
|
|
318
317
|
```
|
|
319
318
|
|
|
320
319
|
Semantics:
|
|
321
320
|
|
|
322
321
|
- **Escapes indentation**: body lines are dedented to column 0, so a block
|
|
323
322
|
nested under an element still writes CoffeeScript flush-left.
|
|
324
|
-
- **
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
- **Render-time**: the compiled statements run on every render, before the
|
|
324
|
+
JSX at their position. In a wrapped component they are hoisted into the
|
|
325
|
+
render expression alongside `-` statements; with `wrap: 'none'` they emit
|
|
326
|
+
in place.
|
|
327
327
|
- **No markup**: the block contributes nothing to the render tree.
|
|
328
328
|
|
|
329
329
|
Note: `---` is a CoffeeHaml fence marker, not an element. For a horizontal
|
|
@@ -342,7 +342,7 @@ CoffeeYield := '===' Newline
|
|
|
342
342
|
|
|
343
343
|
The `===` fence is the multiline counterpart to `=` (output): its body is
|
|
344
344
|
CoffeeScript, dedented to column 0 exactly like a `---` block, but instead
|
|
345
|
-
of
|
|
345
|
+
of running as statements its **final value is yielded as content**
|
|
346
346
|
at that position in the render tree (escaped, matching `=`).
|
|
347
347
|
|
|
348
348
|
```haml
|
|
@@ -358,6 +358,37 @@ wrapped in an IIFE so the last expression is the yielded value.
|
|
|
358
358
|
|
|
359
359
|
---
|
|
360
360
|
|
|
361
|
+
## Preamble Blocks (`~~~`)
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
CoffeePreamble := '~~~' Newline
|
|
365
|
+
CoffeeBody
|
|
366
|
+
'~~~' Newline
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The `~~~` fence holds module-scope code. Its body is CoffeeScript, dedented
|
|
370
|
+
to column 0, and compiled to statements hoisted to **module scope** — outside
|
|
371
|
+
any component wrapper. This is where imports, helper functions and one-time
|
|
372
|
+
setup code live (the once-at-import region).
|
|
373
|
+
|
|
374
|
+
```haml
|
|
375
|
+
~~~
|
|
376
|
+
import { useState } from 'react'
|
|
377
|
+
formatPrice = (n) -> "$#{n.toFixed 2}"
|
|
378
|
+
~~~
|
|
379
|
+
%p= formatPrice total
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Semantics:
|
|
383
|
+
|
|
384
|
+
- **Module scope**: the compiled statements run once at module load, before
|
|
385
|
+
any JSX. `import`/`export` stay valid (they are not wrapped in a function).
|
|
386
|
+
- **No markup**: the block contributes nothing to the render tree.
|
|
387
|
+
- **Runs once**: unlike `---` (render-time), the body does not rerun per
|
|
388
|
+
render — ideal for imports, constants and pure helpers.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
361
392
|
## Raw Text / Passthrough
|
|
362
393
|
|
|
363
394
|
```
|
package/package.json
CHANGED
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
<key>include</key>
|
|
16
16
|
<string>#prologue</string>
|
|
17
17
|
</dict>
|
|
18
|
+
<dict>
|
|
19
|
+
<key>include</key>
|
|
20
|
+
<string>#coffee-block</string>
|
|
21
|
+
</dict>
|
|
22
|
+
<dict>
|
|
23
|
+
<key>include</key>
|
|
24
|
+
<string>#coffee-yield</string>
|
|
25
|
+
</dict>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>include</key>
|
|
28
|
+
<string>#coffee-preamble</string>
|
|
29
|
+
</dict>
|
|
18
30
|
<dict>
|
|
19
31
|
<key>include</key>
|
|
20
32
|
<string>#comment-haml</string>
|
|
@@ -183,6 +195,40 @@
|
|
|
183
195
|
<key>name</key>
|
|
184
196
|
<string>meta.attribute.shorthand.coffeehaml</string>
|
|
185
197
|
</dict>
|
|
198
|
+
<key>coffee-block</key>
|
|
199
|
+
<dict>
|
|
200
|
+
<key>begin</key>
|
|
201
|
+
<string>^([ \t]*)(---)[ \t]*$</string>
|
|
202
|
+
<key>beginCaptures</key>
|
|
203
|
+
<dict>
|
|
204
|
+
<key>2</key>
|
|
205
|
+
<dict>
|
|
206
|
+
<key>name</key>
|
|
207
|
+
<string>punctuation.definition.coffee-block.coffeehaml</string>
|
|
208
|
+
</dict>
|
|
209
|
+
</dict>
|
|
210
|
+
<key>contentName</key>
|
|
211
|
+
<string>source.coffee.embedded.haml</string>
|
|
212
|
+
<key>end</key>
|
|
213
|
+
<string>^([ \t]*)(---)[ \t]*$</string>
|
|
214
|
+
<key>endCaptures</key>
|
|
215
|
+
<dict>
|
|
216
|
+
<key>2</key>
|
|
217
|
+
<dict>
|
|
218
|
+
<key>name</key>
|
|
219
|
+
<string>punctuation.definition.coffee-block.coffeehaml</string>
|
|
220
|
+
</dict>
|
|
221
|
+
</dict>
|
|
222
|
+
<key>name</key>
|
|
223
|
+
<string>meta.coffee-block.coffeehaml</string>
|
|
224
|
+
<key>patterns</key>
|
|
225
|
+
<array>
|
|
226
|
+
<dict>
|
|
227
|
+
<key>include</key>
|
|
228
|
+
<string>source.coffee</string>
|
|
229
|
+
</dict>
|
|
230
|
+
</array>
|
|
231
|
+
</dict>
|
|
186
232
|
<key>coffee-boolean</key>
|
|
187
233
|
<dict>
|
|
188
234
|
<key>match</key>
|
|
@@ -566,6 +612,74 @@
|
|
|
566
612
|
<key>name</key>
|
|
567
613
|
<string>variable.other.coffeehaml</string>
|
|
568
614
|
</dict>
|
|
615
|
+
<key>coffee-yield</key>
|
|
616
|
+
<dict>
|
|
617
|
+
<key>begin</key>
|
|
618
|
+
<string>^([ \t]*)(===)[ \t]*$</string>
|
|
619
|
+
<key>beginCaptures</key>
|
|
620
|
+
<dict>
|
|
621
|
+
<key>2</key>
|
|
622
|
+
<dict>
|
|
623
|
+
<key>name</key>
|
|
624
|
+
<string>punctuation.definition.coffee-yield.coffeehaml</string>
|
|
625
|
+
</dict>
|
|
626
|
+
</dict>
|
|
627
|
+
<key>contentName</key>
|
|
628
|
+
<string>source.coffee.embedded.haml</string>
|
|
629
|
+
<key>end</key>
|
|
630
|
+
<string>^([ \t]*)(===)[ \t]*$</string>
|
|
631
|
+
<key>endCaptures</key>
|
|
632
|
+
<dict>
|
|
633
|
+
<key>2</key>
|
|
634
|
+
<dict>
|
|
635
|
+
<key>name</key>
|
|
636
|
+
<string>punctuation.definition.coffee-yield.coffeehaml</string>
|
|
637
|
+
</dict>
|
|
638
|
+
</dict>
|
|
639
|
+
<key>name</key>
|
|
640
|
+
<string>meta.coffee-yield.coffeehaml</string>
|
|
641
|
+
<key>patterns</key>
|
|
642
|
+
<array>
|
|
643
|
+
<dict>
|
|
644
|
+
<key>include</key>
|
|
645
|
+
<string>source.coffee</string>
|
|
646
|
+
</dict>
|
|
647
|
+
</array>
|
|
648
|
+
</dict>
|
|
649
|
+
<key>coffee-preamble</key>
|
|
650
|
+
<dict>
|
|
651
|
+
<key>begin</key>
|
|
652
|
+
<string>^([ \t]*)(~~~)[ \t]*$</string>
|
|
653
|
+
<key>beginCaptures</key>
|
|
654
|
+
<dict>
|
|
655
|
+
<key>2</key>
|
|
656
|
+
<dict>
|
|
657
|
+
<key>name</key>
|
|
658
|
+
<string>punctuation.definition.coffee-preamble.coffeehaml</string>
|
|
659
|
+
</dict>
|
|
660
|
+
</dict>
|
|
661
|
+
<key>contentName</key>
|
|
662
|
+
<string>source.coffee.embedded.haml</string>
|
|
663
|
+
<key>end</key>
|
|
664
|
+
<string>^([ \t]*)(~~~)[ \t]*$</string>
|
|
665
|
+
<key>endCaptures</key>
|
|
666
|
+
<dict>
|
|
667
|
+
<key>2</key>
|
|
668
|
+
<dict>
|
|
669
|
+
<key>name</key>
|
|
670
|
+
<string>punctuation.definition.coffee-preamble.coffeehaml</string>
|
|
671
|
+
</dict>
|
|
672
|
+
</dict>
|
|
673
|
+
<key>name</key>
|
|
674
|
+
<string>meta.coffee-preamble.coffeehaml</string>
|
|
675
|
+
<key>patterns</key>
|
|
676
|
+
<array>
|
|
677
|
+
<dict>
|
|
678
|
+
<key>include</key>
|
|
679
|
+
<string>source.coffee</string>
|
|
680
|
+
</dict>
|
|
681
|
+
</array>
|
|
682
|
+
</dict>
|
|
569
683
|
<key>coffeescript-expression</key>
|
|
570
684
|
<dict>
|
|
571
685
|
<key>patterns</key>
|
|
@@ -971,4 +1085,4 @@
|
|
|
971
1085
|
<key>uuid</key>
|
|
972
1086
|
<string>1EEA33FD-AEAC-4C44-9EB6-D4D0D9E5C9CB</string>
|
|
973
1087
|
</dict>
|
|
974
|
-
</plist>
|
|
1088
|
+
</plist>
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
{
|
|
12
12
|
"include": "#prologue"
|
|
13
13
|
},
|
|
14
|
+
{
|
|
15
|
+
"include": "#coffee-block"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"include": "#coffee-yield"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"include": "#coffee-preamble"
|
|
22
|
+
},
|
|
14
23
|
{
|
|
15
24
|
"include": "#comment-haml"
|
|
16
25
|
},
|
|
@@ -300,6 +309,69 @@
|
|
|
300
309
|
],
|
|
301
310
|
"contentName": "source.coffee.embedded.haml"
|
|
302
311
|
},
|
|
312
|
+
"coffee-block": {
|
|
313
|
+
"name": "meta.coffee-block.coffeehaml",
|
|
314
|
+
"begin": "^([ \\t]*)(---)[ \\t]*$",
|
|
315
|
+
"beginCaptures": {
|
|
316
|
+
"2": {
|
|
317
|
+
"name": "punctuation.definition.coffee-block.coffeehaml"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"end": "^([ \\t]*)(---)[ \\t]*$",
|
|
321
|
+
"endCaptures": {
|
|
322
|
+
"2": {
|
|
323
|
+
"name": "punctuation.definition.coffee-block.coffeehaml"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"patterns": [
|
|
327
|
+
{
|
|
328
|
+
"include": "source.coffee"
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"contentName": "source.coffee.embedded.haml"
|
|
332
|
+
},
|
|
333
|
+
"coffee-yield": {
|
|
334
|
+
"name": "meta.coffee-yield.coffeehaml",
|
|
335
|
+
"begin": "^([ \\t]*)(===)[ \\t]*$",
|
|
336
|
+
"beginCaptures": {
|
|
337
|
+
"2": {
|
|
338
|
+
"name": "punctuation.definition.coffee-yield.coffeehaml"
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
"end": "^([ \\t]*)(===)[ \\t]*$",
|
|
342
|
+
"endCaptures": {
|
|
343
|
+
"2": {
|
|
344
|
+
"name": "punctuation.definition.coffee-yield.coffeehaml"
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
"patterns": [
|
|
348
|
+
{
|
|
349
|
+
"include": "source.coffee"
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
"contentName": "source.coffee.embedded.haml"
|
|
353
|
+
},
|
|
354
|
+
"coffee-preamble": {
|
|
355
|
+
"name": "meta.coffee-preamble.coffeehaml",
|
|
356
|
+
"begin": "^([ \\t]*)(~~~)[ \\t]*$",
|
|
357
|
+
"beginCaptures": {
|
|
358
|
+
"2": {
|
|
359
|
+
"name": "punctuation.definition.coffee-preamble.coffeehaml"
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"end": "^([ \\t]*)(~~~)[ \\t]*$",
|
|
363
|
+
"endCaptures": {
|
|
364
|
+
"2": {
|
|
365
|
+
"name": "punctuation.definition.coffee-preamble.coffeehaml"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"patterns": [
|
|
369
|
+
{
|
|
370
|
+
"include": "source.coffee"
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"contentName": "source.coffee.embedded.haml"
|
|
374
|
+
},
|
|
303
375
|
"output": {
|
|
304
376
|
"name": "meta.output.coffeehaml",
|
|
305
377
|
"begin": "^(\\s*)(!=|=)(?!=)(\\s*)",
|