coffeehaml 0.7.6 → 0.8.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 -1
- package/dist/ast.js +43 -1
- package/dist/cli.js +0 -0
- package/dist/emitter.js +59 -3
- package/dist/expressions.js +152 -21
- package/dist/lexer.js +123 -4
- package/dist/parser.js +27 -2
- package/dist/prettier/options.js +2 -2
- package/dist/prettier/printer.js +86 -39
- package/docs/grammar.md +66 -0
- package/package.json +3 -3
- package/dist/coffeescript-formatter.js +0 -309
- package/dist/options.js +0 -232
- package/dist/printer.js +0 -583
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, 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,8 @@ import {
|
|
|
12
12
|
ControlFlow,
|
|
13
13
|
Comment,
|
|
14
14
|
Filter,
|
|
15
|
+
CoffeeBlock,
|
|
16
|
+
CoffeeYield,
|
|
15
17
|
Doctype,
|
|
16
18
|
Node,
|
|
17
19
|
Expression,
|
|
@@ -32,7 +34,7 @@ _require = createRequire(import.meta.url);
|
|
|
32
34
|
|
|
33
35
|
_prettier = _require('prettier');
|
|
34
36
|
|
|
35
|
-
({group, indent, hardline, join} = _prettier.doc.builders);
|
|
37
|
+
({group, indent, hardline, line, join} = _prettier.doc.builders);
|
|
36
38
|
|
|
37
39
|
// ─── Format Options ────────────────────────────────────────
|
|
38
40
|
// CoffeeHamlFormatOptions shape (runtime):
|
|
@@ -49,7 +51,7 @@ defaultOpts = {
|
|
|
49
51
|
inlineThreshold: -1,
|
|
50
52
|
voidElementStyle: 'self-closing',
|
|
51
53
|
attributeStyle: 'preserve',
|
|
52
|
-
attributeMultilineThreshold:
|
|
54
|
+
attributeMultilineThreshold: 0,
|
|
53
55
|
attributeSort: 'none',
|
|
54
56
|
quoteStyle: 'preserve',
|
|
55
57
|
coffeeScriptFormat: true,
|
|
@@ -225,6 +227,12 @@ printNode = function(path, o, printFn) {
|
|
|
225
227
|
if (node instanceof Filter) {
|
|
226
228
|
return printFilter(path, o, printFn);
|
|
227
229
|
}
|
|
230
|
+
if (node instanceof CoffeeBlock) {
|
|
231
|
+
return printCoffeeBlock(path, o, printFn);
|
|
232
|
+
}
|
|
233
|
+
if (node instanceof CoffeeYield) {
|
|
234
|
+
return printCoffeeYield(path, o, printFn);
|
|
235
|
+
}
|
|
228
236
|
if (node instanceof Doctype) {
|
|
229
237
|
return printDoctype(path, o);
|
|
230
238
|
}
|
|
@@ -259,7 +267,7 @@ printDoc = function(path, o, printFn) {
|
|
|
259
267
|
|
|
260
268
|
// ─── Element ───────────────────────────────────────────────
|
|
261
269
|
printElement = function(path, o, printFn) {
|
|
262
|
-
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, isVoid, joined, node, ref, ref1, ref2, ref3, selfClose, tag;
|
|
270
|
+
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, isVoid, joined, node, ref, ref1, ref2, ref3, selfClose, sep, tag;
|
|
263
271
|
node = path.node;
|
|
264
272
|
tag = formatTag(node.tag, o);
|
|
265
273
|
// Classes and ID
|
|
@@ -282,16 +290,18 @@ printElement = function(path, o, printFn) {
|
|
|
282
290
|
} else if (node.isSelfClosing) {
|
|
283
291
|
selfClose = '/';
|
|
284
292
|
}
|
|
285
|
-
header = `%${tag}${classes}${id}
|
|
293
|
+
header = [`%${tag}${classes}${id}`, attrs, selfClose];
|
|
286
294
|
// Children
|
|
287
295
|
children = (ref = node.children) != null ? ref : [];
|
|
288
296
|
if (children.length === 0) {
|
|
289
297
|
return header;
|
|
290
298
|
}
|
|
291
299
|
// Inline child
|
|
292
|
-
if (isSingleInlineChild(children)
|
|
300
|
+
if (isSingleInlineChild(children)) {
|
|
293
301
|
childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
|
|
294
|
-
|
|
302
|
+
// Output (= expr) carries its own separator; Text needs a space
|
|
303
|
+
sep = children[0] instanceof Output ? '' : ' ';
|
|
304
|
+
return [header, sep, childDoc];
|
|
295
305
|
}
|
|
296
306
|
childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
|
|
297
307
|
joined = joinChildrenWithBlanks(children, childDocs, o);
|
|
@@ -300,7 +310,7 @@ printElement = function(path, o, printFn) {
|
|
|
300
310
|
|
|
301
311
|
// ─── ImplicitDiv ───────────────────────────────────────────
|
|
302
312
|
printImplicitDiv = function(path, o, printFn) {
|
|
303
|
-
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, joined, node, ref, ref1, ref2, ref3;
|
|
313
|
+
var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, joined, node, ref, ref1, ref2, ref3, sep;
|
|
304
314
|
node = path.node;
|
|
305
315
|
classes = node.classes.map(function(c) {
|
|
306
316
|
return `.${c}`;
|
|
@@ -308,14 +318,15 @@ printImplicitDiv = function(path, o, printFn) {
|
|
|
308
318
|
id = node.id ? `#${node.id}` : '';
|
|
309
319
|
attrStyle = detectStyle(node, o);
|
|
310
320
|
attrs = formatAttributes(node, attrStyle, o);
|
|
311
|
-
header = `${classes}${id}
|
|
321
|
+
header = [`${classes}${id}`, attrs];
|
|
312
322
|
children = (ref = node.children) != null ? ref : [];
|
|
313
323
|
if (children.length === 0) {
|
|
314
324
|
return header;
|
|
315
325
|
}
|
|
316
326
|
if (isSingleInlineChild(children)) {
|
|
317
327
|
childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
|
|
318
|
-
|
|
328
|
+
sep = children[0] instanceof Output ? '' : ' ';
|
|
329
|
+
return [header, sep, childDoc];
|
|
319
330
|
}
|
|
320
331
|
childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
|
|
321
332
|
joined = joinChildrenWithBlanks(children, childDocs, o);
|
|
@@ -473,6 +484,34 @@ printFilter = function(path, o, _printFn) {
|
|
|
473
484
|
}
|
|
474
485
|
};
|
|
475
486
|
|
|
487
|
+
// ─── CoffeeBlock ───────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
// Build a fenced block doc using `hardline` so Prettier can re-indent the
|
|
490
|
+
// dedented body to the correct nesting level (raw `\n` strings would not).
|
|
491
|
+
printFence = function(fence, content) {
|
|
492
|
+
var k, len, parts, ref;
|
|
493
|
+
if (content) {
|
|
494
|
+
parts = [fence];
|
|
495
|
+
ref = content.split('\n');
|
|
496
|
+
for (k = 0, len = ref.length; k < len; k++) {
|
|
497
|
+
line = ref[k];
|
|
498
|
+
parts.push(hardline, line);
|
|
499
|
+
}
|
|
500
|
+
parts.push(hardline, fence);
|
|
501
|
+
return parts;
|
|
502
|
+
} else {
|
|
503
|
+
return [fence, hardline, fence];
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
printCoffeeBlock = function(path, o, _printFn) {
|
|
508
|
+
return printFence('---', path.node.content);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
printCoffeeYield = function(path, o, _printFn) {
|
|
512
|
+
return printFence('===', path.node.content);
|
|
513
|
+
};
|
|
514
|
+
|
|
476
515
|
// ─── Doctype ───────────────────────────────────────────────
|
|
477
516
|
printDoctype = function(path, o) {
|
|
478
517
|
var node;
|
|
@@ -501,12 +540,14 @@ detectStyle = function(node, o) {
|
|
|
501
540
|
};
|
|
502
541
|
|
|
503
542
|
formatAttributes = function(node, style, o) {
|
|
504
|
-
var attr, attrs, bareParts, expr, k, len, name, parts, ref, ref1, ref2, val;
|
|
543
|
+
var attr, attrs, bareParts, close, expr, forceBreak, k, len, name, open, parts, ref, ref1, ref2, sep, val;
|
|
505
544
|
attrs = (ref = node.attributes) != null ? ref : [];
|
|
506
545
|
if (!(attrs.length > 0)) {
|
|
507
546
|
return '';
|
|
508
547
|
}
|
|
509
|
-
// Bare style: HTML-style key="val" key2={expr} — space-separated
|
|
548
|
+
// Bare style: HTML-style key="val" key2={expr} — space-separated.
|
|
549
|
+
// Never folded: an indented continuation line would parse as a child
|
|
550
|
+
// element, silently changing the tree. Bare attributes stay inline.
|
|
510
551
|
if (style === 'bare') {
|
|
511
552
|
bareParts = (function() {
|
|
512
553
|
var k, len, ref1, ref2, results;
|
|
@@ -528,38 +569,44 @@ formatAttributes = function(node, style, o) {
|
|
|
528
569
|
return results;
|
|
529
570
|
})();
|
|
530
571
|
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 : '';
|
|
572
|
+
}
|
|
573
|
+
parts = [];
|
|
574
|
+
for (k = 0, len = attrs.length; k < len; k++) {
|
|
575
|
+
attr = attrs[k];
|
|
576
|
+
if (attr.spread) {
|
|
577
|
+
expr = (ref1 = attr.expression.source) != null ? ref1 : '';
|
|
545
578
|
if (o.coffeeScriptFormat) {
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
if (attr.shorthand) {
|
|
549
|
-
parts.push(name);
|
|
550
|
-
} else {
|
|
551
|
-
parts.push(`${name}: ${val}`);
|
|
579
|
+
expr = formatCoffeeScript(expr, o);
|
|
552
580
|
}
|
|
581
|
+
parts.push(style === 'parens' ? `...${expr}` : `{${expr}...}`);
|
|
582
|
+
continue;
|
|
553
583
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
584
|
+
name = attr.name;
|
|
585
|
+
val = (ref2 = attr.value.source) != null ? ref2 : '';
|
|
586
|
+
if (o.coffeeScriptFormat) {
|
|
587
|
+
val = formatCoffeeScript(val, o);
|
|
588
|
+
}
|
|
589
|
+
if (attr.shorthand) {
|
|
590
|
+
parts.push(name);
|
|
591
|
+
} else {
|
|
592
|
+
parts.push(`${name}: ${val}`);
|
|
561
593
|
}
|
|
562
594
|
}
|
|
595
|
+
// Count-based forcing: a positive threshold breaks even when the
|
|
596
|
+
// attributes would otherwise fit within printWidth.
|
|
597
|
+
forceBreak = o.attributeMultilineThreshold > 0 && attrs.length >= o.attributeMultilineThreshold;
|
|
598
|
+
sep = forceBreak ? hardline : line;
|
|
599
|
+
if (style === 'braces') {
|
|
600
|
+
open = '{';
|
|
601
|
+
close = '}';
|
|
602
|
+
} else if (style === 'parens') {
|
|
603
|
+
open = '(';
|
|
604
|
+
close = ')';
|
|
605
|
+
} else {
|
|
606
|
+
// Unknown style — inline fallback, no folding.
|
|
607
|
+
return parts.join(', ');
|
|
608
|
+
}
|
|
609
|
+
return group([open, indent([sep, join([',', sep], parts)]), sep, close]);
|
|
563
610
|
};
|
|
564
611
|
|
|
565
612
|
// ─── CoffeeScript Embed (for Prettier embed) ───────────────
|
package/docs/grammar.md
CHANGED
|
@@ -292,6 +292,72 @@ 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
|
+
A fenced raw CoffeeScript area. The body between the two `---` lines is
|
|
306
|
+
taken verbatim (dedented by its minimum indentation) and compiled to
|
|
307
|
+
executable JavaScript via the CoffeeScript bridge, then hoisted to module
|
|
308
|
+
scope — outside any component wrapper. This is how imports, helper
|
|
309
|
+
functions and one-time setup code are expressed without fighting HAML's
|
|
310
|
+
indentation tree.
|
|
311
|
+
|
|
312
|
+
```haml
|
|
313
|
+
---
|
|
314
|
+
import { blah } from 'ok'
|
|
315
|
+
answer = 42
|
|
316
|
+
---
|
|
317
|
+
%p= answer
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Semantics:
|
|
321
|
+
|
|
322
|
+
- **Escapes indentation**: body lines are dedented to column 0, so a block
|
|
323
|
+
nested under an element still writes CoffeeScript flush-left.
|
|
324
|
+
- **Module scope**: the compiled statements run once at module load, before
|
|
325
|
+
any JSX. `import`/`export` therefore stay valid (they are not wrapped in
|
|
326
|
+
a component function).
|
|
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 being hoisted to module scope 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
|
+
|
|
295
361
|
## Raw Text / Passthrough
|
|
296
362
|
|
|
297
363
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coffeehaml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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/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
|
},
|
|
@@ -91,4 +91,4 @@
|
|
|
91
91
|
"dependencies": {
|
|
92
92
|
"source-map": "^0.7.4"
|
|
93
93
|
}
|
|
94
|
-
}
|
|
94
|
+
}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
// coffeescript-formatter.coffee — CoffeeScript token-based expression formatter
|
|
3
|
-
var DEFAULT_OPTS, EXPR_STARTERS, FOR_KEYWORDS, KEYWORD_SPACE_AFTER, NO_SPACE_AFTER, NO_SPACE_BEFORE, SPACE_AFTER, SPACE_BOTH, _require, getTokenText, hasImplicitTokens, hasMethodChain, isUnaryOp, lineWrap, loadCoffeeScript, needsSpaceBetween, normalizeSpacing, wrapMethodChain;
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createRequire
|
|
7
|
-
} from 'module';
|
|
8
|
-
|
|
9
|
-
_require = createRequire(import.meta.url);
|
|
10
|
-
|
|
11
|
-
loadCoffeeScript = function() {
|
|
12
|
-
try {
|
|
13
|
-
return _require('coffeescript');
|
|
14
|
-
} catch (error) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// ─── Token types ──────────────────────────────────────────
|
|
20
|
-
// CSToken: { type, value, range, generated?, originalText? }
|
|
21
|
-
|
|
22
|
-
// ─── Spacing categories ───────────────────────────────────
|
|
23
|
-
NO_SPACE_BEFORE = new Set([',', ':', ';', '.', '?.', '::', ')', ']', '}', 'CALL_END', 'PARAM_END', 'INDEX_END', 'POST_IF', 'POST_FOR', 'POST_WHILE', 'TERMINATOR']);
|
|
24
|
-
|
|
25
|
-
NO_SPACE_AFTER = new Set(['.', '?.', '::', '...', '..', '(', '[', '{', 'CALL_START', 'PARAM_START', 'INDEX_START', 'STRING_START', 'INTERPOLATION_START', 'INDENT', '@']);
|
|
26
|
-
|
|
27
|
-
SPACE_AFTER = new Set([',', ':']);
|
|
28
|
-
|
|
29
|
-
SPACE_BOTH = new Set(['=', '?=', '+', '-', 'MATH', '**', 'SHIFT', '&', '|', '^', '&&', '||', '?', '=>', '->', 'COMPARE', 'COMPOUND_ASSIGN', 'RELATION', 'FOR', 'ELSE', 'IF', 'WHILE']);
|
|
30
|
-
|
|
31
|
-
KEYWORD_SPACE_AFTER = new Set(['RETURN', 'THROW', 'NEW', 'TYPEOF', 'INSTANCEOF', 'DELETE', 'CLASS', 'EXTENDS', 'DEFAULT', 'IMPORT', 'EXPORT', 'AWAIT', 'YIELD', 'BREAK', 'CONTINUE', 'SWITCH']);
|
|
32
|
-
|
|
33
|
-
FOR_KEYWORDS = new Set(['FORIN', 'FOROF', 'WHEN', 'BY']);
|
|
34
|
-
|
|
35
|
-
EXPR_STARTERS = new Set(['(', '[', '{', ',', ':', '=', '?=', '+', '-', 'MATH', '**', 'SHIFT', '&', '|', '^', '&&', '||', '?', '=>', '->', 'COMPARE', 'COMPOUND_ASSIGN', 'RETURN', 'INDENT', 'OUTDENT', 'TERMINATOR', 'FOR', 'FORIN', 'FOROF', 'WHILE', 'IF', 'ELSE', 'THROW', 'NEW', 'TYPEOF', 'INSTANCEOF', 'DELETE', 'EXTENDS', 'DEFAULT', 'CALL_START', 'PARAM_START', 'INDEX_START', 'UNARY']);
|
|
36
|
-
|
|
37
|
-
DEFAULT_OPTS = {
|
|
38
|
-
printWidth: 80,
|
|
39
|
-
methodChainAlign: true,
|
|
40
|
-
enabled: true
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// ─── Public API ───────────────────────────────────────────
|
|
44
|
-
export var formatCoffeeScript = function(source, options = {}) {
|
|
45
|
-
var cs, normalized, opts, rawTokens, tokens;
|
|
46
|
-
opts = {...DEFAULT_OPTS, ...options};
|
|
47
|
-
if (!opts.enabled) {
|
|
48
|
-
return source;
|
|
49
|
-
}
|
|
50
|
-
if (!(source && source.trim() !== '')) {
|
|
51
|
-
return source;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
cs = loadCoffeeScript();
|
|
55
|
-
if (!(cs && typeof cs.tokens === 'function')) {
|
|
56
|
-
return source;
|
|
57
|
-
}
|
|
58
|
-
rawTokens = cs.tokens(source);
|
|
59
|
-
if (!(rawTokens && rawTokens.length > 0)) {
|
|
60
|
-
return source;
|
|
61
|
-
}
|
|
62
|
-
tokens = rawTokens.map(function(t) {
|
|
63
|
-
var ref, ref1, ref2, ref3;
|
|
64
|
-
return {
|
|
65
|
-
type: t[0],
|
|
66
|
-
value: t[1],
|
|
67
|
-
range: (ref = (ref1 = t[2]) != null ? ref1.range : void 0) != null ? ref : [0, 0],
|
|
68
|
-
generated: !!((ref2 = t[2]) != null ? ref2.generated : void 0),
|
|
69
|
-
originalText: ((ref3 = t[2]) != null ? ref3.range : void 0) && t[2].range[0] < t[2].range[1] ? source.slice(t[2].range[0], t[2].range[1]) : t[1]
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
if (hasImplicitTokens(tokens)) {
|
|
73
|
-
return source;
|
|
74
|
-
}
|
|
75
|
-
normalized = normalizeSpacing(tokens, source);
|
|
76
|
-
if (normalized.length > opts.printWidth) {
|
|
77
|
-
return lineWrap(normalized, opts, source);
|
|
78
|
-
}
|
|
79
|
-
return normalized;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
return source;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// ─── Block formatting ─────────────────────────────────────
|
|
86
|
-
export var formatCoffeeScriptBlock = function(source, options = {}) {
|
|
87
|
-
var formatted, lines, opts;
|
|
88
|
-
opts = {...DEFAULT_OPTS, ...options};
|
|
89
|
-
if (!opts.enabled) {
|
|
90
|
-
return source;
|
|
91
|
-
}
|
|
92
|
-
if (!(source && source.trim() !== '')) {
|
|
93
|
-
return source;
|
|
94
|
-
}
|
|
95
|
-
lines = source.split('\n');
|
|
96
|
-
formatted = lines.map(function(line) {
|
|
97
|
-
var formattedExpr, leading, ref, ref1, trimmed;
|
|
98
|
-
trimmed = line.trim();
|
|
99
|
-
if (!trimmed) {
|
|
100
|
-
return '';
|
|
101
|
-
}
|
|
102
|
-
leading = (ref = (ref1 = line.match(/^(\s*)/)) != null ? ref1[1] : void 0) != null ? ref : '';
|
|
103
|
-
try {
|
|
104
|
-
formattedExpr = formatCoffeeScript(trimmed, options);
|
|
105
|
-
return leading + formattedExpr;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return line;
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
return formatted.join('\n');
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// ─── Implicit token detection ─────────────────────────────
|
|
114
|
-
hasImplicitTokens = function(tokens) {
|
|
115
|
-
var j, len, ref, t;
|
|
116
|
-
for (j = 0, len = tokens.length; j < len; j++) {
|
|
117
|
-
t = tokens[j];
|
|
118
|
-
if ((ref = t.type) === 'INDENT' || ref === 'OUTDENT' || ref === 'TERMINATOR') {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (t.generated) {
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
if (t.range && t.range[0] >= t.range[1]) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return false;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// ─── Spacing normalization ────────────────────────────────
|
|
132
|
-
normalizeSpacing = function(tokens, originalSource = null) {
|
|
133
|
-
var afterPrevIsExprStart, cur, j, len, parts, prevVisible, prevWasAtExprStart, ref, ref1, ref2, text, trimmed;
|
|
134
|
-
parts = [];
|
|
135
|
-
prevVisible = null;
|
|
136
|
-
prevWasAtExprStart = true;
|
|
137
|
-
afterPrevIsExprStart = true;
|
|
138
|
-
for (j = 0, len = tokens.length; j < len; j++) {
|
|
139
|
-
cur = tokens[j];
|
|
140
|
-
// Skip implicit CALL/PARAM tokens
|
|
141
|
-
if ((ref = cur.type) === 'CALL_START' || ref === 'CALL_END') {
|
|
142
|
-
if (cur.generated) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (cur.range && cur.range[0] >= cur.range[1]) {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if ((ref1 = cur.type) === 'PARAM_START' || ref1 === 'PARAM_END') {
|
|
150
|
-
if (cur.generated) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (cur.range && cur.range[0] >= cur.range[1]) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Handle INDENT — represents inline then/when/else/etc from CoffeeScript
|
|
158
|
-
if (cur.type === 'INDENT') {
|
|
159
|
-
text = getTokenText(cur, originalSource);
|
|
160
|
-
trimmed = text != null ? text.trim() : void 0;
|
|
161
|
-
if (trimmed && trimmed !== '') {
|
|
162
|
-
parts.push(' ' + trimmed + ' ');
|
|
163
|
-
prevWasAtExprStart = false;
|
|
164
|
-
afterPrevIsExprStart = true;
|
|
165
|
-
prevVisible = cur;
|
|
166
|
-
}
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
if (cur.type === 'OUTDENT') {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
// Skip TERMINATOR
|
|
173
|
-
if (cur.type === 'TERMINATOR') {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
// Space before this token
|
|
177
|
-
if (prevVisible && ((ref2 = prevVisible.type) !== 'INDENT' && ref2 !== 'OUTDENT')) {
|
|
178
|
-
if (needsSpaceBetween(prevVisible, cur, prevWasAtExprStart)) {
|
|
179
|
-
parts.push(' ');
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
parts.push(getTokenText(cur, originalSource));
|
|
183
|
-
prevWasAtExprStart = afterPrevIsExprStart;
|
|
184
|
-
afterPrevIsExprStart = EXPR_STARTERS.has(cur.type);
|
|
185
|
-
prevVisible = cur;
|
|
186
|
-
}
|
|
187
|
-
return parts.join('');
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
getTokenText = function(token, originalSource = null) {
|
|
191
|
-
if (token.generated) {
|
|
192
|
-
return token.value;
|
|
193
|
-
}
|
|
194
|
-
if (!(token.range && token.range[0] < token.range[1])) {
|
|
195
|
-
return token.value;
|
|
196
|
-
}
|
|
197
|
-
if (originalSource) {
|
|
198
|
-
return originalSource.slice(token.range[0], token.range[1]);
|
|
199
|
-
}
|
|
200
|
-
return token.value;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
needsSpaceBetween = function(prev, cur, prevAtExprStart) {
|
|
204
|
-
var text;
|
|
205
|
-
if (NO_SPACE_AFTER.has(prev.type)) {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
if (NO_SPACE_BEFORE.has(cur.type)) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
if (prev.type === '@') {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
if (SPACE_AFTER.has(prev.type)) {
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
if (FOR_KEYWORDS.has(prev.type) || FOR_KEYWORDS.has(cur.type)) {
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
if (KEYWORD_SPACE_AFTER.has(prev.type)) {
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
if (prev.type === 'UNARY') {
|
|
224
|
-
text = prev.originalText || prev.value;
|
|
225
|
-
if (text === '!' || text === '!!') {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
if (SPACE_BOTH.has(prev.type) || SPACE_BOTH.has(cur.type)) {
|
|
231
|
-
if (isUnaryOp(prev, prevAtExprStart)) {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
if (prev.type === 'IDENTIFIER' && cur.type === 'IDENTIFIER') {
|
|
237
|
-
return true;
|
|
238
|
-
}
|
|
239
|
-
return false;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
isUnaryOp = function(prev, prevAtExprStart) {
|
|
243
|
-
var ref;
|
|
244
|
-
if ((ref = prev.type) !== '+' && ref !== '-') {
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
if (prevAtExprStart) {
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
if (prev.range && prev.range[0] === 0) {
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
return false;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// ─── Line wrapping ────────────────────────────────────────
|
|
257
|
-
lineWrap = function(source, opts, fallback) {
|
|
258
|
-
if (opts.methodChainAlign && hasMethodChain(source)) {
|
|
259
|
-
return wrapMethodChain(source, opts);
|
|
260
|
-
}
|
|
261
|
-
return fallback;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
hasMethodChain = function(source) {
|
|
265
|
-
return (source.match(/\.[a-zA-Z_$]/g) || []).length >= 2;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
wrapMethodChain = function(source, _opts) {
|
|
269
|
-
var ch, chars, current, depth, i, indent, result;
|
|
270
|
-
indent = ' ';
|
|
271
|
-
result = [];
|
|
272
|
-
chars = [...source];
|
|
273
|
-
depth = 0;
|
|
274
|
-
current = '';
|
|
275
|
-
i = 0;
|
|
276
|
-
while (i < chars.length) {
|
|
277
|
-
ch = chars[i];
|
|
278
|
-
if (ch === '(' || ch === '[' || ch === '{') {
|
|
279
|
-
depth++;
|
|
280
|
-
}
|
|
281
|
-
if (ch === ')' || ch === ']' || ch === '}') {
|
|
282
|
-
depth--;
|
|
283
|
-
}
|
|
284
|
-
if (ch === '.' && depth === 0 && i > 0 && chars[i - 1] !== '?' && current.trim().length > 0) {
|
|
285
|
-
if (result.length === 0) {
|
|
286
|
-
result.push(current.trimEnd());
|
|
287
|
-
} else {
|
|
288
|
-
result.push(indent + current.trimEnd());
|
|
289
|
-
}
|
|
290
|
-
current = ch;
|
|
291
|
-
i++;
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
current += ch;
|
|
295
|
-
i++;
|
|
296
|
-
}
|
|
297
|
-
if (current.trim().length > 0) {
|
|
298
|
-
if (result.length === 0) {
|
|
299
|
-
result.push(current.trim());
|
|
300
|
-
} else {
|
|
301
|
-
result.push(indent + current.trim());
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (result.length > 1) {
|
|
305
|
-
return result.join('\n');
|
|
306
|
-
} else {
|
|
307
|
-
return source;
|
|
308
|
-
}
|
|
309
|
-
};
|