coffeehaml 0.7.6 → 0.7.7

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.
@@ -1,6 +1,7 @@
1
1
  // Generated by CoffeeScript 2.7.0
2
- // expressions.coffee — CoffeeScript expression compilation bridge
3
- var _coffeeScriptAvailable, _coffeeScriptUnavailableReason, _require, checkCoffeeScript, collapseWs, loadCoffeeScript, splitTopLevelStatements, stripCoffeeWrapper, stripToConst;
2
+ // expressions.coffee — CoffeeScript expression compilation bridge
3
+ var _coffeeScriptAvailable, _coffeeScriptUnavailableReason, _require, checkCoffeeScript, collapseWs, loadCoffeeScript, objectTarget, splitTopLevelStatements, stripCoffeeWrapper, stripToConst,
4
+ indexOf = [].indexOf;
4
5
 
5
6
  import {
6
7
  createRequire
@@ -179,8 +180,21 @@ export var compileStatement = function(source, location = null) {
179
180
  };
180
181
 
181
182
  // ─── Convert var → const/let ───────────────────────────────
183
+
184
+ // Extract the bound variable name from one entry of an object
185
+ // destructuring pattern — shorthand `x` stays `x`, aliased `key: target`
186
+ // yields `target`.
187
+ objectTarget = function(pair) {
188
+ pair = pair.trim();
189
+ if (pair.includes(':')) {
190
+ return pair.split(':')[1].trim();
191
+ } else {
192
+ return pair;
193
+ }
194
+ };
195
+
182
196
  stripToConst = function(js) {
183
- var assign, assignments, body, idx, j, len, name, stmts, varIdx, varNames;
197
+ var allConsumed, assignments, body, consumed, inner, j, len, m, n, name, names, s, stmts, varIdx, varNames;
184
198
  stmts = splitTopLevelStatements(js);
185
199
  if (stmts.length === 0) {
186
200
  return js.replace(/;\s*$/, '');
@@ -194,28 +208,86 @@ stripToConst = function(js) {
194
208
  varNames = stmts[varIdx].replace(/^\s*var\s+/, '').replace(/;\s*$/, '').split(',').map(function(s) {
195
209
  return s.trim();
196
210
  }).filter(Boolean);
197
- body = stmts.filter(function(s, i) {
198
- return i !== varIdx;
199
- });
211
+ body = (function() {
212
+ var j, len, ref, results;
213
+ ref = stmts.filter(function(s, i) {
214
+ return i !== varIdx;
215
+ });
216
+ results = [];
217
+ for (j = 0, len = ref.length; j < len; j++) {
218
+ s = ref[j];
219
+ results.push(collapseWs(s).replace(/;\s*$/, ''));
220
+ }
221
+ return results;
222
+ })();
200
223
  assignments = [];
201
- for (j = 0, len = varNames.length; j < len; j++) {
202
- name = varNames[j];
203
- idx = body.findIndex(function(s) {
204
- var t;
205
- t = s.trim();
206
- return t.startsWith(`${name} =`) || t.startsWith(`${name}=`);
224
+ consumed = [];
225
+ for (j = 0, len = body.length; j < len; j++) {
226
+ s = body[j];
227
+ m = s.match(/^\[([^\]]*)\]\s*=/);
228
+ if (m != null) {
229
+ // Array destructuring: [a, b] = expr
230
+ names = (function() {
231
+ var k, len1, ref, results;
232
+ ref = m[1].split(',');
233
+ results = [];
234
+ for (k = 0, len1 = ref.length; k < len1; k++) {
235
+ n = ref[k];
236
+ if (n.trim()) {
237
+ results.push(n.trim());
238
+ }
239
+ }
240
+ return results;
241
+ })();
242
+ consumed = consumed.concat(names);
243
+ assignments.push('const ' + s);
244
+ continue;
245
+ }
246
+ m = s.match(/^\(\s*\{([^}]*)\}\s*=.*\)$/);
247
+ if (m != null) {
248
+ // Object destructuring: ({x, y} = expr) -> const {x, y} = expr
249
+ inner = s.replace(/^\s*\(\s*/, '').replace(/\s*\)\s*$/, '');
250
+ names = (function() {
251
+ var k, len1, ref, results;
252
+ ref = m[1].split(',');
253
+ results = [];
254
+ for (k = 0, len1 = ref.length; k < len1; k++) {
255
+ n = ref[k];
256
+ if (n.trim()) {
257
+ results.push(objectTarget(n));
258
+ }
259
+ }
260
+ return results;
261
+ })();
262
+ consumed = consumed.concat(names);
263
+ assignments.push('const ' + inner);
264
+ continue;
265
+ }
266
+ name = varNames.find(function(n) {
267
+ return s.startsWith(`${n} =`) || s.startsWith(`${n}=`);
207
268
  });
208
- if (idx >= 0) {
209
- assign = collapseWs(body[idx]).replace(/;\s*$/, '');
210
- assignments.push('const ' + assign);
269
+ if (name != null) {
270
+ consumed.push(name);
271
+ assignments.push('const ' + s);
211
272
  }
212
273
  }
213
- if (assignments.length > 0) {
274
+ allConsumed = varNames.every(function(n) {
275
+ return indexOf.call(consumed, n) >= 0;
276
+ });
277
+ if (assignments.length > 0 && allConsumed) {
214
278
  return assignments.join('; ');
215
279
  }
216
- return body.map(function(s) {
217
- return collapseWs(s);
218
- }).join(' ');
280
+ return ((function() {
281
+ var k, len1, results;
282
+ results = [];
283
+ for (k = 0, len1 = stmts.length; k < len1; k++) {
284
+ s = stmts[k];
285
+ // Fallback: keep the original var declaration so no variable goes
286
+ // undeclared (e.g. chained assignment `a = b = 1`).
287
+ results.push(collapseWs(s).replace(/;\s*$/, ''));
288
+ }
289
+ return results;
290
+ })()).join('; ');
219
291
  };
220
292
 
221
293
  // Split compiled JS into top-level statements, respecting string literals
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coffeehaml",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
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/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
- };
package/dist/options.js DELETED
@@ -1,232 +0,0 @@
1
- // Generated by CoffeeScript 2.7.0
2
- // options.coffee — CoffeeHaml Prettier formatting options
3
- export var CAT = {
4
- STRUCTURE: 'CoffeeHaml: Structure',
5
- ATTRIBUTES: 'CoffeeHaml: Attributes',
6
- COFFEESCRIPT: 'CoffeeHaml: CoffeeScript',
7
- WHITESPACE: 'CoffeeHaml: Whitespace',
8
- CONTROL_FLOW: 'CoffeeHaml: Control Flow',
9
- COMMENTS: 'CoffeeHaml: Comments'
10
- };
11
-
12
- export var options = {
13
- tagCase: {
14
- category: CAT.STRUCTURE,
15
- type: 'choice',
16
- description: 'Normalize HTML tag casing. Component tags (%MyComponent) never affected.',
17
- choices: [
18
- {
19
- value: 'preserve',
20
- description: 'Leave tag case as written'
21
- },
22
- {
23
- value: 'lowercase',
24
- description: '%DIV to %div (HTML tags only)'
25
- }
26
- ]
27
- },
28
- implicitDivExpansion: {
29
- category: CAT.STRUCTURE,
30
- type: 'boolean',
31
- description: 'Expand .class to %div.class (anti-idiomatic in HAML).'
32
- },
33
- maxChainLength: {
34
- category: CAT.STRUCTURE,
35
- type: 'int',
36
- description: 'Max .class chain length before breaking into indented hierarchy. 0 = always break.'
37
- },
38
- inlineThreshold: {
39
- category: CAT.STRUCTURE,
40
- type: 'int',
41
- description: 'Try single-line if estimated width <= this. -1 = never inline children.'
42
- },
43
- voidElementStyle: {
44
- category: CAT.STRUCTURE,
45
- type: 'choice',
46
- description: 'How void elements (%br, %img, etc.) are formatted.',
47
- choices: [
48
- {
49
- value: 'self-closing',
50
- description: 'Self-closing (no children)'
51
- },
52
- {
53
- value: 'explicit',
54
- description: 'Preserve %br/ syntax, otherwise self-close'
55
- }
56
- ]
57
- },
58
- attributeStyle: {
59
- category: CAT.ATTRIBUTES,
60
- type: 'choice',
61
- description: 'Enforce attribute syntax: {braces}, (parens), bare, or preserve.',
62
- choices: [
63
- {
64
- value: 'preserve',
65
- description: 'Keep as written'
66
- },
67
- {
68
- value: 'braces',
69
- description: 'Convert to {CoffeeScript} style'
70
- },
71
- {
72
- value: 'parens',
73
- description: 'Convert to HTML (parens) style'
74
- },
75
- {
76
- value: 'bare',
77
- description: 'Convert to bare HAML style'
78
- }
79
- ]
80
- },
81
- attributeMultilineThreshold: {
82
- category: CAT.ATTRIBUTES,
83
- type: 'int',
84
- description: 'Break attributes across lines when count >= this. 0 = always multiline.'
85
- },
86
- attributeSort: {
87
- category: CAT.ATTRIBUTES,
88
- type: 'choice',
89
- description: 'Sort attribute keys (spread attributes always last).',
90
- choices: [
91
- {
92
- value: 'none',
93
- description: 'Preserve author order'
94
- },
95
- {
96
- value: 'alphabetical',
97
- description: 'Sort A to Z'
98
- },
99
- {
100
- value: 'idiomatic',
101
- description: 'id first, class second, rest A to Z'
102
- }
103
- ]
104
- },
105
- quoteStyle: {
106
- category: CAT.ATTRIBUTES,
107
- type: 'choice',
108
- description: 'Quote style for bare/HAML attributes.',
109
- choices: [
110
- {
111
- value: 'preserve',
112
- description: 'Keep as written'
113
- },
114
- {
115
- value: 'double',
116
- description: 'Force double quotes'
117
- },
118
- {
119
- value: 'single',
120
- description: 'Force single quotes'
121
- }
122
- ]
123
- },
124
- coffeeScriptFormat: {
125
- category: CAT.COFFEESCRIPT,
126
- type: 'boolean',
127
- description: 'Format CoffeeScript expressions using prettier/plugins/coffeescript.'
128
- },
129
- methodChainAlign: {
130
- category: CAT.COFFEESCRIPT,
131
- type: 'boolean',
132
- description: 'Align .method chains on newlines for = expressions.'
133
- },
134
- blankLineHandling: {
135
- category: CAT.WHITESPACE,
136
- type: 'choice',
137
- description: 'How blank lines between sibling elements are treated.',
138
- choices: [
139
- {
140
- value: 'preserve',
141
- description: 'Keep single; collapse multiples'
142
- },
143
- {
144
- value: 'collapse',
145
- description: 'Remove all blank lines between siblings'
146
- },
147
- {
148
- value: 'respect',
149
- description: 'Keep exactly as written'
150
- }
151
- ]
152
- },
153
- trailingWhitespace: {
154
- category: CAT.WHITESPACE,
155
- type: 'choice',
156
- description: 'Handle trailing whitespace.',
157
- choices: [
158
- {
159
- value: 'remove',
160
- description: 'Strip trailing whitespace'
161
- },
162
- {
163
- value: 'preserve',
164
- description: 'Leave as written'
165
- }
166
- ]
167
- },
168
- continuationStyle: {
169
- category: CAT.CONTROL_FLOW,
170
- type: 'choice',
171
- description: 'How \\ continuations are formatted.',
172
- choices: [
173
- {
174
- value: 'preserve',
175
- description: 'Keep as written'
176
- },
177
- {
178
- value: 'indent',
179
- description: 'Always use indented continuation'
180
- },
181
- {
182
- value: 'backslash',
183
- description: 'Always use \\ continuation'
184
- }
185
- ]
186
- },
187
- controlFlowInline: {
188
- category: CAT.CONTROL_FLOW,
189
- type: 'boolean',
190
- description: 'Allow - if x then .ok one-liners (anti-Prettier by default).'
191
- },
192
- statementMerging: {
193
- category: CAT.CONTROL_FLOW,
194
- type: 'choice',
195
- description: 'Merge consecutive - lines into a single indented block. Only childless statements.',
196
- choices: [
197
- {
198
- value: 'preserve',
199
- description: 'Keep each - line separate'
200
- },
201
- {
202
- value: 'merge',
203
- description: 'Merge consecutive childless statements into one indented block'
204
- }
205
- ]
206
- },
207
- commentFormat: {
208
- category: CAT.COMMENTS,
209
- type: 'boolean',
210
- description: 'Reflow/word-wrap comment text (experimental).'
211
- }
212
- };
213
-
214
- export var defaultOptions = {
215
- tagCase: 'preserve',
216
- implicitDivExpansion: false,
217
- maxChainLength: 4,
218
- inlineThreshold: -1,
219
- voidElementStyle: 'self-closing',
220
- attributeStyle: 'preserve',
221
- attributeMultilineThreshold: 1,
222
- attributeSort: 'none',
223
- quoteStyle: 'preserve',
224
- coffeeScriptFormat: true,
225
- methodChainAlign: true,
226
- blankLineHandling: 'preserve',
227
- trailingWhitespace: 'remove',
228
- continuationStyle: 'indent',
229
- controlFlowInline: false,
230
- statementMerging: 'preserve',
231
- commentFormat: false
232
- };
package/dist/printer.js DELETED
@@ -1,583 +0,0 @@
1
- // Generated by CoffeeScript 2.7.0
2
- // printer.coffee — AST → Prettier Doc IR
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;
5
-
6
- import {
7
- Document,
8
- Element,
9
- ImplicitDiv,
10
- Text,
11
- Output,
12
- ControlFlow,
13
- Comment,
14
- Filter,
15
- Doctype,
16
- Node,
17
- Expression,
18
- Attribute,
19
- SpreadAttribute
20
- } from '../ast.js';
21
-
22
- import {
23
- formatCoffeeScript,
24
- formatCoffeeScriptBlock
25
- } from './coffeescript-formatter.js';
26
-
27
- import {
28
- createRequire
29
- } from 'module';
30
-
31
- _require = createRequire(import.meta.url);
32
-
33
- _prettier = _require('prettier');
34
-
35
- ({group, indent, hardline, join} = _prettier.doc.builders);
36
-
37
- // ─── Format Options ────────────────────────────────────────
38
- // CoffeeHamlFormatOptions shape (runtime):
39
- // tagCase, implicitDivExpansion, maxChainLength, inlineThreshold
40
- // voidElementStyle, attributeStyle, attributeMultilineThreshold
41
- // attributeSort, quoteStyle, coffeeScriptFormat, methodChainAlign
42
- // blankLineHandling, trailingWhitespace, continuationStyle
43
- // controlFlowInline, statementMerging, commentFormat
44
- // tabWidth, useTabs, printWidth, originalText?
45
- defaultOpts = {
46
- tagCase: 'preserve',
47
- implicitDivExpansion: false,
48
- maxChainLength: 4,
49
- inlineThreshold: -1,
50
- voidElementStyle: 'self-closing',
51
- attributeStyle: 'preserve',
52
- attributeMultilineThreshold: 1,
53
- attributeSort: 'none',
54
- quoteStyle: 'preserve',
55
- coffeeScriptFormat: true,
56
- methodChainAlign: true,
57
- blankLineHandling: 'preserve',
58
- trailingWhitespace: 'remove',
59
- continuationStyle: 'indent',
60
- controlFlowInline: false,
61
- statementMerging: 'preserve',
62
- commentFormat: false,
63
- tabWidth: 2,
64
- useTabs: false,
65
- printWidth: 80,
66
- originalText: void 0
67
- };
68
-
69
- opts = function(p) {
70
- return p.__opts || defaultOpts;
71
- };
72
-
73
- MergedStatements = (function() {
74
- // ─── MergedStatements wrapper ──────────────────────────────
75
- class MergedStatements {
76
- constructor(statements) {
77
- this.statements = statements;
78
- }
79
-
80
- };
81
-
82
- MergedStatements.prototype.kind = 'MergedStatements';
83
-
84
- MergedStatements.prototype.statements = [];
85
-
86
- return MergedStatements;
87
-
88
- }).call(this);
89
-
90
- // ─── Constants ─────────────────────────────────────────────
91
- VOID_ELEMENTS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
92
-
93
- isVoidElement = function(tag) {
94
- return VOID_ELEMENTS.has(tag.toLowerCase());
95
- };
96
-
97
- isSingleInlineChild = function(children) {
98
- return children.length === 1 && (children[0] instanceof Text || children[0] instanceof Output);
99
- };
100
-
101
- // ─── Blank-line-aware join ─────────────────────────────────
102
- joinChildrenWithBlanks = function(nodes, docs, o) {
103
- var hasBlank, i, k, ref, ref1, result;
104
- if (docs.length === 0) {
105
- return '';
106
- }
107
- if (docs.length === 1) {
108
- return docs[0];
109
- }
110
- result = [];
111
- for (i = k = 0, ref = docs.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
112
- if (i > 0) {
113
- hasBlank = ((ref1 = nodes[i]) != null ? ref1._blankBefore : void 0) || false;
114
- result.push(hasBlank ? [hardline, hardline] : hardline);
115
- }
116
- result.push(docs[i]);
117
- }
118
- return result;
119
- };
120
-
121
- analyzeBlankLines = function(children, originalText, locations) {
122
- var between, currStartOffset, i, k, node, prev, prevEndOffset, ref, results;
123
- if (!(originalText && children.length > 1)) {
124
- return;
125
- }
126
- results = [];
127
- for (i = k = 0, ref = children.length; (0 <= ref ? k < ref : k > ref); i = 0 <= ref ? ++k : --k) {
128
- node = children[i];
129
- if (!(node != null ? node.location : void 0)) {
130
- continue;
131
- }
132
- prev = children[i - 1];
133
- if (!(prev != null ? prev.location : void 0)) {
134
- continue;
135
- }
136
- // Check for blank line in source between prev end and this node start
137
- prevEndOffset = prev.location.offset + prev.location.length;
138
- currStartOffset = node.location.offset;
139
- between = originalText.slice(prevEndOffset, currStartOffset);
140
- if (/\n\s*\n/.test(between)) {
141
- results.push(node._blankBefore = true);
142
- } else {
143
- results.push(void 0);
144
- }
145
- }
146
- return results;
147
- };
148
-
149
- // ─── Merge consecutive statements ──────────────────────────
150
- mergeAllStatements = function(children) {
151
- var child, i, j, next, result;
152
- if (!((children != null ? children.length : void 0) > 1)) {
153
- return children;
154
- }
155
- result = [];
156
- i = 0;
157
- while (i < children.length) {
158
- child = children[i];
159
- if (child instanceof ControlFlow && child.controlKind === 'statement' && child.children.length === 0) {
160
- group = [child];
161
- j = i + 1;
162
- while (j < children.length) {
163
- next = children[j];
164
- if (next instanceof ControlFlow && next.controlKind === 'statement' && next.children.length === 0) {
165
- group.push(next);
166
- j++;
167
- } else {
168
- break;
169
- }
170
- }
171
- if (group.length > 1) {
172
- result.push(new MergedStatements(group));
173
- i = j;
174
- continue;
175
- }
176
- }
177
- result.push(child);
178
- i++;
179
- }
180
- return result;
181
- };
182
-
183
- // ─── Main entry ────────────────────────────────────────────
184
- export var print = function(path, o, printFn) {
185
- var node;
186
- node = path.node;
187
- if (!node) {
188
- return '';
189
- }
190
- // Hang options on path for nested lookups
191
- path.__opts = o;
192
- return printNode(path, o, printFn);
193
- };
194
-
195
- printNode = function(path, o, printFn) {
196
- var node;
197
- node = path.node;
198
- if (!node) {
199
- return '';
200
- }
201
- if (node instanceof Document) {
202
- return printDoc(path, o, printFn);
203
- }
204
- if (node instanceof Element) {
205
- return printElement(path, o, printFn);
206
- }
207
- if (node instanceof ImplicitDiv) {
208
- return printImplicitDiv(path, o, printFn);
209
- }
210
- if (node instanceof Text) {
211
- return node.value;
212
- }
213
- if (node instanceof Output) {
214
- return printOutput(path, o, printFn);
215
- }
216
- if (node instanceof ControlFlow) {
217
- return printControlFlow(path, o, printFn);
218
- }
219
- if (node instanceof MergedStatements) {
220
- return printMergedFlow(path, o, printFn);
221
- }
222
- if (node instanceof Comment) {
223
- return printComment(path, o, printFn);
224
- }
225
- if (node instanceof Filter) {
226
- return printFilter(path, o, printFn);
227
- }
228
- if (node instanceof Doctype) {
229
- return printDoctype(path, o);
230
- }
231
- return '';
232
- };
233
-
234
- // ─── Document ──────────────────────────────────────────────
235
- printDoc = function(path, o, printFn) {
236
- var childDocs, k, len, n, next, node, ref, ref1;
237
- node = path.node;
238
- // Pre-pass: merge statements if enabled
239
- if (o.statementMerging === 'merge') {
240
- node.children = mergeAllStatements(node.children);
241
- }
242
- // Pre-pass: analyze blank lines
243
- if (o.blankLineHandling === 'preserve' && o.originalText) {
244
- ref = node.children;
245
- for (k = 0, len = ref.length; k < len; k++) {
246
- n = ref[k];
247
- next = node.children[node.children.indexOf(n) + 1];
248
- if (next) {
249
- analyzeBlankLines([n, next], o.originalText);
250
- }
251
- }
252
- }
253
- // Print children
254
- childDocs = (ref1 = path.map(printFn, 'children')) != null ? ref1 : [];
255
- return join(hardline, childDocs);
256
- };
257
-
258
- // ─── Element ───────────────────────────────────────────────
259
- printElement = function(path, o, printFn) {
260
- var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, isVoid, joined, node, ref, ref1, ref2, ref3, selfClose, tag;
261
- node = path.node;
262
- tag = formatTag(node.tag, o);
263
- // Classes and ID
264
- classes = node.classes.map(function(c) {
265
- return `.${c}`;
266
- }).join('');
267
- id = node.id ? `#${node.id}` : '';
268
- // Attributes
269
- attrStyle = detectStyle(node, o);
270
- attrs = formatAttributes(node, attrStyle, o);
271
- // Self-close marker
272
- isVoid = typeof node.tag === 'string' && isVoidElement(node.tag);
273
- selfClose = '';
274
- if (isVoid) {
275
- if (o.voidElementStyle === 'self-closing') {
276
- selfClose = '/';
277
- } else if (node.isSelfClosing) {
278
- selfClose = '/';
279
- }
280
- } else if (node.isSelfClosing) {
281
- selfClose = '/';
282
- }
283
- header = `%${tag}${classes}${id}${attrs}${selfClose}`;
284
- // Children
285
- children = (ref = node.children) != null ? ref : [];
286
- if (children.length === 0) {
287
- return header;
288
- }
289
- // Inline child
290
- if (isSingleInlineChild(children) && o.controlFlowInline !== false) {
291
- childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
292
- return `${header} ${childDoc}`;
293
- }
294
- childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
295
- joined = joinChildrenWithBlanks(children, childDocs, o);
296
- return [header, indent([hardline, joined])];
297
- };
298
-
299
- // ─── ImplicitDiv ───────────────────────────────────────────
300
- printImplicitDiv = function(path, o, printFn) {
301
- var attrStyle, attrs, childDoc, childDocs, children, classes, header, id, joined, node, ref, ref1, ref2, ref3;
302
- node = path.node;
303
- classes = node.classes.map(function(c) {
304
- return `.${c}`;
305
- }).join('');
306
- id = node.id ? `#${node.id}` : '';
307
- attrStyle = detectStyle(node, o);
308
- attrs = formatAttributes(node, attrStyle, o);
309
- header = `${classes}${id}${attrs}`;
310
- children = (ref = node.children) != null ? ref : [];
311
- if (children.length === 0) {
312
- return header;
313
- }
314
- if (isSingleInlineChild(children)) {
315
- childDoc = (ref1 = (ref2 = path.map(printFn, 'children')) != null ? ref2[0] : void 0) != null ? ref1 : '';
316
- return `${header} ${childDoc}`;
317
- }
318
- childDocs = (ref3 = path.map(printFn, 'children')) != null ? ref3 : [];
319
- joined = joinChildrenWithBlanks(children, childDocs, o);
320
- return [header, indent([hardline, joined])];
321
- };
322
-
323
- // ─── Output ────────────────────────────────────────────────
324
- printOutput = function(path, o, printFn) {
325
- var childDocs, children, expr, joined, marker, node, ref, ref1, ref2;
326
- node = path.node;
327
- marker = node.outputKind === 'unescaped' ? '!=' : '=';
328
- expr = (ref = node.expression.source) != null ? ref : '';
329
- if (o.coffeeScriptFormat) {
330
- expr = formatCoffeeScript(expr, o);
331
- }
332
- children = (ref1 = node.children) != null ? ref1 : [];
333
- if (children.length === 0) {
334
- return `${marker} ${expr}`;
335
- }
336
- childDocs = (ref2 = path.map(printFn, 'children')) != null ? ref2 : [];
337
- joined = joinChildrenWithBlanks(children, childDocs, o);
338
- return [`${marker} ${expr}`, indent([hardline, joined])];
339
- };
340
-
341
- // ─── Control Flow ──────────────────────────────────────────
342
- printControlFlow = function(path, o, printFn) {
343
- return printFlow(path, o, printFn);
344
- };
345
-
346
- printFlow = function(path, o, printFn) {
347
- var childLines, children, expr, header, kind, node, ref, ref1, ref2, sep;
348
- node = path.node;
349
- kind = node.controlKind;
350
- // Format expression
351
- expr = (ref = node.expression.source) != null ? ref : '';
352
- if (o.coffeeScriptFormat && kind !== 'statement') {
353
- expr = formatCoffeeScript(expr, o);
354
- }
355
- // Build header
356
- header = (function() {
357
- switch (kind) {
358
- case 'statement':
359
- if (expr) {
360
- return `- ${expr}`;
361
- } else {
362
- return '-';
363
- }
364
- break;
365
- case 'else':
366
- return '- else';
367
- case 'else if':
368
- return `- else if ${expr}`;
369
- default:
370
- return `- ${kind} ${expr}`;
371
- }
372
- })();
373
- children = (ref1 = node.children) != null ? ref1 : [];
374
- // Inline expansion (controlFlowInline)
375
- if (o.controlFlowInline && children.length > 0 && (kind === 'if' || kind === 'unless' || kind === 'for' || kind === 'while')) {
376
- if (children.length === 1 && (children[0] instanceof Element || children[0] instanceof ImplicitDiv)) {
377
- sep = kind === 'if' || kind === 'unless' ? ' then ' : ' ';
378
- return `${header}${sep}${printNode(path.map(printFn, 'children.0'), o, printFn)}`;
379
- }
380
- }
381
- if (children.length === 0) {
382
- return header;
383
- }
384
- // Re-parsed merged statements: statement-kind with empty expr + TEXT children
385
- // Use baked indentation (matching printMergedFlow) to avoid Prettier indent quirks
386
- if (kind === 'statement' && !expr && children.every(function(c) {
387
- return c instanceof Text || c instanceof Output;
388
- })) {
389
- childLines = children.map(function(c) {
390
- var val;
391
- val = (function() {
392
- var ref2, ref3;
393
- switch (false) {
394
- case !(c instanceof Text):
395
- return c.value;
396
- case !(c instanceof Output):
397
- return (ref2 = (ref3 = c.expression) != null ? ref3.source : void 0) != null ? ref2 : '';
398
- default:
399
- return '';
400
- }
401
- })();
402
- return ' ' + val;
403
- });
404
- return join(hardline, ['-', childLines.join('\n')]);
405
- }
406
- return [header, indent([hardline, join(hardline, (ref2 = path.map(printFn, 'children')) != null ? ref2 : [])])];
407
- };
408
-
409
- printMergedFlow = function(path, o, printFn) {
410
- var lines, node;
411
- node = path.node;
412
- lines = node.statements.map(function(stmt) {
413
- var expr, ref;
414
- expr = (ref = stmt.expression.source) != null ? ref : '';
415
- if (o.coffeeScriptFormat) {
416
- expr = formatCoffeeScript(expr, o);
417
- }
418
- return ' ' + expr;
419
- });
420
- return join(hardline, ['-', lines.join('\n')]);
421
- };
422
-
423
- // ─── Comment ───────────────────────────────────────────────
424
- printComment = function(path, o, _printFn) {
425
- var node, text;
426
- node = path.node;
427
- if (node.commentKind === 'html') {
428
- return `/ ${node.text}`;
429
- }
430
- text = node.text || '';
431
- if (o.commentFormat && text.length > o.printWidth * 0.7) {
432
- text = reflowCommentText(text, o.printWidth);
433
- }
434
- return `-# ${text}`;
435
- };
436
-
437
- reflowCommentText = function(text, width) {
438
- var current, k, len, lines, word, words;
439
- words = text.split(/\s+/);
440
- lines = [];
441
- current = '';
442
- for (k = 0, len = words.length; k < len; k++) {
443
- word = words[k];
444
- if (current.length + word.length + 1 > width - 3) { // 3 = "-# "
445
- if (current) {
446
- lines.push(current.trim());
447
- }
448
- current = word;
449
- } else {
450
- current += current ? ' ' + word : word;
451
- }
452
- }
453
- if (current) {
454
- lines.push(current.trim());
455
- }
456
- return lines.join('\n-# ');
457
- };
458
-
459
- // ─── Filter ────────────────────────────────────────────────
460
- printFilter = function(path, o, _printFn) {
461
- var header, indented, node;
462
- node = path.node;
463
- header = `:${node.filterName}`;
464
- if (node.content) {
465
- indented = node.content.split('\n').map(function(l) {
466
- return ' ' + l;
467
- }).join('\n');
468
- return `${header}\n${indented}`;
469
- } else {
470
- return header;
471
- }
472
- };
473
-
474
- // ─── Doctype ───────────────────────────────────────────────
475
- printDoctype = function(path, o) {
476
- var node;
477
- node = path.node;
478
- return `!!! ${node.value || 'html'}`;
479
- };
480
-
481
- // ─── Tag formatting ────────────────────────────────────────
482
- formatTag = function(tag, o) {
483
- if (tag instanceof Expression) {
484
- return tag.source;
485
- }
486
- if (o.tagCase === 'lowercase' && /^[a-z]/.test(tag)) {
487
- return tag.toLowerCase();
488
- }
489
- return tag;
490
- };
491
-
492
- // ─── Attribute style detection ─────────────────────────────
493
- detectStyle = function(node, o) {
494
- var ref;
495
- if (o.attributeStyle !== 'preserve') {
496
- return o.attributeStyle;
497
- }
498
- return (ref = node.attrStyle) != null ? ref : 'bare';
499
- };
500
-
501
- formatAttributes = function(node, style, o) {
502
- var attr, attrs, bareParts, expr, k, len, name, parts, ref, ref1, ref2, val;
503
- attrs = (ref = node.attributes) != null ? ref : [];
504
- if (!(attrs.length > 0)) {
505
- return '';
506
- }
507
- // Bare style: HTML-style key="val" key2={expr} — space-separated
508
- if (style === 'bare') {
509
- bareParts = (function() {
510
- var k, len, ref1, ref2, results;
511
- results = [];
512
- for (k = 0, len = attrs.length; k < len; k++) {
513
- attr = attrs[k];
514
- if (attr.spread) {
515
- expr = (ref1 = attr.expression.source) != null ? ref1 : '';
516
- if (o.coffeeScriptFormat) {
517
- expr = formatCoffeeScript(expr, o);
518
- }
519
- results.push(`{${expr}...}`);
520
- } else if (attr.shorthand) {
521
- results.push(attr.name);
522
- } else {
523
- results.push(`${attr.name}=${(ref2 = attr.value.source) != null ? ref2 : ''}`);
524
- }
525
- }
526
- return results;
527
- })();
528
- return ` ${bareParts.join(' ')}`;
529
- } else {
530
- parts = [];
531
- for (k = 0, len = attrs.length; k < len; k++) {
532
- attr = attrs[k];
533
- if (attr.spread) {
534
- expr = (ref1 = attr.expression.source) != null ? ref1 : '';
535
- if (o.coffeeScriptFormat) {
536
- expr = formatCoffeeScript(expr, o);
537
- }
538
- parts.push(style === 'parens' ? `...${expr}` : `{${expr}...}`);
539
- continue;
540
- }
541
- name = attr.name;
542
- val = (ref2 = attr.value.source) != null ? ref2 : '';
543
- if (o.coffeeScriptFormat) {
544
- val = formatCoffeeScript(val, o);
545
- }
546
- if (attr.shorthand) {
547
- parts.push(name);
548
- } else {
549
- parts.push(`${name}: ${val}`);
550
- }
551
- }
552
- switch (style) {
553
- case 'braces':
554
- return `{ ${parts.join(', ')} }`;
555
- case 'parens':
556
- return `( ${parts.join(', ')} )`;
557
- default:
558
- return parts.join(', ');
559
- }
560
- }
561
- };
562
-
563
- // ─── CoffeeScript Embed (for Prettier embed) ───────────────
564
- export var embedCoffeeScript = function(path, _print) {
565
- var expr, node, ref;
566
- node = path.node;
567
- if (!node) {
568
- return null;
569
- }
570
- if (node instanceof Output || node instanceof ControlFlow) {
571
- expr = (ref = node.expression) != null ? ref.source : void 0;
572
- if (!(expr && /[\u0000-\u00ff]/.test(expr))) {
573
- return null;
574
- }
575
- null;
576
- }
577
- return null;
578
- };
579
-
580
- export {
581
- MergedStatements,
582
- mergeAllStatements
583
- };