prettier-plugin-sfmc 1.0.0 → 1.0.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/package.json +13 -13
- package/src/ampscript-block-boundaries.js +14 -14
- package/src/index.js +89 -77
- package/src/printer.js +67 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prettier-plugin-sfmc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Prettier plugin for Salesforce Marketing Cloud - AMPscript, Marketing Cloud Next Handlebars, SSJS, and SQL formatting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -47,25 +47,25 @@
|
|
|
47
47
|
],
|
|
48
48
|
"license": "MIT",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"ampscript-data": "^
|
|
51
|
-
"ampscript-parser": "^0.
|
|
50
|
+
"ampscript-data": "^3.0.0",
|
|
51
|
+
"ampscript-parser": "^0.2.0",
|
|
52
52
|
"handlebars-data": "^0.3.0",
|
|
53
53
|
"prettier-plugin-sql": "^0.20.0",
|
|
54
|
-
"ssjs-data": "^0.
|
|
54
|
+
"ssjs-data": "^0.14.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/js": "^10.0.1",
|
|
58
|
-
"@jest/globals": "^30.
|
|
59
|
-
"eslint": "^10.
|
|
58
|
+
"@jest/globals": "^30.4.1",
|
|
59
|
+
"eslint": "^10.6.0",
|
|
60
60
|
"eslint-config-prettier": "^10.1.8",
|
|
61
|
-
"eslint-plugin-jsdoc": "^
|
|
62
|
-
"eslint-plugin-prettier": "^5.5.
|
|
63
|
-
"eslint-plugin-unicorn": "^
|
|
64
|
-
"globals": "^17.
|
|
61
|
+
"eslint-plugin-jsdoc": "^63.0.12",
|
|
62
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
63
|
+
"eslint-plugin-unicorn": "^71.1.0",
|
|
64
|
+
"globals": "^17.7.0",
|
|
65
65
|
"husky": "^9.1.7",
|
|
66
|
-
"jest": "^30.
|
|
67
|
-
"lint-staged": "^17.0.
|
|
68
|
-
"prettier": "^3.
|
|
66
|
+
"jest": "^30.4.2",
|
|
67
|
+
"lint-staged": "^17.0.8",
|
|
68
|
+
"prettier": "^3.9.5"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"prettier": ">=3.7.0"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @param {string} ch
|
|
7
7
|
*/
|
|
8
8
|
function isHorizontalSpaceOrBom(ch) {
|
|
9
|
-
return
|
|
9
|
+
return [' ', '\t', '\u{FEFF}'].includes(ch);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -21,16 +21,16 @@ function isIndexAtLineStart(text, index) {
|
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
let
|
|
25
|
-
while (
|
|
26
|
-
|
|
24
|
+
let index_ = index - 1;
|
|
25
|
+
while (index_ >= 0 && isHorizontalSpaceOrBom(text[index_])) {
|
|
26
|
+
index_--;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (
|
|
29
|
+
if (index_ < 0) {
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const ch = text[
|
|
33
|
+
const ch = text[index_];
|
|
34
34
|
return ch === '\n' || ch === '\r';
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -42,18 +42,18 @@ function isIndexAtLineStart(text, index) {
|
|
|
42
42
|
* @param {number} index
|
|
43
43
|
*/
|
|
44
44
|
function isIndexAtLineEnd(text, index) {
|
|
45
|
-
const
|
|
46
|
-
let
|
|
45
|
+
const length = text.length;
|
|
46
|
+
let index_ = index;
|
|
47
47
|
|
|
48
|
-
while (
|
|
49
|
-
|
|
48
|
+
while (index_ < length && isHorizontalSpaceOrBom(text[index_])) {
|
|
49
|
+
index_++;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (
|
|
52
|
+
if (index_ >= length) {
|
|
53
53
|
return true;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const ch = text[
|
|
56
|
+
const ch = text[index_];
|
|
57
57
|
return ch === '\n' || ch === '\r';
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -65,7 +65,7 @@ function isIndexAtLineEnd(text, index) {
|
|
|
65
65
|
* @param {number | undefined} blockStart
|
|
66
66
|
*/
|
|
67
67
|
export function needsLeadingBlockBreak(originalText, blockStart) {
|
|
68
|
-
if (typeof originalText !== 'string' || blockStart ==
|
|
68
|
+
if (typeof originalText !== 'string' || blockStart == undefined || blockStart < 0) {
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -80,7 +80,7 @@ export function needsLeadingBlockBreak(originalText, blockStart) {
|
|
|
80
80
|
* @param {number | undefined} blockEnd
|
|
81
81
|
*/
|
|
82
82
|
export function needsTrailingBlockBreak(originalText, blockEnd) {
|
|
83
|
-
if (typeof originalText !== 'string' || blockEnd ==
|
|
83
|
+
if (typeof originalText !== 'string' || blockEnd == undefined || blockEnd < 0) {
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
86
|
|
package/src/index.js
CHANGED
|
@@ -37,7 +37,7 @@ function mergeSqlOptionsWithSfmcDefaults(sqlOptions) {
|
|
|
37
37
|
dataTypeCase: 'preserve',
|
|
38
38
|
};
|
|
39
39
|
for (const [key, defaultValue] of Object.entries(defaults)) {
|
|
40
|
-
if (merged
|
|
40
|
+
if (Object.hasOwn(merged, key)) {
|
|
41
41
|
merged[key].default = defaultValue;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -111,21 +111,21 @@ function isBareName(token) {
|
|
|
111
111
|
* caller, not here.
|
|
112
112
|
*
|
|
113
113
|
* @param {string} inner Raw inner text of a mustache expression.
|
|
114
|
-
* @param {{ helperCase?: 'upper-camel'|'lower-camel'|'upper'|'lower'|'preserve' }} [
|
|
114
|
+
* @param {{ helperCase?: 'upper-camel'|'lower-camel'|'upper'|'lower'|'preserve' }} [options] Options.
|
|
115
115
|
* @returns {string} Normalized inner text (collapsed whitespace, recased heads).
|
|
116
116
|
*/
|
|
117
|
-
function normalizeMustacheInner(inner,
|
|
118
|
-
const helperCase =
|
|
117
|
+
function normalizeMustacheInner(inner, options = {}) {
|
|
118
|
+
const helperCase = options.helperCase || 'lower-camel';
|
|
119
119
|
|
|
120
120
|
// First pass: collapse whitespace outside string literals, preserving quotes.
|
|
121
121
|
let collapsed = '';
|
|
122
|
-
/** @type {string|
|
|
123
|
-
let quote
|
|
122
|
+
/** @type {string|undefined} */
|
|
123
|
+
let quote;
|
|
124
124
|
for (const ch of inner) {
|
|
125
125
|
if (quote) {
|
|
126
126
|
collapsed += ch;
|
|
127
127
|
if (ch === quote) {
|
|
128
|
-
quote =
|
|
128
|
+
quote = undefined;
|
|
129
129
|
}
|
|
130
130
|
continue;
|
|
131
131
|
}
|
|
@@ -134,7 +134,7 @@ function normalizeMustacheInner(inner, opts = {}) {
|
|
|
134
134
|
collapsed += ch;
|
|
135
135
|
continue;
|
|
136
136
|
}
|
|
137
|
-
if (
|
|
137
|
+
if ([' ', '\t', '\n', '\r', '\f', '\v'].includes(ch)) {
|
|
138
138
|
if (collapsed.length > 0 && collapsed.at(-1) !== ' ') {
|
|
139
139
|
collapsed += ' ';
|
|
140
140
|
}
|
|
@@ -153,60 +153,65 @@ function normalizeMustacheInner(inner, opts = {}) {
|
|
|
153
153
|
// immediately after each `(`. We walk the collapsed string, tracking string
|
|
154
154
|
// literals so `(` / heads inside quotes are ignored.
|
|
155
155
|
let result = '';
|
|
156
|
-
quote =
|
|
157
|
-
let
|
|
158
|
-
let
|
|
159
|
-
let
|
|
156
|
+
quote = undefined;
|
|
157
|
+
let isExpectHead = true;
|
|
158
|
+
let isLeadingSigilConsumed = false;
|
|
159
|
+
let index = 0;
|
|
160
160
|
const n = collapsed.length;
|
|
161
|
-
while (
|
|
162
|
-
const ch = collapsed[
|
|
161
|
+
while (index < n) {
|
|
162
|
+
const ch = collapsed[index];
|
|
163
163
|
if (quote) {
|
|
164
164
|
result += ch;
|
|
165
165
|
if (ch === quote) {
|
|
166
|
-
quote =
|
|
166
|
+
quote = undefined;
|
|
167
167
|
}
|
|
168
|
-
|
|
168
|
+
index++;
|
|
169
169
|
continue;
|
|
170
170
|
}
|
|
171
171
|
if (ch === '"' || ch === "'") {
|
|
172
172
|
quote = ch;
|
|
173
173
|
result += ch;
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
isExpectHead = false;
|
|
175
|
+
index++;
|
|
176
176
|
continue;
|
|
177
177
|
}
|
|
178
178
|
// At the outermost head position, skip a single leading sigil.
|
|
179
|
-
if (
|
|
179
|
+
if (
|
|
180
|
+
isExpectHead &&
|
|
181
|
+
!isLeadingSigilConsumed &&
|
|
182
|
+
result.length === 0 &&
|
|
183
|
+
MUSTACHE_SIGILS.has(ch)
|
|
184
|
+
) {
|
|
180
185
|
result += ch;
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
isLeadingSigilConsumed = true;
|
|
187
|
+
index++;
|
|
183
188
|
continue;
|
|
184
189
|
}
|
|
185
190
|
if (ch === '(') {
|
|
186
191
|
result += ch;
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
isExpectHead = true;
|
|
193
|
+
index++;
|
|
189
194
|
continue;
|
|
190
195
|
}
|
|
191
196
|
if (ch === ' ') {
|
|
192
197
|
result += ch;
|
|
193
|
-
|
|
198
|
+
index++;
|
|
194
199
|
continue;
|
|
195
200
|
}
|
|
196
|
-
if (
|
|
201
|
+
if (isExpectHead) {
|
|
197
202
|
// Read the head token up to the next whitespace, `(`, `)`, or quote.
|
|
198
|
-
let
|
|
199
|
-
while (
|
|
200
|
-
|
|
203
|
+
let index_ = index;
|
|
204
|
+
while (index_ < n && !/[\s()'"]/.test(collapsed[index_])) {
|
|
205
|
+
index_++;
|
|
201
206
|
}
|
|
202
|
-
const token = collapsed.slice(
|
|
207
|
+
const token = collapsed.slice(index, index_);
|
|
203
208
|
result += isBareName(token) ? applyHelperCase(token, helperCase) : token;
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
isExpectHead = false;
|
|
210
|
+
index = index_;
|
|
206
211
|
continue;
|
|
207
212
|
}
|
|
208
213
|
result += ch;
|
|
209
|
-
|
|
214
|
+
index++;
|
|
210
215
|
}
|
|
211
216
|
return result;
|
|
212
217
|
}
|
|
@@ -218,6 +223,36 @@ function normalizeMustacheInner(inner, opts = {}) {
|
|
|
218
223
|
*/
|
|
219
224
|
const HANDLEBARS_PH = 'HANDLEBARSPH';
|
|
220
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Scan forward from `start` for a Handlebars closing delimiter, ignoring any
|
|
228
|
+
* occurrence that sits inside a single- or double-quoted string literal.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} text Text being scanned.
|
|
231
|
+
* @param {number} start Index to begin scanning at (just past the opener).
|
|
232
|
+
* @param {string} closeDelim Closing delimiter to look for (`}}` or `}}}`).
|
|
233
|
+
* @param {number} end Exclusive upper bound for scanning (usually `text.length`).
|
|
234
|
+
* @returns {number} Index of the closing delimiter, or `-1` when not found.
|
|
235
|
+
*/
|
|
236
|
+
function findClosingDelimiter(text, start, closeDelim, end) {
|
|
237
|
+
/** @type {string|undefined} */
|
|
238
|
+
let quote;
|
|
239
|
+
let index = start;
|
|
240
|
+
while (index < end) {
|
|
241
|
+
const ch = text[index];
|
|
242
|
+
if (quote) {
|
|
243
|
+
if (ch === quote) {
|
|
244
|
+
quote = undefined;
|
|
245
|
+
}
|
|
246
|
+
} else if (ch === '"' || ch === "'") {
|
|
247
|
+
quote = ch;
|
|
248
|
+
} else if (text.startsWith(closeDelim, index)) {
|
|
249
|
+
return index;
|
|
250
|
+
}
|
|
251
|
+
index++;
|
|
252
|
+
}
|
|
253
|
+
return -1;
|
|
254
|
+
}
|
|
255
|
+
|
|
221
256
|
/**
|
|
222
257
|
* Extract every well-formed MCN Handlebars `{{…}}` region from a string, replacing
|
|
223
258
|
* each with a `HANDLEBARSPH<n>END` placeholder and recording the normalized
|
|
@@ -235,15 +270,15 @@ const HANDLEBARS_PH = 'HANDLEBARSPH';
|
|
|
235
270
|
* (no throw, no change), so the HTML parser passes them through untouched.
|
|
236
271
|
*
|
|
237
272
|
* @param {string} text Joined HTML/text (AMPscript already placeholdered).
|
|
238
|
-
* @param {{ spacing?: boolean, helperCase?: 'upper-camel'|'lower-camel'|'upper'|'lower'|'preserve' }} [
|
|
273
|
+
* @param {{ spacing?: boolean, helperCase?: 'upper-camel'|'lower-camel'|'upper'|'lower'|'preserve' }} [options]
|
|
239
274
|
* Formatting options: `spacing` pads simple/triple mustaches (sigil mustaches stay
|
|
240
275
|
* tight); `helperCase` recases known catalog helpers at head positions.
|
|
241
276
|
* @returns {{ html: string, tokens: string[] }} Text with Handlebars placeholders
|
|
242
277
|
* and the ordered list of normalized mustache replacements.
|
|
243
278
|
*/
|
|
244
|
-
function extractHandlebarsRegions(text,
|
|
245
|
-
const
|
|
246
|
-
const helperCase =
|
|
279
|
+
function extractHandlebarsRegions(text, options = {}) {
|
|
280
|
+
const isSpacing = options.spacing === true;
|
|
281
|
+
const helperCase = options.helperCase || 'lower-camel';
|
|
247
282
|
/** @type {string[]} */
|
|
248
283
|
const tokens = [];
|
|
249
284
|
if (typeof text !== 'string' || !text.includes('{{')) {
|
|
@@ -251,62 +286,39 @@ function extractHandlebarsRegions(text, opts = {}) {
|
|
|
251
286
|
}
|
|
252
287
|
|
|
253
288
|
let out = '';
|
|
254
|
-
let
|
|
289
|
+
let index = 0;
|
|
255
290
|
const n = text.length;
|
|
256
|
-
while (
|
|
257
|
-
const open = text.indexOf('{{',
|
|
291
|
+
while (index < n) {
|
|
292
|
+
const open = text.indexOf('{{', index);
|
|
258
293
|
if (open === -1) {
|
|
259
|
-
out += text.slice(
|
|
294
|
+
out += text.slice(index);
|
|
260
295
|
break;
|
|
261
296
|
}
|
|
262
|
-
out += text.slice(
|
|
297
|
+
out += text.slice(index, open);
|
|
263
298
|
|
|
264
299
|
const isTriple = text[open + 2] === '{';
|
|
265
|
-
const
|
|
266
|
-
const afterOpen = text.slice(open +
|
|
300
|
+
const openLength = isTriple ? 3 : 2;
|
|
301
|
+
const afterOpen = text.slice(open + openLength);
|
|
267
302
|
|
|
268
303
|
// Comments: record verbatim, never normalize their contents.
|
|
269
304
|
if (!isTriple && afterOpen.startsWith('!')) {
|
|
270
305
|
const isBlock = afterOpen.startsWith('!--');
|
|
271
|
-
const
|
|
272
|
-
const close = text.indexOf(
|
|
306
|
+
const closeString = isBlock ? '--}}' : '}}';
|
|
307
|
+
const close = text.indexOf(closeString, open + openLength);
|
|
273
308
|
if (close === -1) {
|
|
274
309
|
out += text.slice(open);
|
|
275
310
|
break;
|
|
276
311
|
}
|
|
277
|
-
const end = close +
|
|
312
|
+
const end = close + closeString.length;
|
|
278
313
|
tokens.push(text.slice(open, end));
|
|
279
314
|
out += `${HANDLEBARS_PH}${tokens.length - 1}END`;
|
|
280
|
-
|
|
315
|
+
index = end;
|
|
281
316
|
continue;
|
|
282
317
|
}
|
|
283
318
|
|
|
284
319
|
// Scan for the closing delimiter, ignoring `}}` inside string literals.
|
|
285
320
|
const closeDelim = isTriple ? '}}}' : '}}';
|
|
286
|
-
|
|
287
|
-
let quote = null;
|
|
288
|
-
let j = open + openLen;
|
|
289
|
-
let foundClose = -1;
|
|
290
|
-
while (j < n) {
|
|
291
|
-
const ch = text[j];
|
|
292
|
-
if (quote) {
|
|
293
|
-
if (ch === quote) {
|
|
294
|
-
quote = null;
|
|
295
|
-
}
|
|
296
|
-
j++;
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (ch === '"' || ch === "'") {
|
|
300
|
-
quote = ch;
|
|
301
|
-
j++;
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
if (text.startsWith(closeDelim, j)) {
|
|
305
|
-
foundClose = j;
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
j++;
|
|
309
|
-
}
|
|
321
|
+
const foundClose = findClosingDelimiter(text, open + openLength, closeDelim, n);
|
|
310
322
|
|
|
311
323
|
if (foundClose === -1) {
|
|
312
324
|
// Unbalanced — leave the remainder exactly as-is for the HTML parser.
|
|
@@ -314,17 +326,17 @@ function extractHandlebarsRegions(text, opts = {}) {
|
|
|
314
326
|
break;
|
|
315
327
|
}
|
|
316
328
|
|
|
317
|
-
const inner = text.slice(open +
|
|
318
|
-
const opener = text.slice(open, open +
|
|
329
|
+
const inner = text.slice(open + openLength, foundClose);
|
|
330
|
+
const opener = text.slice(open, open + openLength);
|
|
319
331
|
const normalized = normalizeMustacheInner(inner, { helperCase });
|
|
320
332
|
// Pad simple mustaches (`{{ … }}`) and triple-stache (`{{{ … }}}`) when
|
|
321
333
|
// `spacing` is enabled; sigil mustaches (`{{#…}}`, `{{/…}}`, `{{^…}}`,
|
|
322
334
|
// `{{>…}}`, `{{&…}}`) always stay tight.
|
|
323
335
|
const hasSigil = MUSTACHE_SIGILS.has(normalized[0]);
|
|
324
|
-
const pad =
|
|
336
|
+
const pad = isSpacing && !hasSigil && normalized.length > 0 ? ' ' : '';
|
|
325
337
|
tokens.push(`${opener}${pad}${normalized}${pad}${closeDelim}`);
|
|
326
338
|
out += `${HANDLEBARS_PH}${tokens.length - 1}END`;
|
|
327
|
-
|
|
339
|
+
index = foundClose + closeDelim.length;
|
|
328
340
|
}
|
|
329
341
|
|
|
330
342
|
return { html: out, tokens };
|
|
@@ -481,9 +493,9 @@ export const printers = {
|
|
|
481
493
|
parts.push(d.slice(last, m.index));
|
|
482
494
|
}
|
|
483
495
|
if (m[1] === undefined) {
|
|
484
|
-
parts.push(handlebarsTokens[Number
|
|
496
|
+
parts.push(handlebarsTokens[Number(m[2])]);
|
|
485
497
|
} else {
|
|
486
|
-
parts.push(print(['children', Number
|
|
498
|
+
parts.push(print(['children', Number(m[1])]));
|
|
487
499
|
}
|
|
488
500
|
last = m.index + m[0].length;
|
|
489
501
|
}
|
package/src/printer.js
CHANGED
|
@@ -36,10 +36,14 @@ function collectVariableMap(node) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (Array.isArray(n.children)) {
|
|
39
|
-
n.children
|
|
39
|
+
for (const child of n.children) {
|
|
40
|
+
walk(child);
|
|
41
|
+
}
|
|
40
42
|
}
|
|
41
43
|
if (Array.isArray(n.statements)) {
|
|
42
|
-
n.statements
|
|
44
|
+
for (const statement of n.statements) {
|
|
45
|
+
walk(statement);
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
if (n.target) {
|
|
45
49
|
walk(n.target);
|
|
@@ -63,13 +67,19 @@ function collectVariableMap(node) {
|
|
|
63
67
|
walk(n.argument);
|
|
64
68
|
}
|
|
65
69
|
if (Array.isArray(n.arguments)) {
|
|
66
|
-
n.arguments
|
|
70
|
+
for (const argument of n.arguments) {
|
|
71
|
+
walk(argument);
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
74
|
if (Array.isArray(n.variables)) {
|
|
69
|
-
n.variables
|
|
75
|
+
for (const variable of n.variables) {
|
|
76
|
+
walk(variable);
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
79
|
if (Array.isArray(n.consequent)) {
|
|
72
|
-
n.consequent
|
|
80
|
+
for (const consequent of n.consequent) {
|
|
81
|
+
walk(consequent);
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
if (Array.isArray(n.alternates)) {
|
|
75
85
|
for (const alt of n.alternates) {
|
|
@@ -77,12 +87,16 @@ function collectVariableMap(node) {
|
|
|
77
87
|
walk(alt.condition);
|
|
78
88
|
}
|
|
79
89
|
if (Array.isArray(alt.body)) {
|
|
80
|
-
alt.body
|
|
90
|
+
for (const bodyNode of alt.body) {
|
|
91
|
+
walk(bodyNode);
|
|
92
|
+
}
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
if (Array.isArray(n.body)) {
|
|
85
|
-
n.body
|
|
97
|
+
for (const bodyNode of n.body) {
|
|
98
|
+
walk(bodyNode);
|
|
99
|
+
}
|
|
86
100
|
}
|
|
87
101
|
if (n.counter) {
|
|
88
102
|
walk(n.counter);
|
|
@@ -109,12 +123,8 @@ function isNeedlessParenExpression(node) {
|
|
|
109
123
|
return false;
|
|
110
124
|
}
|
|
111
125
|
const inner = node.expression;
|
|
112
|
-
return (
|
|
113
|
-
inner.type
|
|
114
|
-
inner.type === 'StringLiteral' ||
|
|
115
|
-
inner.type === 'NumberLiteral' ||
|
|
116
|
-
inner.type === 'BooleanLiteral' ||
|
|
117
|
-
inner.type === 'Identifier'
|
|
126
|
+
return ['Variable', 'StringLiteral', 'NumberLiteral', 'BooleanLiteral', 'Identifier'].includes(
|
|
127
|
+
inner.type,
|
|
118
128
|
);
|
|
119
129
|
}
|
|
120
130
|
|
|
@@ -207,7 +217,7 @@ function printAmpscriptNode(path, options, print) {
|
|
|
207
217
|
const variableStyle = options.ampscriptVarDeclarationStyle || 'multi-line';
|
|
208
218
|
const keywordCase = options.ampscriptKeywordCase || 'lower';
|
|
209
219
|
const functionCase = options.ampscriptFunctionCase || 'upper-camel';
|
|
210
|
-
const
|
|
220
|
+
const isBlockLineBreaks = options.ampscriptBlockLineBreaks === true;
|
|
211
221
|
|
|
212
222
|
function resolveVariable(variableName) {
|
|
213
223
|
if (!enforceCasing) {
|
|
@@ -269,7 +279,7 @@ function printAmpscriptNode(path, options, print) {
|
|
|
269
279
|
|
|
270
280
|
switch (node.type) {
|
|
271
281
|
case 'Document': {
|
|
272
|
-
return path.map(print, 'children');
|
|
282
|
+
return path.map((childPath, index) => print(childPath, index), 'children');
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
case 'Content': {
|
|
@@ -277,17 +287,21 @@ function printAmpscriptNode(path, options, print) {
|
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
case 'Block': {
|
|
280
|
-
const
|
|
281
|
-
|
|
290
|
+
const statements = path.map(
|
|
291
|
+
(statementPath, index) => print(statementPath, index),
|
|
292
|
+
'statements',
|
|
293
|
+
);
|
|
294
|
+
const statementNodes = node.statements;
|
|
282
295
|
const body = [];
|
|
283
|
-
for (const [index,
|
|
296
|
+
for (const [index, statement] of statements.entries()) {
|
|
284
297
|
if (index > 0) {
|
|
285
298
|
body.push(hardline);
|
|
286
|
-
|
|
299
|
+
const statementNode = statementNodes[index];
|
|
300
|
+
if (statementNode && statementNode.blankLineBefore) {
|
|
287
301
|
body.push(hardline);
|
|
288
302
|
}
|
|
289
303
|
}
|
|
290
|
-
body.push(
|
|
304
|
+
body.push(statement);
|
|
291
305
|
}
|
|
292
306
|
const isScriptTag = node.syntax === 'script-tag';
|
|
293
307
|
const blockOpen = isScriptTag ? '<script runat="server" language="ampscript">' : '%%[';
|
|
@@ -298,7 +312,7 @@ function printAmpscriptNode(path, options, print) {
|
|
|
298
312
|
hardline,
|
|
299
313
|
blockClose,
|
|
300
314
|
]);
|
|
301
|
-
if (
|
|
315
|
+
if (isBlockLineBreaks) {
|
|
302
316
|
const originalText = options.originalText;
|
|
303
317
|
const parts = [];
|
|
304
318
|
if (needsLeadingBlockBreak(originalText, node.start)) {
|
|
@@ -318,11 +332,11 @@ function printAmpscriptNode(path, options, print) {
|
|
|
318
332
|
}
|
|
319
333
|
|
|
320
334
|
case 'InlineExpression': {
|
|
321
|
-
const
|
|
335
|
+
const expression = print('expression');
|
|
322
336
|
if (enforceSpacing) {
|
|
323
|
-
return ['%%= ',
|
|
337
|
+
return ['%%= ', expression, ' =%%'];
|
|
324
338
|
}
|
|
325
|
-
return ['%%=',
|
|
339
|
+
return ['%%=', expression, '=%%'];
|
|
326
340
|
}
|
|
327
341
|
|
|
328
342
|
case 'Comment': {
|
|
@@ -330,7 +344,10 @@ function printAmpscriptNode(path, options, print) {
|
|
|
330
344
|
}
|
|
331
345
|
|
|
332
346
|
case 'VarDeclaration': {
|
|
333
|
-
const variables = path.map(
|
|
347
|
+
const variables = path.map(
|
|
348
|
+
(variablePath, index) => print(variablePath, index),
|
|
349
|
+
'variables',
|
|
350
|
+
);
|
|
334
351
|
if (variableStyle === 'multi-line') {
|
|
335
352
|
return [
|
|
336
353
|
kw('var', node.originalKeyword),
|
|
@@ -367,10 +384,17 @@ function printAmpscriptNode(path, options, print) {
|
|
|
367
384
|
group([kw('if', okw.if), ' ', print('condition'), ' ', kw('then', okw.then)]),
|
|
368
385
|
);
|
|
369
386
|
if (node.consequent.length > 0) {
|
|
370
|
-
|
|
387
|
+
const consequentDocuments = path.map(
|
|
388
|
+
(consequentPath, index) => print(consequentPath, index),
|
|
389
|
+
'consequent',
|
|
390
|
+
);
|
|
391
|
+
parts.push(indent([hardline, join(hardline, consequentDocuments)]));
|
|
371
392
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
393
|
+
const altDocuments = path.map(
|
|
394
|
+
(alternatePath, index) => print(alternatePath, index),
|
|
395
|
+
'alternates',
|
|
396
|
+
);
|
|
397
|
+
for (const altDocument of altDocuments) {
|
|
374
398
|
parts.push(altDocument);
|
|
375
399
|
}
|
|
376
400
|
// Only emit endif when the parser saw it in this block (cross-block
|
|
@@ -395,7 +419,8 @@ function printAmpscriptNode(path, options, print) {
|
|
|
395
419
|
]),
|
|
396
420
|
);
|
|
397
421
|
if (node.body.length > 0) {
|
|
398
|
-
|
|
422
|
+
const bodyDocuments = path.map((bodyPath, index) => print(bodyPath, index), 'body');
|
|
423
|
+
parts.push(indent([hardline, join(hardline, bodyDocuments)]));
|
|
399
424
|
}
|
|
400
425
|
return parts;
|
|
401
426
|
}
|
|
@@ -405,7 +430,8 @@ function printAmpscriptNode(path, options, print) {
|
|
|
405
430
|
const parts = [];
|
|
406
431
|
parts.push([hardline, kw('else', akw.else)]);
|
|
407
432
|
if (node.body.length > 0) {
|
|
408
|
-
|
|
433
|
+
const bodyDocuments = path.map((bodyPath, index) => print(bodyPath, index), 'body');
|
|
434
|
+
parts.push(indent([hardline, join(hardline, bodyDocuments)]));
|
|
409
435
|
}
|
|
410
436
|
return parts;
|
|
411
437
|
}
|
|
@@ -415,7 +441,7 @@ function printAmpscriptNode(path, options, print) {
|
|
|
415
441
|
const counterDocument = node.counter ? print('counter') : '';
|
|
416
442
|
const startDocument = print('startExpr');
|
|
417
443
|
const endDocument = print('endExpr');
|
|
418
|
-
const
|
|
444
|
+
const direction = kw(node.direction, okw.direction);
|
|
419
445
|
const parts = [];
|
|
420
446
|
parts.push([
|
|
421
447
|
kw('for', okw.for),
|
|
@@ -424,14 +450,15 @@ function printAmpscriptNode(path, options, print) {
|
|
|
424
450
|
' = ',
|
|
425
451
|
startDocument,
|
|
426
452
|
' ',
|
|
427
|
-
|
|
453
|
+
direction,
|
|
428
454
|
' ',
|
|
429
455
|
endDocument,
|
|
430
456
|
' ',
|
|
431
457
|
kw('do', okw.do),
|
|
432
458
|
]);
|
|
433
459
|
if (node.body.length > 0) {
|
|
434
|
-
|
|
460
|
+
const bodyDocuments = path.map((bodyPath, index) => print(bodyPath, index), 'body');
|
|
461
|
+
parts.push(indent([hardline, join(hardline, bodyDocuments)]));
|
|
435
462
|
}
|
|
436
463
|
// Only emit next when the parser saw it in this block (cross-block
|
|
437
464
|
// FOR/NEXT spans multiple %%[ ]%% segments; missing next is intentional).
|
|
@@ -446,7 +473,10 @@ function printAmpscriptNode(path, options, print) {
|
|
|
446
473
|
}
|
|
447
474
|
|
|
448
475
|
case 'FunctionCall': {
|
|
449
|
-
const arguments_ = path.map(
|
|
476
|
+
const arguments_ = path.map(
|
|
477
|
+
(argumentPath, index) => print(argumentPath, index),
|
|
478
|
+
'arguments',
|
|
479
|
+
);
|
|
450
480
|
const functionName = function_(node.name);
|
|
451
481
|
return group([
|
|
452
482
|
functionName,
|
|
@@ -503,15 +533,15 @@ function printAmpscriptNode(path, options, print) {
|
|
|
503
533
|
}
|
|
504
534
|
|
|
505
535
|
case 'RawStatement': {
|
|
506
|
-
const
|
|
536
|
+
const document = kw(node.value.toLowerCase(), node.keyword);
|
|
507
537
|
const extra = node.crossBlockIndentDepth || 0;
|
|
508
538
|
if (extra === 0) {
|
|
509
|
-
return
|
|
539
|
+
return document;
|
|
510
540
|
}
|
|
511
541
|
// Nested `indent()` does not stack visible spaces on single-line string
|
|
512
542
|
// leaves; prefix each line so cross-block ENDIF aligns with its opening IF.
|
|
513
543
|
const oneLevel = options.useTabs ? '\t' : ' '.repeat(options.tabWidth || 4);
|
|
514
|
-
return [oneLevel.repeat(extra),
|
|
544
|
+
return [oneLevel.repeat(extra), document];
|
|
515
545
|
}
|
|
516
546
|
|
|
517
547
|
case 'Raw': {
|