coffeehaml 0.7.0 → 0.7.2
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 +21 -5
- package/dist/ast.js +334 -125
- package/dist/cli.js +363 -291
- package/dist/compiler.js +126 -89
- package/dist/emitter.js +771 -736
- package/dist/errors.js +5 -2
- package/dist/expressions.js +211 -154
- package/dist/index.js +22 -6
- package/dist/lexer.js +536 -340
- package/dist/parser.js +533 -438
- package/dist/prettier/coffeescript-formatter.js +292 -392
- package/dist/prettier/index.js +96 -60
- package/dist/prettier/options.js +179 -147
- package/dist/prettier/printer.js +549 -642
- package/dist/types.js +122 -12
- package/dist/vite-plugin.js +78 -63
- package/package.json +4 -9
- package/dist/ast.d.ts +0 -123
- package/dist/ast.d.ts.map +0 -1
- package/dist/ast.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/compiler.d.ts +0 -10
- package/dist/compiler.d.ts.map +0 -1
- package/dist/compiler.js.map +0 -1
- package/dist/emitter.d.ts +0 -4
- package/dist/emitter.d.ts.map +0 -1
- package/dist/emitter.js.map +0 -1
- package/dist/errors.d.ts +0 -3
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/expressions.d.ts +0 -22
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lexer.d.ts +0 -30
- package/dist/lexer.d.ts.map +0 -1
- package/dist/lexer.js.map +0 -1
- package/dist/parser.d.ts +0 -10
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/prettier/coffeescript-formatter.d.ts +0 -31
- package/dist/prettier/coffeescript-formatter.d.ts.map +0 -1
- package/dist/prettier/coffeescript-formatter.js.map +0 -1
- package/dist/prettier/index.d.ts +0 -8
- package/dist/prettier/index.d.ts.map +0 -1
- package/dist/prettier/index.js.map +0 -1
- package/dist/prettier/options.d.ts +0 -10
- package/dist/prettier/options.d.ts.map +0 -1
- package/dist/prettier/options.js.map +0 -1
- package/dist/prettier/printer.d.ts +0 -45
- package/dist/prettier/printer.d.ts.map +0 -1
- package/dist/prettier/printer.js.map +0 -1
- package/dist/types.d.ts +0 -76
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/vite-plugin.d.ts +0 -7
- package/dist/vite-plugin.d.ts.map +0 -1
- package/dist/vite-plugin.js.map +0 -1
|
@@ -1,409 +1,309 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
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
|
+
|
|
25
22
|
// ─── Spacing categories ───────────────────────────────────
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'POST_IF', 'POST_FOR', 'POST_WHILE',
|
|
45
|
-
'TERMINATOR',
|
|
46
|
-
// NOTE: FORIN, FOROF removed — they need space before them
|
|
47
|
-
]);
|
|
48
|
-
/** Tokens that should NOT have a space after them. */
|
|
49
|
-
const NO_SPACE_AFTER = new Set([
|
|
50
|
-
'.', '?.', '::', '...', '..',
|
|
51
|
-
'(', '[', '{',
|
|
52
|
-
'CALL_START', 'PARAM_START', 'INDEX_START',
|
|
53
|
-
'STRING_START', 'INTERPOLATION_START',
|
|
54
|
-
'INDENT', '@',
|
|
55
|
-
// UNARY removed — we handle it in needsSpaceBetween based on original text
|
|
56
|
-
]);
|
|
57
|
-
/** Tokens that ALWAYS get a space after them. */
|
|
58
|
-
const SPACE_AFTER = new Set([
|
|
59
|
-
',', // comma in args/arrays
|
|
60
|
-
':', // colon in object literals
|
|
61
|
-
]);
|
|
62
|
-
/** Binary operators that get spaces on both sides.
|
|
63
|
-
* Uses CoffeeScript token TYPES (MATH, SHIFT, COMPARE, etc.). */
|
|
64
|
-
const SPACE_BOTH = new Set([
|
|
65
|
-
'=', '?=',
|
|
66
|
-
'+', '-', // simple arithmetic (unary handled separately)
|
|
67
|
-
'MATH', // * / %
|
|
68
|
-
'**', // power
|
|
69
|
-
'SHIFT', // << >> >>>
|
|
70
|
-
'&', '|', '^', // bitwise
|
|
71
|
-
'&&', '||', '?', // logical
|
|
72
|
-
'=>', '->', // arrows
|
|
73
|
-
'COMPARE', // == != < > <= >= is isnt
|
|
74
|
-
'COMPOUND_ASSIGN', // += -= *= /= etc.
|
|
75
|
-
'RELATION', // in, of (when not FORIN/FOROF)
|
|
76
|
-
'FOR', // for (comprehension keyword)
|
|
77
|
-
'ELSE', 'IF', 'WHILE', // control flow keywords
|
|
78
|
-
]);
|
|
79
|
-
/** Keywords that should have a space after them (but not before). */
|
|
80
|
-
const KEYWORD_SPACE_AFTER = new Set([
|
|
81
|
-
'RETURN', 'THROW', 'NEW', 'TYPEOF', 'INSTANCEOF', 'DELETE',
|
|
82
|
-
'CLASS', 'EXTENDS', 'DEFAULT', 'IMPORT', 'EXPORT', 'AWAIT',
|
|
83
|
-
'YIELD', 'BREAK', 'CONTINUE', 'SWITCH',
|
|
84
|
-
]);
|
|
85
|
-
/** For-comprehension internal keywords get space on both sides. */
|
|
86
|
-
const FOR_KEYWORDS = new Set(['FORIN', 'FOROF', 'WHEN', 'BY']);
|
|
87
|
-
/** Tokens that start a new expression — after these, - and + are unary. */
|
|
88
|
-
const EXPR_STARTERS = new Set([
|
|
89
|
-
'(', '[', '{', ',', ':',
|
|
90
|
-
'=', '?=',
|
|
91
|
-
'+', '-', 'MATH', '**', 'SHIFT',
|
|
92
|
-
'&', '|', '^',
|
|
93
|
-
'&&', '||', '?',
|
|
94
|
-
'=>', '->',
|
|
95
|
-
'COMPARE', 'COMPOUND_ASSIGN',
|
|
96
|
-
'RETURN', 'INDENT', 'OUTDENT', 'TERMINATOR', 'FOR', 'FORIN', 'FOROF',
|
|
97
|
-
'WHILE', 'IF', 'ELSE', 'THROW', 'NEW', 'TYPEOF', 'INSTANCEOF',
|
|
98
|
-
'DELETE', 'EXTENDS', 'DEFAULT',
|
|
99
|
-
'CALL_START', 'PARAM_START', 'INDEX_START',
|
|
100
|
-
'UNARY',
|
|
101
|
-
]);
|
|
102
|
-
const DEFAULT_OPTS = {
|
|
103
|
-
printWidth: 80,
|
|
104
|
-
methodChainAlign: true,
|
|
105
|
-
enabled: true,
|
|
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
|
|
106
41
|
};
|
|
42
|
+
|
|
107
43
|
// ─── Public API ───────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 : '';
|
|
118
103
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Map them to our CSToken interface for easier access
|
|
124
|
-
const rawTokens = cs.tokens(source);
|
|
125
|
-
if (!rawTokens || rawTokens.length === 0)
|
|
126
|
-
return source;
|
|
127
|
-
const tokens = rawTokens.map((t) => ({
|
|
128
|
-
type: t[0],
|
|
129
|
-
value: t[1],
|
|
130
|
-
range: t[2]?.range ?? [0, 0],
|
|
131
|
-
generated: !!t[2]?.generated,
|
|
132
|
-
originalText: (t[2]?.range && t[2].range[0] < t[2].range[1])
|
|
133
|
-
? source.slice(t[2].range[0], t[2].range[1])
|
|
134
|
-
: t[1],
|
|
135
|
-
}));
|
|
136
|
-
// Check for implicit/synthetic tokens that would make formatting unsafe.
|
|
137
|
-
// If the expression has zero-width tokens (implicit calls, generated params),
|
|
138
|
-
// fall back to original source to avoid changing semantics.
|
|
139
|
-
if (hasImplicitTokens(tokens)) {
|
|
140
|
-
return source;
|
|
141
|
-
}
|
|
142
|
-
// Phase 2: Normalize spacing (pass original source for text extraction)
|
|
143
|
-
const normalized = normalizeSpacing(tokens, source);
|
|
144
|
-
// Phase 3: Line-wrap if needed
|
|
145
|
-
if (normalized.length > opts.printWidth) {
|
|
146
|
-
return lineWrap(normalized, opts, source);
|
|
147
|
-
}
|
|
148
|
-
return normalized;
|
|
104
|
+
formattedExpr = formatCoffeeScript(trimmed, options);
|
|
105
|
+
return leading + formattedExpr;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return line;
|
|
149
108
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
153
120
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* Check if the token stream contains implicit/synthetic tokens
|
|
157
|
-
* (zero-width CALL/PARAM tokens) that indicate CoffeeScript
|
|
158
|
-
* implicit function calls. Formatting these would change semantics.
|
|
159
|
-
*/
|
|
160
|
-
function hasImplicitTokens(tokens) {
|
|
161
|
-
for (const t of tokens) {
|
|
162
|
-
if (t.type === 'INDENT' || t.type === 'OUTDENT' || t.type === 'TERMINATOR')
|
|
163
|
-
continue;
|
|
164
|
-
if (t.generated)
|
|
165
|
-
return true;
|
|
166
|
-
if (t.range && t.range[0] >= t.range[1])
|
|
167
|
-
return true;
|
|
168
|
-
// Check for CALL_START/CALL_END that aren't explicit parens:
|
|
169
|
-
// If they're at same position as another token, they're implicit
|
|
121
|
+
if (t.generated) {
|
|
122
|
+
return true;
|
|
170
123
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (cur.type === 'INDENT' || cur.type === 'OUTDENT') {
|
|
213
|
-
// Don't update prevVisible — keep the last real token
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// TERMINATOR: skip entirely
|
|
217
|
-
if (cur.type === 'TERMINATOR') {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
// Determine space before this token
|
|
221
|
-
if (prevVisible && prevVisible.type !== 'INDENT' && prevVisible.type !== 'OUTDENT') {
|
|
222
|
-
if (needsSpaceBetween(prevVisible, cur, prevWasAtExprStart)) {
|
|
223
|
-
parts.push(' ');
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// Output token text: prefer original source, fall back to token value
|
|
227
|
-
parts.push(getTokenText(cur, originalSource));
|
|
228
|
-
// Update tracking for next iteration
|
|
229
|
-
prevWasAtExprStart = afterPrevIsExprStart;
|
|
230
|
-
afterPrevIsExprStart = EXPR_STARTERS.has(cur.type);
|
|
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;
|
|
231
165
|
prevVisible = cur;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
232
168
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Get the original source text for a token.
|
|
237
|
-
* CoffeeScript may rewrite token values (is→==, and→&&),
|
|
238
|
-
* so we use the original source range when available.
|
|
239
|
-
*/
|
|
240
|
-
/**
|
|
241
|
-
* Get the output text for a token.
|
|
242
|
-
* Prefers original source text from the token's range,
|
|
243
|
-
* but falls back to token value for zero-width ranges,
|
|
244
|
-
* generated tokens, or when originalSource is unavailable.
|
|
245
|
-
*/
|
|
246
|
-
function getTokenText(token, originalSource) {
|
|
247
|
-
// For generated tokens, use the token value (e.g., '(' for CALL_START)
|
|
248
|
-
if (token.generated)
|
|
249
|
-
return token.value;
|
|
250
|
-
// For zero-width ranges, use token value
|
|
251
|
-
if (!token.range || token.range[0] >= token.range[1])
|
|
252
|
-
return token.value;
|
|
253
|
-
// Use original source text
|
|
254
|
-
if (originalSource) {
|
|
255
|
-
return originalSource.slice(token.range[0], token.range[1]);
|
|
169
|
+
if (cur.type === 'OUTDENT') {
|
|
170
|
+
continue;
|
|
256
171
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
* Determine if a space is needed between two visible tokens.
|
|
261
|
-
* @param prevAtExprStart true if the position BEFORE prev was at
|
|
262
|
-
* expression start — used to detect unary + / - operators.
|
|
263
|
-
*/
|
|
264
|
-
function needsSpaceBetween(prev, cur, prevAtExprStart) {
|
|
265
|
-
// No space after these token types (e.g., ., (, [, @)
|
|
266
|
-
if (NO_SPACE_AFTER.has(prev.type))
|
|
267
|
-
return false;
|
|
268
|
-
// No space before these token types (e.g., , : ; . ) ] })
|
|
269
|
-
if (NO_SPACE_BEFORE.has(cur.type))
|
|
270
|
-
return false;
|
|
271
|
-
// @property access: no space after @
|
|
272
|
-
if (prev.type === '@')
|
|
273
|
-
return false;
|
|
274
|
-
// Tokens that always get a space after (comma, colon)
|
|
275
|
-
if (SPACE_AFTER.has(prev.type))
|
|
276
|
-
return true;
|
|
277
|
-
// For-comprehension keywords: space around FORIN, FOROF, WHEN, BY
|
|
278
|
-
if (FOR_KEYWORDS.has(prev.type) || FOR_KEYWORDS.has(cur.type))
|
|
279
|
-
return true;
|
|
280
|
-
// Keywords that take space after them (FOR, RETURN, THROW, etc.)
|
|
281
|
-
if (KEYWORD_SPACE_AFTER.has(prev.type))
|
|
282
|
-
return true;
|
|
283
|
-
// UNARY (! or not): no space after if the original text is !
|
|
284
|
-
// (as opposed to the keyword 'not')
|
|
285
|
-
if (prev.type === 'UNARY') {
|
|
286
|
-
const text = prev.originalText || prev.value;
|
|
287
|
-
if (text === '!' || text === '!!')
|
|
288
|
-
return false;
|
|
289
|
-
// For 'not' keyword: space after is desired
|
|
290
|
-
return true;
|
|
172
|
+
// Skip TERMINATOR
|
|
173
|
+
if (cur.type === 'TERMINATOR') {
|
|
174
|
+
continue;
|
|
291
175
|
}
|
|
292
|
-
//
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return true;
|
|
176
|
+
// Space before this token
|
|
177
|
+
if (prevVisible && ((ref2 = prevVisible.type) !== 'INDENT' && ref2 !== 'OUTDENT')) {
|
|
178
|
+
if (needsSpaceBetween(prevVisible, cur, prevWasAtExprStart)) {
|
|
179
|
+
parts.push(' ');
|
|
180
|
+
}
|
|
298
181
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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)) {
|
|
303
209
|
return false;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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 !== '-') {
|
|
318
245
|
return false;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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++;
|
|
330
280
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
function hasMethodChain(source) {
|
|
335
|
-
// Detect .method chains (not just a single .property)
|
|
336
|
-
const dotCount = (source.match(/\.[a-zA-Z_$]/g) || []).length;
|
|
337
|
-
return dotCount >= 2;
|
|
338
|
-
}
|
|
339
|
-
function wrapMethodChain(source, _opts) {
|
|
340
|
-
// Break .method calls onto separate lines with aligned dots
|
|
341
|
-
// But be careful not to break inside strings, parens, or brackets
|
|
342
|
-
const indent = ' '.repeat(1); // 1 level of indentation for continuation
|
|
343
|
-
const result = [];
|
|
344
|
-
const chars = [...source];
|
|
345
|
-
let depth = 0;
|
|
346
|
-
let current = '';
|
|
347
|
-
let i = 0;
|
|
348
|
-
while (i < chars.length) {
|
|
349
|
-
const ch = chars[i];
|
|
350
|
-
// Track nesting depth
|
|
351
|
-
if (ch === '(' || ch === '[' || ch === '{')
|
|
352
|
-
depth++;
|
|
353
|
-
if (ch === ')' || ch === ']' || ch === '}')
|
|
354
|
-
depth--;
|
|
355
|
-
// Break before . at depth 0
|
|
356
|
-
if (ch === '.' && depth === 0 && i > 0 && chars[i - 1] !== '?' && current.trim().length > 0) {
|
|
357
|
-
if (result.length === 0) {
|
|
358
|
-
// First segment
|
|
359
|
-
result.push(current.trimEnd());
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
result.push(indent + current.trimEnd());
|
|
363
|
-
}
|
|
364
|
-
current = ch;
|
|
365
|
-
i++;
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
current += ch;
|
|
369
|
-
i++;
|
|
281
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
282
|
+
depth--;
|
|
370
283
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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;
|
|
379
293
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return '';
|
|
397
|
-
// Preserve leading whitespace for indentation
|
|
398
|
-
const leading = line.match(/^(\s*)/)?.[1] ?? '';
|
|
399
|
-
try {
|
|
400
|
-
const formattedExpr = formatCoffeeScript(trimmed, options);
|
|
401
|
-
return leading + formattedExpr;
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
return line;
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
return formatted.join('\n');
|
|
408
|
-
}
|
|
409
|
-
//# sourceMappingURL=coffeescript-formatter.js.map
|
|
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
|
+
};
|