coffeehaml 0.7.7 → 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 +7 -1
- package/dist/ast.js +66 -1
- package/dist/cli.js +0 -0
- package/dist/emitter.js +89 -4
- package/dist/expressions.js +69 -10
- package/dist/lexer.js +136 -4
- package/dist/parser.js +39 -2
- package/dist/prettier/options.js +2 -2
- package/dist/prettier/printer.js +94 -39
- package/docs/grammar.md +97 -0
- package/package.json +2 -2
- package/syntaxes/CoffeeHaml.tmLanguage +115 -1
- package/syntaxes/coffeehaml.tmLanguage.json +72 -0
package/README.md
CHANGED
|
@@ -152,6 +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 — render-time statements (multiline `-`) |
|
|
156
|
+
| `===` … `===` | Fenced yield block — CoffeeScript whose final value renders (escaped) |
|
|
157
|
+
| `~~~` … `~~~` | Fenced preamble block — module-scope code (imports, helpers, setup) |
|
|
155
158
|
| `-# comment` | Haml comment (stripped) |
|
|
156
159
|
| `/ comment` | HTML comment |
|
|
157
160
|
| `.wrapper` | Implicit div |
|
|
@@ -173,7 +176,7 @@ Full grammar: [`docs/grammar.md`](docs/grammar.md)
|
|
|
173
176
|
|
|
174
177
|
## Status
|
|
175
178
|
|
|
176
|
-
**v0.
|
|
179
|
+
**v0.10.0 — production beta.** The compiler pipeline (Lexer → Parser → Emitter)
|
|
177
180
|
is complete. The [Prettier plugin](src/prettier/) provides 16 deactivatable
|
|
178
181
|
formatting options including attribute style preservation, statement merging,
|
|
179
182
|
blank line handling, and CoffeeScript code formatting.
|
|
@@ -194,6 +197,9 @@ blank line handling, and CoffeeScript code formatting.
|
|
|
194
197
|
| Haml/HTML comments | ✅ |
|
|
195
198
|
| `:filter` blocks | ✅ |
|
|
196
199
|
| Prologue passthrough (`import`, `@decorator`) | ✅ |
|
|
200
|
+
| `---` render-time CoffeeScript block | ✅ |
|
|
201
|
+
| `===` yield block (fenced output) | ✅ |
|
|
202
|
+
| `~~~` module-scope preamble block | ✅ |
|
|
197
203
|
| Component / HOC wrapping (`wrap: 'observer'`) | ✅ |
|
|
198
204
|
| Vite plugin with HMR | ✅ |
|
|
199
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', '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
|
|
|
@@ -319,6 +319,71 @@ export var Filter = (function() {
|
|
|
319
319
|
|
|
320
320
|
}).call(this);
|
|
321
321
|
|
|
322
|
+
export var CoffeeBlock = (function() {
|
|
323
|
+
// ─── CoffeeBlock ───────────────────────────────────────────
|
|
324
|
+
// A fenced raw CoffeeScript block (--- ... ---). Its content is a
|
|
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.
|
|
328
|
+
class CoffeeBlock extends Node {
|
|
329
|
+
constructor(content, location = null) {
|
|
330
|
+
super(location);
|
|
331
|
+
this.content = content;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
CoffeeBlock.prototype.kind = 'CoffeeBlock';
|
|
337
|
+
|
|
338
|
+
CoffeeBlock.prototype.content = '';
|
|
339
|
+
|
|
340
|
+
return CoffeeBlock;
|
|
341
|
+
|
|
342
|
+
}).call(this);
|
|
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
|
+
|
|
366
|
+
export var CoffeeYield = (function() {
|
|
367
|
+
// ─── CoffeeYield ───────────────────────────────────────────
|
|
368
|
+
// A fenced CoffeeScript yield block (=== ... ===). The body is a
|
|
369
|
+
// multi-line CoffeeScript program whose final value is yielded as
|
|
370
|
+
// content at that position (the multiline counterpart to `=`).
|
|
371
|
+
class CoffeeYield extends Node {
|
|
372
|
+
constructor(content, location = null) {
|
|
373
|
+
super(location);
|
|
374
|
+
this.content = content;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
CoffeeYield.prototype.kind = 'CoffeeYield';
|
|
380
|
+
|
|
381
|
+
CoffeeYield.prototype.content = '';
|
|
382
|
+
|
|
383
|
+
return CoffeeYield;
|
|
384
|
+
|
|
385
|
+
}).call(this);
|
|
386
|
+
|
|
322
387
|
export var Doctype = (function() {
|
|
323
388
|
// ─── Doctype ───────────────────────────────────────────────
|
|
324
389
|
class Doctype extends Node {
|
package/dist/cli.js
CHANGED
|
File without changes
|
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, compileComponentBody, emitArrowOutput, emitArrowOutputToJs, emitChildToJs, emitComment, emitConditional, emitControlFlow, emitControlFlowToJs, emitDoctype, emitElement, emitElseBranch, emitFilter, emitFilterToJs, emitImplicitDiv, emitLoop, emitNode, emitNodes, emitOutput, emitStatement, emitText, escapeString, mapSourceLocation, splitJsStatements, tagToJs;
|
|
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,
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
ControlFlow,
|
|
12
12
|
Comment,
|
|
13
13
|
Filter,
|
|
14
|
+
CoffeeBlock,
|
|
15
|
+
CoffeeYield,
|
|
16
|
+
CoffeePreamble,
|
|
14
17
|
Doctype,
|
|
15
18
|
Node,
|
|
16
19
|
Expression
|
|
@@ -18,7 +21,8 @@ import {
|
|
|
18
21
|
|
|
19
22
|
import {
|
|
20
23
|
compileExpression,
|
|
21
|
-
compileStatement
|
|
24
|
+
compileStatement,
|
|
25
|
+
compileYield
|
|
22
26
|
} from './expressions.js';
|
|
23
27
|
|
|
24
28
|
import {
|
|
@@ -36,6 +40,11 @@ EmitState = (function() {
|
|
|
36
40
|
class EmitState {
|
|
37
41
|
constructor(options1 = {}) {
|
|
38
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 = [];
|
|
39
48
|
if (this.options.sourceMap && this.options.filename) {
|
|
40
49
|
this.sourceMapGenerator = new SourceMapGenerator({
|
|
41
50
|
file: this.options.filename
|
|
@@ -110,7 +119,7 @@ EmitState = (function() {
|
|
|
110
119
|
|
|
111
120
|
// ─── Public API ────────────────────────────────────────────
|
|
112
121
|
export var emit = function(ast, options = {}) {
|
|
113
|
-
var bodyExpr, hocs, inner, j, len, line, name, ref, result, state, wrap, wrapped;
|
|
122
|
+
var bodyExpr, hocs, inner, j, k, len, len1, line, name, p, preambles, ref, result, state, wrap, wrapped;
|
|
114
123
|
state = new EmitState(options);
|
|
115
124
|
state.emitLine(`import { jsx, jsxs, Fragment } from \"${options.jsxRuntime || 'react/jsx-runtime'}\";`);
|
|
116
125
|
// Emit prologue lines
|
|
@@ -122,6 +131,18 @@ export var emit = function(ast, options = {}) {
|
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
state.emitLine();
|
|
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));
|
|
143
|
+
}
|
|
144
|
+
state.emitLine();
|
|
145
|
+
}
|
|
125
146
|
wrap = options.wrap;
|
|
126
147
|
if (wrap && wrap !== 'none') {
|
|
127
148
|
name = options.componentName || 'Component';
|
|
@@ -231,6 +252,34 @@ splitJsStatements = function(body) {
|
|
|
231
252
|
};
|
|
232
253
|
|
|
233
254
|
// ─── Node Emitters ─────────────────────────────────────────
|
|
255
|
+
|
|
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) {
|
|
260
|
+
var i, node, ref;
|
|
261
|
+
if (!(nodes != null ? nodes.length : void 0)) {
|
|
262
|
+
return acc;
|
|
263
|
+
}
|
|
264
|
+
i = 0;
|
|
265
|
+
while (i < nodes.length) {
|
|
266
|
+
node = nodes[i];
|
|
267
|
+
if (node instanceof CoffeePreamble) {
|
|
268
|
+
acc.push(node);
|
|
269
|
+
nodes.splice(i, 1);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (((ref = node.children) != null ? ref.length : void 0) > 0) {
|
|
273
|
+
collectCoffeePreamble(node.children, acc);
|
|
274
|
+
}
|
|
275
|
+
if (node.next != null) {
|
|
276
|
+
collectCoffeePreamble([node.next], acc);
|
|
277
|
+
}
|
|
278
|
+
i++;
|
|
279
|
+
}
|
|
280
|
+
return acc;
|
|
281
|
+
};
|
|
282
|
+
|
|
234
283
|
emitNodes = function(nodes, state, isRoot = false) {
|
|
235
284
|
var i, node, remaining;
|
|
236
285
|
i = 0;
|
|
@@ -248,6 +297,15 @@ emitNodes = function(nodes, state, isRoot = false) {
|
|
|
248
297
|
};
|
|
249
298
|
|
|
250
299
|
emitNode = function(node, state, isRoot) {
|
|
300
|
+
if (node instanceof CoffeeBlock) {
|
|
301
|
+
return emitCoffeeBlock(node, state);
|
|
302
|
+
}
|
|
303
|
+
if (node instanceof CoffeePreamble) { // hoisted to module scope above
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (node instanceof CoffeeYield) {
|
|
307
|
+
return emitCoffeeYield(node, state);
|
|
308
|
+
}
|
|
251
309
|
if (node instanceof Element) {
|
|
252
310
|
return emitElement(node, state);
|
|
253
311
|
} else if (node instanceof ImplicitDiv) {
|
|
@@ -396,7 +454,7 @@ emitImplicitDiv = function(div, state) {
|
|
|
396
454
|
|
|
397
455
|
// ─── Child Emission ────────────────────────────────────────
|
|
398
456
|
emitChildToJs = function(node, state) {
|
|
399
|
-
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;
|
|
400
458
|
if (node instanceof Text) {
|
|
401
459
|
return escapeString(node.value);
|
|
402
460
|
}
|
|
@@ -456,6 +514,13 @@ emitChildToJs = function(node, state) {
|
|
|
456
514
|
if (node instanceof Filter) {
|
|
457
515
|
return emitFilterToJs(node, state);
|
|
458
516
|
}
|
|
517
|
+
if (node instanceof CoffeeBlock) {
|
|
518
|
+
js = compileStatement(node.content, node.location);
|
|
519
|
+
return `/* - ${js} */ null`;
|
|
520
|
+
}
|
|
521
|
+
if (node instanceof CoffeeYield) {
|
|
522
|
+
return compileYield(node.content, node.location);
|
|
523
|
+
}
|
|
459
524
|
return 'null';
|
|
460
525
|
};
|
|
461
526
|
|
|
@@ -496,6 +561,20 @@ emitStatement = function(cf, remaining, state) {
|
|
|
496
561
|
}
|
|
497
562
|
};
|
|
498
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
|
+
|
|
499
578
|
emitLoop = function(cf, remaining, state) {
|
|
500
579
|
var cond, expr, forMatch, iterable, vars;
|
|
501
580
|
if (cf.controlKind === 'for') {
|
|
@@ -683,6 +762,12 @@ emitOutput = function(out, state) {
|
|
|
683
762
|
return state.emitLine(result + ';');
|
|
684
763
|
};
|
|
685
764
|
|
|
765
|
+
emitCoffeeYield = function(node, state) {
|
|
766
|
+
var js;
|
|
767
|
+
js = compileYield(node.content, node.location);
|
|
768
|
+
return state.emitLine(`jsx(Fragment, { children: ${js} });`);
|
|
769
|
+
};
|
|
770
|
+
|
|
686
771
|
emitArrowOutput = function(exprSource, elementChildren, out, state) {
|
|
687
772
|
var arrowArgs, arrowFn, arrowMatch, body, callTarget, compiledTarget, result, stripped;
|
|
688
773
|
stripped = exprSource.replace(/\s*->\s*$/, '').trim();
|
package/dist/expressions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
2
|
// expressions.coffee — CoffeeScript expression compilation bridge
|
|
3
|
-
var _coffeeScriptAvailable, _coffeeScriptUnavailableReason, _require, checkCoffeeScript, collapseWs, loadCoffeeScript, objectTarget, splitTopLevelStatements, stripCoffeeWrapper, stripToConst,
|
|
3
|
+
var _coffeeScriptAvailable, _coffeeScriptUnavailableReason, _require, checkCoffeeScript, collapseWs, isSingleExpression, loadCoffeeScript, objectTarget, splitTopLevelStatements, stripCoffeeWrapper, stripToConst,
|
|
4
4
|
indexOf = [].indexOf;
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -179,6 +179,55 @@ export var compileStatement = function(source, location = null) {
|
|
|
179
179
|
return source;
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
// ─── Compile yield block to JS expression ──────────────────
|
|
183
|
+
// Compiles a fenced `=== ... ===` body to a single JS expression whose
|
|
184
|
+
// value is the body's final result (the multiline counterpart to `=`).
|
|
185
|
+
// A single expression compiles cleanly; multiple statements are wrapped
|
|
186
|
+
// in an IIFE so CoffeeScript's implicit return becomes the yielded value.
|
|
187
|
+
export var compileYield = function(source, location = null) {
|
|
188
|
+
var CoffeeScript, e, indented, js, msg, wrapped;
|
|
189
|
+
if (source.trim() === '') {
|
|
190
|
+
return '';
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
CoffeeScript = loadCoffeeScript();
|
|
194
|
+
if (!(CoffeeScript && typeof CoffeeScript.compile === 'function')) {
|
|
195
|
+
return source;
|
|
196
|
+
}
|
|
197
|
+
if (isSingleExpression(source)) {
|
|
198
|
+
return compileExpression(new Expression(source), location);
|
|
199
|
+
}
|
|
200
|
+
indented = source.replace(/^/gm, ' ');
|
|
201
|
+
wrapped = `(->\n${indented}\n)()`;
|
|
202
|
+
js = CoffeeScript.compile(wrapped, {
|
|
203
|
+
bare: true,
|
|
204
|
+
inlineMap: false
|
|
205
|
+
});
|
|
206
|
+
return collapseWs(js.trim()).replace(/;\s*$/, '');
|
|
207
|
+
} catch (error) {
|
|
208
|
+
e = error;
|
|
209
|
+
msg = e instanceof Error ? e.message : String(e);
|
|
210
|
+
if (location) {
|
|
211
|
+
throw new CompileError(`CoffeeScript yield error: ${msg}`, 'emitter', 'YIELD_ERROR', location, `Check block: ${source.slice(0, 80)}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return source;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
isSingleExpression = function(source) {
|
|
218
|
+
var CoffeeScript, ref, ref1, root;
|
|
219
|
+
try {
|
|
220
|
+
CoffeeScript = loadCoffeeScript();
|
|
221
|
+
if (CoffeeScript && typeof CoffeeScript.nodes === 'function') {
|
|
222
|
+
root = CoffeeScript.nodes(source);
|
|
223
|
+
return (root != null ? (ref = root.body) != null ? (ref1 = ref.expressions) != null ? ref1.length : void 0 : void 0 : void 0) === 1;
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
false;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
};
|
|
230
|
+
|
|
182
231
|
// ─── Convert var → const/let ───────────────────────────────
|
|
183
232
|
|
|
184
233
|
// Extract the bound variable name from one entry of an object
|
|
@@ -194,7 +243,7 @@ objectTarget = function(pair) {
|
|
|
194
243
|
};
|
|
195
244
|
|
|
196
245
|
stripToConst = function(js) {
|
|
197
|
-
var allConsumed,
|
|
246
|
+
var allConsumed, body, constReplacements, consumed, idx, inner, j, len, m, n, name, names, s, stmts, varIdx, varNames;
|
|
198
247
|
stmts = splitTopLevelStatements(js);
|
|
199
248
|
if (stmts.length === 0) {
|
|
200
249
|
return js.replace(/;\s*$/, '');
|
|
@@ -220,10 +269,10 @@ stripToConst = function(js) {
|
|
|
220
269
|
}
|
|
221
270
|
return results;
|
|
222
271
|
})();
|
|
223
|
-
|
|
272
|
+
constReplacements = {};
|
|
224
273
|
consumed = [];
|
|
225
|
-
for (j = 0, len = body.length; j < len; j
|
|
226
|
-
s = body[
|
|
274
|
+
for (idx = j = 0, len = body.length; j < len; idx = ++j) {
|
|
275
|
+
s = body[idx];
|
|
227
276
|
m = s.match(/^\[([^\]]*)\]\s*=/);
|
|
228
277
|
if (m != null) {
|
|
229
278
|
// Array destructuring: [a, b] = expr
|
|
@@ -240,7 +289,7 @@ stripToConst = function(js) {
|
|
|
240
289
|
return results;
|
|
241
290
|
})();
|
|
242
291
|
consumed = consumed.concat(names);
|
|
243
|
-
|
|
292
|
+
constReplacements[idx] = 'const ' + s;
|
|
244
293
|
continue;
|
|
245
294
|
}
|
|
246
295
|
m = s.match(/^\(\s*\{([^}]*)\}\s*=.*\)$/);
|
|
@@ -260,7 +309,7 @@ stripToConst = function(js) {
|
|
|
260
309
|
return results;
|
|
261
310
|
})();
|
|
262
311
|
consumed = consumed.concat(names);
|
|
263
|
-
|
|
312
|
+
constReplacements[idx] = 'const ' + inner;
|
|
264
313
|
continue;
|
|
265
314
|
}
|
|
266
315
|
name = varNames.find(function(n) {
|
|
@@ -268,14 +317,24 @@ stripToConst = function(js) {
|
|
|
268
317
|
});
|
|
269
318
|
if (name != null) {
|
|
270
319
|
consumed.push(name);
|
|
271
|
-
|
|
320
|
+
constReplacements[idx] = 'const ' + s;
|
|
272
321
|
}
|
|
273
322
|
}
|
|
274
323
|
allConsumed = varNames.every(function(n) {
|
|
275
324
|
return indexOf.call(consumed, n) >= 0;
|
|
276
325
|
});
|
|
277
|
-
if (
|
|
278
|
-
return
|
|
326
|
+
if (allConsumed && Object.keys(constReplacements).length > 0) {
|
|
327
|
+
return ((function() {
|
|
328
|
+
var k, len1, ref, results;
|
|
329
|
+
results = [];
|
|
330
|
+
for (idx = k = 0, len1 = body.length; k < len1; idx = ++k) {
|
|
331
|
+
s = body[idx];
|
|
332
|
+
// Preserve non-assignment statements (imports, calls, declarations)
|
|
333
|
+
// in their original order, converting only bound assignments to const.
|
|
334
|
+
results.push((ref = constReplacements[idx]) != null ? ref : s);
|
|
335
|
+
}
|
|
336
|
+
return results;
|
|
337
|
+
})()).join('; ');
|
|
279
338
|
}
|
|
280
339
|
return ((function() {
|
|
281
340
|
var k, len1, results;
|
package/dist/lexer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
2
|
// lexer.coffee — CoffeeHaml tokenizer
|
|
3
3
|
// Converts raw .chaml source into a token stream with INDENT/DEDENT
|
|
4
|
-
var countIndent, dedentToken, extractBracketed, indentToken, isHamlConstruct, looksLikeBareAttributes, parseModifiersAndAttrs, preMergeBracketLines, tokenizeLine;
|
|
4
|
+
var countIndent, dedentLines, dedentToken, extractBracketed, indentToken, isHamlConstruct, looksLikeBareAttributes, parseModifiersAndAttrs, preMergeBracketLines, stripIndent, tokenizeLine;
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
SourceLocation
|
|
@@ -21,6 +21,9 @@ export var TokenType = {
|
|
|
21
21
|
COMMENT: 'COMMENT',
|
|
22
22
|
HTML_COMMENT: 'HTML_COMMENT',
|
|
23
23
|
FILTER: 'FILTER',
|
|
24
|
+
COFFEE_BLOCK: 'COFFEE_BLOCK',
|
|
25
|
+
COFFEE_YIELD: 'COFFEE_YIELD',
|
|
26
|
+
COFFEE_PREAMBLE: 'COFFEE_PREAMBLE',
|
|
24
27
|
DOCTYPE: 'DOCTYPE',
|
|
25
28
|
TEXT: 'TEXT',
|
|
26
29
|
PROLOGUE: 'PROLOGUE',
|
|
@@ -75,21 +78,23 @@ preMergeBracketLines = function(lines) {
|
|
|
75
78
|
};
|
|
76
79
|
|
|
77
80
|
export var tokenize = function(source, filename = null) {
|
|
78
|
-
var content, currentIndent,
|
|
81
|
+
var body, bodyLine, content, currentIndent, fence, fenceIndent, fenceStartLine, fenceStartOffset, i, inPrologue, indent, indentStack, line, lineIndex, lineLength, lineStartOffset, lineTokens, lines, offset, rawLine, tokens;
|
|
79
82
|
tokens = [];
|
|
80
83
|
lines = preMergeBracketLines(source.split('\n'));
|
|
81
84
|
indentStack = [0];
|
|
82
85
|
offset = 0;
|
|
83
86
|
lineIndex = 0;
|
|
84
87
|
inPrologue = true;
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
i = 0;
|
|
89
|
+
while (i < lines.length) {
|
|
90
|
+
rawLine = lines[i];
|
|
87
91
|
line = rawLine.replace(/\r$/, '');
|
|
88
92
|
lineLength = rawLine.length + 1;
|
|
89
93
|
// Skip empty lines
|
|
90
94
|
if (line.trim() === '') {
|
|
91
95
|
offset += lineLength;
|
|
92
96
|
lineIndex++;
|
|
97
|
+
i++;
|
|
93
98
|
continue;
|
|
94
99
|
}
|
|
95
100
|
indent = countIndent(line);
|
|
@@ -97,6 +102,85 @@ export var tokenize = function(source, filename = null) {
|
|
|
97
102
|
if (content === '') {
|
|
98
103
|
offset += lineLength;
|
|
99
104
|
lineIndex++;
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
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).
|
|
118
|
+
fence = content.trim();
|
|
119
|
+
if (fence === '---' || fence === '===' || fence === '~~~') {
|
|
120
|
+
inPrologue = false;
|
|
121
|
+
fenceIndent = indent;
|
|
122
|
+
fenceStartLine = lineIndex;
|
|
123
|
+
fenceStartOffset = offset + indent;
|
|
124
|
+
// Handle indentation change (same as a normal line)
|
|
125
|
+
currentIndent = indentStack[indentStack.length - 1];
|
|
126
|
+
if (fenceIndent > currentIndent) {
|
|
127
|
+
indentStack.push(fenceIndent);
|
|
128
|
+
tokens.push(indentToken(fenceIndent, fenceStartOffset, fenceIndent, filename, fenceStartLine));
|
|
129
|
+
} else if (fenceIndent < currentIndent) {
|
|
130
|
+
while (indentStack.length > 1 && fenceIndent < indentStack[indentStack.length - 1]) {
|
|
131
|
+
indentStack.pop();
|
|
132
|
+
tokens.push(dedentToken(fenceStartOffset, filename, fenceStartLine));
|
|
133
|
+
}
|
|
134
|
+
if (fenceIndent !== indentStack[indentStack.length - 1]) {
|
|
135
|
+
indentStack.push(fenceIndent);
|
|
136
|
+
tokens.push(indentToken(fenceIndent, fenceStartOffset, fenceIndent, filename, fenceStartLine));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Consume opening fence
|
|
140
|
+
offset += lineLength;
|
|
141
|
+
lineIndex++;
|
|
142
|
+
i++;
|
|
143
|
+
// Collect body until closing fence
|
|
144
|
+
body = [];
|
|
145
|
+
while (i < lines.length) {
|
|
146
|
+
bodyLine = lines[i].replace(/\r$/, '');
|
|
147
|
+
if (bodyLine.trim() === fence) {
|
|
148
|
+
offset += lines[i].length + 1;
|
|
149
|
+
lineIndex++;
|
|
150
|
+
i++;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
body.push(bodyLine);
|
|
154
|
+
offset += lines[i].length + 1;
|
|
155
|
+
lineIndex++;
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
tokens.push({
|
|
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
|
+
})(),
|
|
169
|
+
value: (dedentLines(body)).join('\n'),
|
|
170
|
+
location: {
|
|
171
|
+
start: {
|
|
172
|
+
line: fenceStartLine,
|
|
173
|
+
column: fenceIndent
|
|
174
|
+
},
|
|
175
|
+
end: {
|
|
176
|
+
line: lineIndex,
|
|
177
|
+
column: 0
|
|
178
|
+
},
|
|
179
|
+
offset: fenceStartOffset,
|
|
180
|
+
length: offset - fenceStartOffset,
|
|
181
|
+
file: filename
|
|
182
|
+
}
|
|
183
|
+
});
|
|
100
184
|
continue;
|
|
101
185
|
}
|
|
102
186
|
// Prologue detection: non-indented JS before first HAML construct
|
|
@@ -120,6 +204,7 @@ export var tokenize = function(source, filename = null) {
|
|
|
120
204
|
});
|
|
121
205
|
offset += lineLength;
|
|
122
206
|
lineIndex++;
|
|
207
|
+
i++;
|
|
123
208
|
continue;
|
|
124
209
|
}
|
|
125
210
|
inPrologue = false;
|
|
@@ -144,6 +229,7 @@ export var tokenize = function(source, filename = null) {
|
|
|
144
229
|
tokens.push(...lineTokens);
|
|
145
230
|
offset += lineLength;
|
|
146
231
|
lineIndex++;
|
|
232
|
+
i++;
|
|
147
233
|
}
|
|
148
234
|
// Emit remaining DEDENT tokens at EOF
|
|
149
235
|
while (indentStack.length > 1) {
|
|
@@ -535,6 +621,52 @@ countIndent = function(line) {
|
|
|
535
621
|
return count;
|
|
536
622
|
};
|
|
537
623
|
|
|
624
|
+
// Strip up to `amount` visual columns of leading whitespace (space=1, tab=2).
|
|
625
|
+
stripIndent = function(line, amount) {
|
|
626
|
+
var ch, i, seen;
|
|
627
|
+
seen = 0;
|
|
628
|
+
i = 0;
|
|
629
|
+
while (i < line.length && seen < amount) {
|
|
630
|
+
ch = line[i];
|
|
631
|
+
if (ch === ' ') {
|
|
632
|
+
seen += 1;
|
|
633
|
+
} else if (ch === '\t') {
|
|
634
|
+
seen += 2;
|
|
635
|
+
} else {
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
i++;
|
|
639
|
+
}
|
|
640
|
+
return line.slice(i);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// Normalize a fenced block's body to column 0 by removing the minimum
|
|
644
|
+
// indentation found across its non-empty lines.
|
|
645
|
+
dedentLines = function(lines) {
|
|
646
|
+
var amount, ind, j, k, l, len, len1, min, results;
|
|
647
|
+
min = null;
|
|
648
|
+
for (j = 0, len = lines.length; j < len; j++) {
|
|
649
|
+
l = lines[j];
|
|
650
|
+
if (l.trim() === '') {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
ind = countIndent(l);
|
|
654
|
+
if (min === null || ind < min) {
|
|
655
|
+
min = ind;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
amount = min === null ? 0 : min;
|
|
659
|
+
if (amount === 0) {
|
|
660
|
+
return lines;
|
|
661
|
+
}
|
|
662
|
+
results = [];
|
|
663
|
+
for (k = 0, len1 = lines.length; k < len1; k++) {
|
|
664
|
+
l = lines[k];
|
|
665
|
+
results.push(stripIndent(l, amount));
|
|
666
|
+
}
|
|
667
|
+
return results;
|
|
668
|
+
};
|
|
669
|
+
|
|
538
670
|
indentToken = function(_indent, offset, level, filename = null, line = 0) {
|
|
539
671
|
return {
|
|
540
672
|
type: TokenType.INDENT,
|
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, 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
|
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
ControlFlow,
|
|
17
17
|
Comment,
|
|
18
18
|
Filter,
|
|
19
|
+
CoffeeBlock,
|
|
20
|
+
CoffeeYield,
|
|
21
|
+
CoffeePreamble,
|
|
19
22
|
Doctype,
|
|
20
23
|
Expression,
|
|
21
24
|
Attribute,
|
|
@@ -79,7 +82,7 @@ ParserState = (function() {
|
|
|
79
82
|
var t;
|
|
80
83
|
while (this.current()) {
|
|
81
84
|
t = this.current().type;
|
|
82
|
-
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.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) {
|
|
83
86
|
return;
|
|
84
87
|
}
|
|
85
88
|
this.advance();
|
|
@@ -168,6 +171,12 @@ parseNode = function(state) {
|
|
|
168
171
|
return parseHtmlComment(state);
|
|
169
172
|
case TokenType.FILTER:
|
|
170
173
|
return parseFilter(state);
|
|
174
|
+
case TokenType.COFFEE_BLOCK:
|
|
175
|
+
return parseCoffeeBlock(state);
|
|
176
|
+
case TokenType.COFFEE_YIELD:
|
|
177
|
+
return parseCoffeeYield(state);
|
|
178
|
+
case TokenType.COFFEE_PREAMBLE:
|
|
179
|
+
return parseCoffeePreamble(state);
|
|
171
180
|
case TokenType.DOCTYPE:
|
|
172
181
|
return parseDoctype(state);
|
|
173
182
|
case TokenType.TEXT:
|
|
@@ -461,6 +470,34 @@ parseFilter = function(state) {
|
|
|
461
470
|
return new Filter(filterName, content, token.location);
|
|
462
471
|
};
|
|
463
472
|
|
|
473
|
+
// ─── CoffeeBlock ───────────────────────────────────────────
|
|
474
|
+
parseCoffeeBlock = function(state) {
|
|
475
|
+
var token;
|
|
476
|
+
token = state.expect(TokenType.COFFEE_BLOCK);
|
|
477
|
+
if (!token) {
|
|
478
|
+
return new CoffeeBlock('');
|
|
479
|
+
}
|
|
480
|
+
return new CoffeeBlock(token.value, token.location);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
parseCoffeeYield = function(state) {
|
|
484
|
+
var token;
|
|
485
|
+
token = state.expect(TokenType.COFFEE_YIELD);
|
|
486
|
+
if (!token) {
|
|
487
|
+
return new CoffeeYield('');
|
|
488
|
+
}
|
|
489
|
+
return new CoffeeYield(token.value, token.location);
|
|
490
|
+
};
|
|
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
|
+
|
|
464
501
|
// ─── Doctype ───────────────────────────────────────────────
|
|
465
502
|
parseDoctype = function(state) {
|
|
466
503
|
var token;
|
package/dist/prettier/options.js
CHANGED
|
@@ -81,7 +81,7 @@ export var options = {
|
|
|
81
81
|
attributeMultilineThreshold: {
|
|
82
82
|
category: CAT.ATTRIBUTES,
|
|
83
83
|
type: 'int',
|
|
84
|
-
description: '
|
|
84
|
+
description: 'Force attributes across lines when count >= this. 0 = never force (fold only when exceeding printWidth).'
|
|
85
85
|
},
|
|
86
86
|
attributeSort: {
|
|
87
87
|
category: CAT.ATTRIBUTES,
|
|
@@ -218,7 +218,7 @@ export var defaultOptions = {
|
|
|
218
218
|
inlineThreshold: -1,
|
|
219
219
|
voidElementStyle: 'explicit',
|
|
220
220
|
attributeStyle: 'preserve',
|
|
221
|
-
attributeMultilineThreshold:
|
|
221
|
+
attributeMultilineThreshold: 0,
|
|
222
222
|
attributeSort: 'none',
|
|
223
223
|
quoteStyle: 'preserve',
|
|
224
224
|
coffeeScriptFormat: true,
|
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, mergeAllStatements, opts, printComment, printControlFlow, printDoc, printDoctype, printElement, 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,
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
ControlFlow,
|
|
13
13
|
Comment,
|
|
14
14
|
Filter,
|
|
15
|
+
CoffeeBlock,
|
|
16
|
+
CoffeeYield,
|
|
17
|
+
CoffeePreamble,
|
|
15
18
|
Doctype,
|
|
16
19
|
Node,
|
|
17
20
|
Expression,
|
|
@@ -32,7 +35,7 @@ _require = createRequire(import.meta.url);
|
|
|
32
35
|
|
|
33
36
|
_prettier = _require('prettier');
|
|
34
37
|
|
|
35
|
-
({group, indent, hardline, join} = _prettier.doc.builders);
|
|
38
|
+
({group, indent, hardline, line, join} = _prettier.doc.builders);
|
|
36
39
|
|
|
37
40
|
// ─── Format Options ────────────────────────────────────────
|
|
38
41
|
// CoffeeHamlFormatOptions shape (runtime):
|
|
@@ -49,7 +52,7 @@ defaultOpts = {
|
|
|
49
52
|
inlineThreshold: -1,
|
|
50
53
|
voidElementStyle: 'self-closing',
|
|
51
54
|
attributeStyle: 'preserve',
|
|
52
|
-
attributeMultilineThreshold:
|
|
55
|
+
attributeMultilineThreshold: 0,
|
|
53
56
|
attributeSort: 'none',
|
|
54
57
|
quoteStyle: 'preserve',
|
|
55
58
|
coffeeScriptFormat: true,
|
|
@@ -225,6 +228,15 @@ printNode = function(path, o, printFn) {
|
|
|
225
228
|
if (node instanceof Filter) {
|
|
226
229
|
return printFilter(path, o, printFn);
|
|
227
230
|
}
|
|
231
|
+
if (node instanceof CoffeeBlock) {
|
|
232
|
+
return printCoffeeBlock(path, o, printFn);
|
|
233
|
+
}
|
|
234
|
+
if (node instanceof CoffeeYield) {
|
|
235
|
+
return printCoffeeYield(path, o, printFn);
|
|
236
|
+
}
|
|
237
|
+
if (node instanceof CoffeePreamble) {
|
|
238
|
+
return printCoffeePreamble(path, o, printFn);
|
|
239
|
+
}
|
|
228
240
|
if (node instanceof Doctype) {
|
|
229
241
|
return printDoctype(path, o);
|
|
230
242
|
}
|
|
@@ -259,7 +271,7 @@ printDoc = function(path, o, printFn) {
|
|
|
259
271
|
|
|
260
272
|
// ─── Element ───────────────────────────────────────────────
|
|
261
273
|
printElement = function(path, o, printFn) {
|
|
262
|
-
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, isVoid, joined, node, ref, ref1, ref2, ref3, selfClose, tag;
|
|
274
|
+
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, isVoid, joined, node, ref, ref1, ref2, ref3, selfClose, sep, tag;
|
|
263
275
|
node = path.node;
|
|
264
276
|
tag = formatTag(node.tag, o);
|
|
265
277
|
// Classes and ID
|
|
@@ -282,16 +294,18 @@ printElement = function(path, o, printFn) {
|
|
|
282
294
|
} else if (node.isSelfClosing) {
|
|
283
295
|
selfClose = '/';
|
|
284
296
|
}
|
|
285
|
-
header = `%${tag}${classes}${id}
|
|
297
|
+
header = [`%${tag}${classes}${id}`, attrs, selfClose];
|
|
286
298
|
// Children
|
|
287
299
|
children = (ref = node.children) != null ? ref : [];
|
|
288
300
|
if (children.length === 0) {
|
|
289
301
|
return header;
|
|
290
302
|
}
|
|
291
303
|
// Inline child
|
|
292
|
-
if (isSingleInlineChild(children)
|
|
304
|
+
if (isSingleInlineChild(children)) {
|
|
293
305
|
childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
|
|
294
|
-
|
|
306
|
+
// Output (= expr) carries its own separator; Text needs a space
|
|
307
|
+
sep = children[0] instanceof Output ? '' : ' ';
|
|
308
|
+
return [header, sep, childDoc];
|
|
295
309
|
}
|
|
296
310
|
childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
|
|
297
311
|
joined = joinChildrenWithBlanks(children, childDocs, o);
|
|
@@ -300,7 +314,7 @@ printElement = function(path, o, printFn) {
|
|
|
300
314
|
|
|
301
315
|
// ─── ImplicitDiv ───────────────────────────────────────────
|
|
302
316
|
printImplicitDiv = function(path, o, printFn) {
|
|
303
|
-
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, joined, node, ref, ref1, ref2, ref3;
|
|
317
|
+
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, joined, node, ref, ref1, ref2, ref3, sep;
|
|
304
318
|
node = path.node;
|
|
305
319
|
classes = node.classes.map(function(c) {
|
|
306
320
|
return `.${c}`;
|
|
@@ -308,14 +322,15 @@ printImplicitDiv = function(path, o, printFn) {
|
|
|
308
322
|
id = node.id ? `#${node.id}` : '';
|
|
309
323
|
attrStyle = detectStyle(node, o);
|
|
310
324
|
attrs = formatAttributes(node, attrStyle, o);
|
|
311
|
-
header = `${classes}${id}
|
|
325
|
+
header = [`${classes}${id}`, attrs];
|
|
312
326
|
children = (ref = node.children) != null ? ref : [];
|
|
313
327
|
if (children.length === 0) {
|
|
314
328
|
return header;
|
|
315
329
|
}
|
|
316
330
|
if (isSingleInlineChild(children)) {
|
|
317
331
|
childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
|
|
318
|
-
|
|
332
|
+
sep = children[0] instanceof Output ? '' : ' ';
|
|
333
|
+
return [header, sep, childDoc];
|
|
319
334
|
}
|
|
320
335
|
childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
|
|
321
336
|
joined = joinChildrenWithBlanks(children, childDocs, o);
|
|
@@ -473,6 +488,38 @@ printFilter = function(path, o, _printFn) {
|
|
|
473
488
|
}
|
|
474
489
|
};
|
|
475
490
|
|
|
491
|
+
// ─── CoffeeBlock ───────────────────────────────────────────
|
|
492
|
+
|
|
493
|
+
// Build a fenced block doc using `hardline` so Prettier can re-indent the
|
|
494
|
+
// dedented body to the correct nesting level (raw `\n` strings would not).
|
|
495
|
+
printFence = function(fence, content) {
|
|
496
|
+
var k, len, parts, ref;
|
|
497
|
+
if (content) {
|
|
498
|
+
parts = [fence];
|
|
499
|
+
ref = content.split('\n');
|
|
500
|
+
for (k = 0, len = ref.length; k < len; k++) {
|
|
501
|
+
line = ref[k];
|
|
502
|
+
parts.push(hardline, line);
|
|
503
|
+
}
|
|
504
|
+
parts.push(hardline, fence);
|
|
505
|
+
return parts;
|
|
506
|
+
} else {
|
|
507
|
+
return [fence, hardline, fence];
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
printCoffeeBlock = function(path, o, _printFn) {
|
|
512
|
+
return printFence('---', path.node.content);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
printCoffeeYield = function(path, o, _printFn) {
|
|
516
|
+
return printFence('===', path.node.content);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
printCoffeePreamble = function(path, o, _printFn) {
|
|
520
|
+
return printFence('~~~', path.node.content);
|
|
521
|
+
};
|
|
522
|
+
|
|
476
523
|
// ─── Doctype ───────────────────────────────────────────────
|
|
477
524
|
printDoctype = function(path, o) {
|
|
478
525
|
var node;
|
|
@@ -501,12 +548,14 @@ detectStyle = function(node, o) {
|
|
|
501
548
|
};
|
|
502
549
|
|
|
503
550
|
formatAttributes = function(node, style, o) {
|
|
504
|
-
var attr, attrs, bareParts, expr, k, len, name, parts, ref, ref1, ref2, val;
|
|
551
|
+
var attr, attrs, bareParts, close, expr, forceBreak, k, len, name, open, parts, ref, ref1, ref2, sep, val;
|
|
505
552
|
attrs = (ref = node.attributes) != null ? ref : [];
|
|
506
553
|
if (!(attrs.length > 0)) {
|
|
507
554
|
return '';
|
|
508
555
|
}
|
|
509
|
-
// Bare style: HTML-style key="val" key2={expr} — space-separated
|
|
556
|
+
// Bare style: HTML-style key="val" key2={expr} — space-separated.
|
|
557
|
+
// Never folded: an indented continuation line would parse as a child
|
|
558
|
+
// element, silently changing the tree. Bare attributes stay inline.
|
|
510
559
|
if (style === 'bare') {
|
|
511
560
|
bareParts = (function() {
|
|
512
561
|
var k, len, ref1, ref2, results;
|
|
@@ -528,38 +577,44 @@ formatAttributes = function(node, style, o) {
|
|
|
528
577
|
return results;
|
|
529
578
|
})();
|
|
530
579
|
return ` ${bareParts.join(' ')}`;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (o.coffeeScriptFormat) {
|
|
538
|
-
expr = formatCoffeeScript(expr, o);
|
|
539
|
-
}
|
|
540
|
-
parts.push(style === 'parens' ? `...${expr}` : `{${expr}...}`);
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
name = attr.name;
|
|
544
|
-
val = (ref2 = attr.value.source) != null ? ref2 : '';
|
|
580
|
+
}
|
|
581
|
+
parts = [];
|
|
582
|
+
for (k = 0, len = attrs.length; k < len; k++) {
|
|
583
|
+
attr = attrs[k];
|
|
584
|
+
if (attr.spread) {
|
|
585
|
+
expr = (ref1 = attr.expression.source) != null ? ref1 : '';
|
|
545
586
|
if (o.coffeeScriptFormat) {
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
if (attr.shorthand) {
|
|
549
|
-
parts.push(name);
|
|
550
|
-
} else {
|
|
551
|
-
parts.push(`${name}: ${val}`);
|
|
587
|
+
expr = formatCoffeeScript(expr, o);
|
|
552
588
|
}
|
|
589
|
+
parts.push(style === 'parens' ? `...${expr}` : `{${expr}...}`);
|
|
590
|
+
continue;
|
|
553
591
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return `( ${parts.join(', ')} )`;
|
|
559
|
-
default:
|
|
560
|
-
return parts.join(', ');
|
|
592
|
+
name = attr.name;
|
|
593
|
+
val = (ref2 = attr.value.source) != null ? ref2 : '';
|
|
594
|
+
if (o.coffeeScriptFormat) {
|
|
595
|
+
val = formatCoffeeScript(val, o);
|
|
561
596
|
}
|
|
597
|
+
if (attr.shorthand) {
|
|
598
|
+
parts.push(name);
|
|
599
|
+
} else {
|
|
600
|
+
parts.push(`${name}: ${val}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Count-based forcing: a positive threshold breaks even when the
|
|
604
|
+
// attributes would otherwise fit within printWidth.
|
|
605
|
+
forceBreak = o.attributeMultilineThreshold > 0 && attrs.length >= o.attributeMultilineThreshold;
|
|
606
|
+
sep = forceBreak ? hardline : line;
|
|
607
|
+
if (style === 'braces') {
|
|
608
|
+
open = '{';
|
|
609
|
+
close = '}';
|
|
610
|
+
} else if (style === 'parens') {
|
|
611
|
+
open = '(';
|
|
612
|
+
close = ')';
|
|
613
|
+
} else {
|
|
614
|
+
// Unknown style — inline fallback, no folding.
|
|
615
|
+
return parts.join(', ');
|
|
562
616
|
}
|
|
617
|
+
return group([open, indent([sep, join([',', sep], parts)]), sep, close]);
|
|
563
618
|
};
|
|
564
619
|
|
|
565
620
|
// ─── CoffeeScript Embed (for Prettier embed) ───────────────
|
package/docs/grammar.md
CHANGED
|
@@ -292,6 +292,103 @@ depending on configuration.
|
|
|
292
292
|
|
|
293
293
|
---
|
|
294
294
|
|
|
295
|
+
## CoffeeScript Blocks (`---`)
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
CoffeeBlock := '---' Newline
|
|
299
|
+
CoffeeBody
|
|
300
|
+
'---' Newline
|
|
301
|
+
|
|
302
|
+
CoffeeBody := Raw CoffeeScript lines, dedented to column 0.
|
|
303
|
+
```
|
|
304
|
+
|
|
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.
|
|
310
|
+
|
|
311
|
+
```haml
|
|
312
|
+
---
|
|
313
|
+
total = price * quantity
|
|
314
|
+
tax = total * 0.08
|
|
315
|
+
---
|
|
316
|
+
%p= total + tax
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Semantics:
|
|
320
|
+
|
|
321
|
+
- **Escapes indentation**: body lines are dedented to column 0, so a block
|
|
322
|
+
nested under an element still writes CoffeeScript flush-left.
|
|
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
|
+
- **No markup**: the block contributes nothing to the render tree.
|
|
328
|
+
|
|
329
|
+
Note: `---` is a CoffeeHaml fence marker, not an element. For a horizontal
|
|
330
|
+
rule use `%hr` — mapping `---` to `<hr>` is a Markdown/YAML convention, not
|
|
331
|
+
Haml.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Yield Blocks (`===`)
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
CoffeeYield := '===' Newline
|
|
339
|
+
CoffeeBody
|
|
340
|
+
'===' Newline
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The `===` fence is the multiline counterpart to `=` (output): its body is
|
|
344
|
+
CoffeeScript, dedented to column 0 exactly like a `---` block, but instead
|
|
345
|
+
of running as statements its **final value is yielded as content**
|
|
346
|
+
at that position in the render tree (escaped, matching `=`).
|
|
347
|
+
|
|
348
|
+
```haml
|
|
349
|
+
%p
|
|
350
|
+
===
|
|
351
|
+
name = computeName()
|
|
352
|
+
"Hello, #{name}"
|
|
353
|
+
===
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
A single-expression body compiles directly; a multi-statement body is
|
|
357
|
+
wrapped in an IIFE so the last expression is the yielded value.
|
|
358
|
+
|
|
359
|
+
---
|
|
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
|
+
|
|
295
392
|
## Raw Text / Passthrough
|
|
296
393
|
|
|
297
394
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coffeehaml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Haml structure. CoffeeScript semantics. React runtime. Zero runtime. Vite-native.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "coffee -c -o dist/ src/*.coffee && coffee -c -o dist/prettier/ src/prettier/*.coffee",
|
|
64
64
|
"dev": "coffee -c -w -o dist/ src/*.coffee & coffee -c -w -o dist/prettier/ src/prettier/*.coffee",
|
|
65
|
-
"test": "node test/run-compiler-tests.mjs && node test/prettier/run-integration-tests.mjs && node test/prettier/run-coffeescript-tests.mjs && node test/prettier/run-statement-merge-tests.mjs",
|
|
65
|
+
"test": "node test/run-compiler-tests.mjs && node test/run-coffee-block-tests.mjs && node test/prettier/run-integration-tests.mjs && node test/prettier/run-coffeescript-tests.mjs && node test/prettier/run-statement-merge-tests.mjs",
|
|
66
66
|
"prepublishOnly": "npm run build",
|
|
67
67
|
"fmt": "prettier --write --plugin=. **/*.chaml"
|
|
68
68
|
},
|
|
@@ -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*)",
|