prettier-plugin-sfmc 1.0.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettier-plugin-sfmc",
3
- "version": "1.0.1",
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",
@@ -55,17 +55,17 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@eslint/js": "^10.0.1",
58
- "@jest/globals": "^30.3.0",
59
- "eslint": "^10.1.0",
58
+ "@jest/globals": "^30.4.1",
59
+ "eslint": "^10.6.0",
60
60
  "eslint-config-prettier": "^10.1.8",
61
- "eslint-plugin-jsdoc": "^62.0.0",
62
- "eslint-plugin-prettier": "^5.5.0",
63
- "eslint-plugin-unicorn": "^64.0.0",
64
- "globals": "^17.4.0",
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.3.0",
67
- "lint-staged": "^17.0.5",
68
- "prettier": "^3.8.1"
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 ch === ' ' || ch === '\t' || ch === '\uFEFF';
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 i = index - 1;
25
- while (i >= 0 && isHorizontalSpaceOrBom(text[i])) {
26
- i--;
24
+ let index_ = index - 1;
25
+ while (index_ >= 0 && isHorizontalSpaceOrBom(text[index_])) {
26
+ index_--;
27
27
  }
28
28
 
29
- if (i < 0) {
29
+ if (index_ < 0) {
30
30
  return true;
31
31
  }
32
32
 
33
- const ch = text[i];
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 len = text.length;
46
- let j = index;
45
+ const length = text.length;
46
+ let index_ = index;
47
47
 
48
- while (j < len && isHorizontalSpaceOrBom(text[j])) {
49
- j++;
48
+ while (index_ < length && isHorizontalSpaceOrBom(text[index_])) {
49
+ index_++;
50
50
  }
51
51
 
52
- if (j >= len) {
52
+ if (index_ >= length) {
53
53
  return true;
54
54
  }
55
55
 
56
- const ch = text[j];
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 == null || blockStart < 0) {
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 == null || blockEnd < 0) {
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[key]) {
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' }} [opts] Options.
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, opts = {}) {
118
- const helperCase = opts.helperCase || 'lower-camel';
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|null} */
123
- let quote = null;
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 = null;
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 (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\f' || ch === '\v') {
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 = null;
157
- let expectHead = true;
158
- let leadingSigilConsumed = false;
159
- let i = 0;
156
+ quote = undefined;
157
+ let isExpectHead = true;
158
+ let isLeadingSigilConsumed = false;
159
+ let index = 0;
160
160
  const n = collapsed.length;
161
- while (i < n) {
162
- const ch = collapsed[i];
161
+ while (index < n) {
162
+ const ch = collapsed[index];
163
163
  if (quote) {
164
164
  result += ch;
165
165
  if (ch === quote) {
166
- quote = null;
166
+ quote = undefined;
167
167
  }
168
- i++;
168
+ index++;
169
169
  continue;
170
170
  }
171
171
  if (ch === '"' || ch === "'") {
172
172
  quote = ch;
173
173
  result += ch;
174
- expectHead = false;
175
- i++;
174
+ isExpectHead = false;
175
+ index++;
176
176
  continue;
177
177
  }
178
178
  // At the outermost head position, skip a single leading sigil.
179
- if (expectHead && !leadingSigilConsumed && result.length === 0 && MUSTACHE_SIGILS.has(ch)) {
179
+ if (
180
+ isExpectHead &&
181
+ !isLeadingSigilConsumed &&
182
+ result.length === 0 &&
183
+ MUSTACHE_SIGILS.has(ch)
184
+ ) {
180
185
  result += ch;
181
- leadingSigilConsumed = true;
182
- i++;
186
+ isLeadingSigilConsumed = true;
187
+ index++;
183
188
  continue;
184
189
  }
185
190
  if (ch === '(') {
186
191
  result += ch;
187
- expectHead = true;
188
- i++;
192
+ isExpectHead = true;
193
+ index++;
189
194
  continue;
190
195
  }
191
196
  if (ch === ' ') {
192
197
  result += ch;
193
- i++;
198
+ index++;
194
199
  continue;
195
200
  }
196
- if (expectHead) {
201
+ if (isExpectHead) {
197
202
  // Read the head token up to the next whitespace, `(`, `)`, or quote.
198
- let j = i;
199
- while (j < n && !/[\s()'"]/.test(collapsed[j])) {
200
- j++;
203
+ let index_ = index;
204
+ while (index_ < n && !/[\s()'"]/.test(collapsed[index_])) {
205
+ index_++;
201
206
  }
202
- const token = collapsed.slice(i, j);
207
+ const token = collapsed.slice(index, index_);
203
208
  result += isBareName(token) ? applyHelperCase(token, helperCase) : token;
204
- expectHead = false;
205
- i = j;
209
+ isExpectHead = false;
210
+ index = index_;
206
211
  continue;
207
212
  }
208
213
  result += ch;
209
- i++;
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' }} [opts]
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, opts = {}) {
245
- const spacing = opts.spacing === true;
246
- const helperCase = opts.helperCase || 'lower-camel';
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 i = 0;
289
+ let index = 0;
255
290
  const n = text.length;
256
- while (i < n) {
257
- const open = text.indexOf('{{', i);
291
+ while (index < n) {
292
+ const open = text.indexOf('{{', index);
258
293
  if (open === -1) {
259
- out += text.slice(i);
294
+ out += text.slice(index);
260
295
  break;
261
296
  }
262
- out += text.slice(i, open);
297
+ out += text.slice(index, open);
263
298
 
264
299
  const isTriple = text[open + 2] === '{';
265
- const openLen = isTriple ? 3 : 2;
266
- const afterOpen = text.slice(open + openLen);
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 closeStr = isBlock ? '--}}' : '}}';
272
- const close = text.indexOf(closeStr, open + openLen);
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 + closeStr.length;
312
+ const end = close + closeString.length;
278
313
  tokens.push(text.slice(open, end));
279
314
  out += `${HANDLEBARS_PH}${tokens.length - 1}END`;
280
- i = end;
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
- /** @type {string|null} */
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 + openLen, foundClose);
318
- const opener = text.slice(open, open + openLen);
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 = spacing && !hasSigil && normalized.length > 0 ? ' ' : '';
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
- i = foundClose + closeDelim.length;
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.parseInt(m[2], 10)]);
496
+ parts.push(handlebarsTokens[Number(m[2])]);
485
497
  } else {
486
- parts.push(print(['children', Number.parseInt(m[1], 10)]));
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.forEach(walk);
39
+ for (const child of n.children) {
40
+ walk(child);
41
+ }
40
42
  }
41
43
  if (Array.isArray(n.statements)) {
42
- n.statements.forEach(walk);
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.forEach(walk);
70
+ for (const argument of n.arguments) {
71
+ walk(argument);
72
+ }
67
73
  }
68
74
  if (Array.isArray(n.variables)) {
69
- n.variables.forEach(walk);
75
+ for (const variable of n.variables) {
76
+ walk(variable);
77
+ }
70
78
  }
71
79
  if (Array.isArray(n.consequent)) {
72
- n.consequent.forEach(walk);
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.forEach(walk);
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.forEach(walk);
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 === 'Variable' ||
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 blockLineBreaks = options.ampscriptBlockLineBreaks === true;
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 stmts = path.map(print, 'statements');
281
- const stmtNodes = node.statements;
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, stmt] of stmts.entries()) {
296
+ for (const [index, statement] of statements.entries()) {
284
297
  if (index > 0) {
285
298
  body.push(hardline);
286
- if (stmtNodes[index] && stmtNodes[index].blankLineBefore) {
299
+ const statementNode = statementNodes[index];
300
+ if (statementNode && statementNode.blankLineBefore) {
287
301
  body.push(hardline);
288
302
  }
289
303
  }
290
- body.push(stmt);
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 (blockLineBreaks) {
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 expr = print('expression');
335
+ const expression = print('expression');
322
336
  if (enforceSpacing) {
323
- return ['%%= ', expr, ' =%%'];
337
+ return ['%%= ', expression, ' =%%'];
324
338
  }
325
- return ['%%=', expr, '=%%'];
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(print, 'variables');
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
- parts.push(indent([hardline, join(hardline, path.map(print, 'consequent'))]));
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 altDocs = path.map(print, 'alternates');
373
- for (const altDocument of altDocs) {
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
- parts.push(indent([hardline, join(hardline, path.map(print, 'body'))]));
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
- parts.push(indent([hardline, join(hardline, path.map(print, 'body'))]));
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 dir = kw(node.direction, okw.direction);
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
- dir,
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
- parts.push(indent([hardline, join(hardline, path.map(print, 'body'))]));
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(print, 'arguments');
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 doc = kw(node.value.toLowerCase(), node.keyword);
536
+ const document = kw(node.value.toLowerCase(), node.keyword);
507
537
  const extra = node.crossBlockIndentDepth || 0;
508
538
  if (extra === 0) {
509
- return doc;
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), doc];
544
+ return [oneLevel.repeat(extra), document];
515
545
  }
516
546
 
517
547
  case 'Raw': {