prettier-plugin-sfmc 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -20
- package/docs/options/ampscript-block-line-breaks.md +22 -25
- package/package.json +3 -1
- package/src/ampscript-block-boundaries.js +88 -0
- package/src/index.js +7 -3
- package/src/printer.js +16 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ All options use the `ampscript` prefix — they control AMPscript formatting beh
|
|
|
36
36
|
| [`ampscriptQuoteStyle`](docs/options/ampscript-quote-style.md) | `"single"` \| `"double"` | `"single"` | String quote style |
|
|
37
37
|
| [`ampscriptKeywordCase`](docs/options/ampscript-keyword-case.md) | `"lower"` \| `"upper"` \| `"preserve"` | `"lower"` | Keyword casing |
|
|
38
38
|
| [`ampscriptFunctionCase`](docs/options/ampscript-function-case.md) | `"upper-camel"` \| `"lower-camel"` \| `"upper"` \| `"lower"` \| `"preserve"` | `"upper-camel"` | Function name casing |
|
|
39
|
-
| [`ampscriptBlockLineBreaks`](docs/options/ampscript-block-line-breaks.md) | boolean | `
|
|
39
|
+
| [`ampscriptBlockLineBreaks`](docs/options/ampscript-block-line-breaks.md) | boolean | `false` | Optional line breaks around `%%[ ]%%` when not already at a line boundary |
|
|
40
40
|
| [`ampscriptVarDeclarationStyle`](docs/options/ampscript-var-declaration-style.md) | `"auto"` \| `"single-line"` \| `"multi-line"` | `"multi-line"` | Var declaration formatting |
|
|
41
41
|
|
|
42
42
|
### Example `.prettierrc`
|
|
@@ -60,41 +60,40 @@ All options use the `ampscript` prefix — they control AMPscript formatting beh
|
|
|
60
60
|
| `.ssjs` | `babel` (built-in) | Standard JavaScript formatting |
|
|
61
61
|
| `.sql` | `sql` | SQL via composed `prettier-plugin-sql` |
|
|
62
62
|
|
|
63
|
-
## Core Prettier defaults (AMPscript, HTML, SQL)
|
|
63
|
+
## Core Prettier defaults (AMPscript, HTML, SQL, JavaScript / SSJS)
|
|
64
64
|
|
|
65
|
-
This plugin exports [Prettier `defaultOptions`](https://prettier.io/docs/plugins#defaultoptions). Prettier merges them
|
|
65
|
+
This plugin exports [Prettier `defaultOptions`](https://prettier.io/docs/plugins#defaultoptions). Prettier merges them from whichever plugin **owns the active printer** for the file being formatted. This package supplies printers for **AMPscript**, **SQL** (via composed `prettier-plugin-sql`), and the shared **`estree`** printer (the same implementation Prettier ships for JavaScript). User plugins are loaded **after** built-ins, so this plugin becomes the effective `estree` printer—meaning **`.ssjs`** files (typically `parser: "babel"`) pick up the table below **without** copying these keys into `.prettierrc`.
|
|
66
66
|
|
|
67
67
|
| Option | Default | Rationale |
|
|
68
68
|
| ------ | ------- | --------- |
|
|
69
69
|
| `useTabs` | `false` | SFMC often normalizes tabs away on save; spaces keep layout stable. |
|
|
70
70
|
| `tabWidth` | `4` | Readable indentation (override in config if you prefer 2). |
|
|
71
71
|
| `printWidth` | `100` | Fits typical editor panes better than Prettier’s 80. |
|
|
72
|
-
| `singleQuote` | `true` |
|
|
73
|
-
| `trailingComma` | `'none'` | Avoids trailing commas
|
|
72
|
+
| `singleQuote` | `true` | Common JS style; aligns with `ampscriptQuoteStyle: 'single'` where the core quote option applies. |
|
|
73
|
+
| `trailingComma` | `'none'` | Avoids trailing commas that can break SSJS in some SFMC contexts. |
|
|
74
74
|
|
|
75
75
|
String delimiters inside AMPscript blocks still follow `ampscriptQuoteStyle`. See [Prettier options](https://prettier.io/docs/options) for every standard flag.
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
**Overrides:** Add options to `.prettierrc` or [overrides](https://prettier.io/docs/configuration#configuration-overrides) only when you want to **diverge** (for example `tabWidth: 2` for the whole project, or different rules per file glob).
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```json
|
|
82
|
-
{
|
|
83
|
-
"plugins": ["prettier-plugin-sfmc"],
|
|
84
|
-
"singleQuote": true,
|
|
85
|
-
"trailingComma": "none",
|
|
86
|
-
"tabWidth": 4,
|
|
87
|
-
"printWidth": 100
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
To keep **AMPscript-only** indentation at 2 spaces while using different values for SSJS, use `overrides` keyed by file pattern.
|
|
79
|
+
**Scope:** With this plugin enabled, any file Prettier formats using the **`estree`** printer uses these defaults—not only `.ssjs`. If another plugin in your config also registers `printers.estree`, plugin **order** matters (the last one wins).
|
|
92
80
|
|
|
93
81
|
## SQL (Transact-SQL / SFMC)
|
|
94
82
|
|
|
95
83
|
`.sql` formatting is provided by [prettier-plugin-sql](https://github.com/un-ts/prettier/tree/master/packages/sql) (npm: [prettier-plugin-sql](https://www.npmjs.com/package/prettier-plugin-sql)). You only need `prettier` and `prettier-plugin-sfmc`; do not add a second entry in `plugins` for SQL.
|
|
96
84
|
|
|
97
|
-
**Defaults
|
|
85
|
+
**Defaults for SFMC-style T-SQL** (from composed [prettier-plugin-sql](https://www.npmjs.com/package/prettier-plugin-sql)):
|
|
86
|
+
|
|
87
|
+
| Option | Default | Other values | Rationale |
|
|
88
|
+
| ------ | ------- | ------------ | --------- |
|
|
89
|
+
| `language` | `tsql` | n/a | Must stay `tsql` for SFMC T-SQL. Other dialects are not supported for SFMC SQL; changing this can break formatting or behaviour. |
|
|
90
|
+
| `formatter` | `sql-formatter` | n/a | Must stay `sql-formatter` for SFMC SQL. Other formatters are not supported in this context; changing this can break. |
|
|
91
|
+
| `keywordCase` | `upper` | `preserve`, `lower` | Casing for reserved keywords. |
|
|
92
|
+
| `functionCase` | `upper` | `preserve`, `lower` | Casing for function names. |
|
|
93
|
+
| `identifierCase` | `preserve` | `upper`, `lower` | Unquoted identifiers only (upstream treats this as experimental). |
|
|
94
|
+
| `dataTypeCase` | `preserve` | `upper`, `lower` | Casing for data type names. |
|
|
95
|
+
|
|
96
|
+
Do **not** override `language` or `formatter` for SFMC. You may override the **casing** options in `.prettierrc` or under `overrides` with `files: "*.sql"` if you want different keyword/function/identifier/data-type casing.
|
|
98
97
|
|
|
99
98
|
Core layout still follows [Prettier options](https://prettier.io/docs/options). SQL-specific knobs (`expressionWidth`, `linesBetweenQueries`, etc.) are documented in the upstream package README.
|
|
100
99
|
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
# `ampscriptBlockLineBreaks`
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Optionally insert line breaks before and after `%%[ … ]%%` block expressions when they are **not** already at a line boundary in the source.
|
|
4
4
|
|
|
5
5
|
| | |
|
|
6
6
|
|---|---|
|
|
7
7
|
| **Type** | `boolean` |
|
|
8
|
-
| **Default** | `
|
|
8
|
+
| **Default** | `false` |
|
|
9
9
|
| **Applied by** | `prettier --write` · VS Code format-on-save (requires the [Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)) |
|
|
10
10
|
|
|
11
11
|
## What It Controls
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
When set to `true`, Prettier may add a **single** mandatory line break before the opening delimiter and/or after the closing delimiter so the block is not glued to other text on the same line (for example `</p>%%[` or `]%%<p>`).
|
|
14
|
+
|
|
15
|
+
Breaks are **not** added when the opening is already at the start of a logical line (beginning of file after optional UTF-8 BOM, optional spaces/tabs only before `%%[`, or immediately after `\n` / `\r\n` / `\r`), or when the closing is already at the end of a logical line (only horizontal whitespace until the next line break or end of file). That keeps formatting **idempotent** on repeated save instead of growing extra blank lines.
|
|
16
|
+
|
|
17
|
+
Inline expressions (`%%= … =%%`) are never affected.
|
|
18
|
+
|
|
19
|
+
The default is `false` so SMS and other line-break–sensitive templates are not altered unless you opt in.
|
|
14
20
|
|
|
15
21
|
## Settings
|
|
16
22
|
|
|
17
23
|
| Value | Effect |
|
|
18
24
|
|-------|--------|
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
25
|
+
| `false` (default) | No extra outer line breaks from this option; inner block layout still normalizes |
|
|
26
|
+
| `true` | Add outer line breaks only where the block is not already isolated on its own line |
|
|
21
27
|
|
|
22
|
-
### `true`
|
|
28
|
+
### `true` — inline-adjacent block
|
|
23
29
|
|
|
24
30
|
**Input:**
|
|
25
31
|
|
|
@@ -30,35 +36,26 @@ set @name = "Alice"
|
|
|
30
36
|
]%%<p>%%=v(@name)=%%</p>
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
**Output
|
|
34
|
-
|
|
35
|
-
```html
|
|
36
|
-
<p>Hello</p>
|
|
39
|
+
**Output** (illustrative; exact HTML wrapping may include normal Prettier line breaks):
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
set @name = "Alice"
|
|
41
|
-
]%%
|
|
41
|
+
- A line break is inserted so `%%[` is not on the same line as `</p>`.
|
|
42
|
+
- A line break is inserted so `]%%` is not on the same line as `<p>`.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
```
|
|
44
|
+
### `true` — already line-isolated (no extra outer breaks)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
If the source already has `%%[` at the beginning of a line (after optional BOM / spaces / tabs) and `]%%` at the end of a line (before optional spaces and `\n` or EOF), enabling this option does **not** add further outer breaks, so the document does not grow on every format.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
### `false` (default)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
<p>Hello</p>%%[
|
|
52
|
-
var @name
|
|
53
|
-
set @name = "Alice"
|
|
54
|
-
]%%<p>%%= v(@name) =%%</p>
|
|
55
|
-
```
|
|
50
|
+
**Output:** blocks stay flush with surrounding content except for whatever the HTML / AMPscript printers do for normal indentation and line wrapping.
|
|
56
51
|
|
|
57
52
|
## Configuration Example
|
|
58
53
|
|
|
54
|
+
Opt in when you want blocks separated from inline HTML only where needed:
|
|
55
|
+
|
|
59
56
|
```json
|
|
60
57
|
{
|
|
61
58
|
"plugins": ["prettier-plugin-sfmc"],
|
|
62
|
-
"ampscriptBlockLineBreaks":
|
|
59
|
+
"ampscriptBlockLineBreaks": true
|
|
63
60
|
}
|
|
64
61
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prettier-plugin-sfmc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Prettier plugin for Salesforce Marketing Cloud — AMPscript, SSJS, and SQL formatting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test": "node ./scripts/run-jest.mjs",
|
|
28
28
|
"lint": "eslint .",
|
|
29
29
|
"lint:fix": "eslint --fix .",
|
|
30
|
+
"prepare": "husky || true",
|
|
30
31
|
"format": "prettier --write .",
|
|
31
32
|
"format:check": "prettier --check ."
|
|
32
33
|
},
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"eslint-plugin-prettier": "^5.5.0",
|
|
60
61
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
61
62
|
"globals": "^17.4.0",
|
|
63
|
+
"husky": "^9.1.7",
|
|
62
64
|
"jest": "^29.0.0",
|
|
63
65
|
"prettier": "^3.8.1"
|
|
64
66
|
},
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect whether AMPscript block delimiters already sit on their own logical
|
|
3
|
+
* lines in the original source (LF, CRLF, or CR). Used to avoid Prettier
|
|
4
|
+
* hardlines that would accumulate on repeated format.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} ch
|
|
7
|
+
*/
|
|
8
|
+
function isHorizontalSpaceOrBom(ch) {
|
|
9
|
+
return ch === ' ' || ch === '\t' || ch === '\uFEFF';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* True if `index` is the first non–BOM/horizontal-ws column of a line
|
|
14
|
+
* (BOF, or only BOM/ws before index, or newline before index after skipping ws).
|
|
15
|
+
*
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @param {number} index
|
|
18
|
+
*/
|
|
19
|
+
function isIndexAtLineStart(text, index) {
|
|
20
|
+
if (index <= 0) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let i = index - 1;
|
|
25
|
+
while (i >= 0 && isHorizontalSpaceOrBom(text[i])) {
|
|
26
|
+
i--;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (i < 0) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ch = text[i];
|
|
34
|
+
return ch === '\n' || ch === '\r';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* True if everything from `index` to EOF is horizontal ws/BOM, or the first
|
|
39
|
+
* non-ws character begins a new line.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} text
|
|
42
|
+
* @param {number} index
|
|
43
|
+
*/
|
|
44
|
+
function isIndexAtLineEnd(text, index) {
|
|
45
|
+
const len = text.length;
|
|
46
|
+
let j = index;
|
|
47
|
+
|
|
48
|
+
while (j < len && isHorizontalSpaceOrBom(text[j])) {
|
|
49
|
+
j++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (j >= len) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ch = text[j];
|
|
57
|
+
return ch === '\n' || ch === '\r';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* When ampscriptBlockLineBreaks is enabled, add a leading hardline only if
|
|
62
|
+
* the block opening is not already at the start of a line in the source.
|
|
63
|
+
*
|
|
64
|
+
* @param {string | undefined} originalText
|
|
65
|
+
* @param {number | undefined} blockStart
|
|
66
|
+
*/
|
|
67
|
+
export function needsLeadingBlockBreak(originalText, blockStart) {
|
|
68
|
+
if (typeof originalText !== 'string' || blockStart == null || blockStart < 0) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return !isIndexAtLineStart(originalText, blockStart);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* When ampscriptBlockLineBreaks is enabled, add a trailing hardline only if
|
|
77
|
+
* the block closing is not already at the end of a line in the source.
|
|
78
|
+
*
|
|
79
|
+
* @param {string | undefined} originalText
|
|
80
|
+
* @param {number | undefined} blockEnd
|
|
81
|
+
*/
|
|
82
|
+
export function needsTrailingBlockBreak(originalText, blockEnd) {
|
|
83
|
+
if (typeof originalText !== 'string' || blockEnd == null || blockEnd < 0) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return !isIndexAtLineEnd(originalText, blockEnd);
|
|
88
|
+
}
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* A unified Prettier plugin for Salesforce Marketing Cloud.
|
|
5
5
|
* Handles AMPscript formatting (.ampscript, .amp, .html), SSJS file
|
|
6
6
|
* registration (.ssjs), and SQL (.sql) via composed prettier-plugin-sql.
|
|
7
|
+
* Re-exports Prettier’s `estree` printer so `defaultOptions` apply to JS/SSJS.
|
|
7
8
|
*
|
|
8
9
|
* Exports: languages, parsers, printers, options, defaultOptions
|
|
9
10
|
* Following the Prettier v3 plugin API (ESM).
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
|
|
12
13
|
import { parse } from 'ampscript-parser';
|
|
13
14
|
import sqlPlugin from 'prettier-plugin-sql';
|
|
15
|
+
import { printers as prettierEstreePrinters } from 'prettier/plugins/estree';
|
|
14
16
|
import { printAmpscriptNode, collectVariableMap } from './printer.js';
|
|
15
17
|
import * as prettier from 'prettier';
|
|
16
18
|
|
|
@@ -96,6 +98,7 @@ export const parsers = {
|
|
|
96
98
|
|
|
97
99
|
export const printers = {
|
|
98
100
|
...sqlPlugin.printers,
|
|
101
|
+
estree: prettierEstreePrinters.estree,
|
|
99
102
|
'ampscript-ast': {
|
|
100
103
|
print(path, options, print) {
|
|
101
104
|
const node = path.node;
|
|
@@ -330,11 +333,12 @@ const sfmcOptions = {
|
|
|
330
333
|
ampscriptBlockLineBreaks: {
|
|
331
334
|
type: 'boolean',
|
|
332
335
|
category: 'AMPscript',
|
|
333
|
-
default:
|
|
336
|
+
default: false,
|
|
334
337
|
description:
|
|
335
|
-
'
|
|
338
|
+
'When true, insert line breaks before and after %%[ ... ]%% blocks only when the ' +
|
|
339
|
+
'opening/closing delimiters are not already at a line boundary in the source. ' +
|
|
336
340
|
'Inline expressions (%%= ... =%%) are never affected. ' +
|
|
337
|
-
'
|
|
341
|
+
'Default is false to avoid surprising line breaks in SMS and similar contexts.',
|
|
338
342
|
},
|
|
339
343
|
ampscriptVarDeclarationStyle: {
|
|
340
344
|
type: 'choice',
|
package/src/printer.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import * as prettier from 'prettier';
|
|
10
10
|
import { FUNCTION_CANONICAL_MAP, AMPSCRIPT_KEYWORDS } from 'ampscript-data';
|
|
11
|
+
import { needsLeadingBlockBreak, needsTrailingBlockBreak } from './ampscript-block-boundaries.js';
|
|
11
12
|
|
|
12
13
|
const { group, indent, join, line, softline, hardline } = prettier.doc.builders;
|
|
13
14
|
|
|
@@ -203,7 +204,7 @@ function printAmpscriptNode(path, options, print) {
|
|
|
203
204
|
const variableStyle = options.ampscriptVarDeclarationStyle || 'multi-line';
|
|
204
205
|
const keywordCase = options.ampscriptKeywordCase || 'lower';
|
|
205
206
|
const functionCase = options.ampscriptFunctionCase || 'upper-camel';
|
|
206
|
-
const blockLineBreaks = options.ampscriptBlockLineBreaks
|
|
207
|
+
const blockLineBreaks = options.ampscriptBlockLineBreaks === true;
|
|
207
208
|
|
|
208
209
|
function resolveVariable(variableName) {
|
|
209
210
|
if (!enforceCasing) {
|
|
@@ -295,8 +296,21 @@ function printAmpscriptNode(path, options, print) {
|
|
|
295
296
|
blockClose,
|
|
296
297
|
]);
|
|
297
298
|
if (blockLineBreaks) {
|
|
298
|
-
|
|
299
|
+
const originalText = options.originalText;
|
|
300
|
+
const parts = [];
|
|
301
|
+
if (needsLeadingBlockBreak(originalText, node.start)) {
|
|
302
|
+
parts.push(hardline);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
parts.push(blockDocument);
|
|
306
|
+
|
|
307
|
+
if (needsTrailingBlockBreak(originalText, node.end)) {
|
|
308
|
+
parts.push(hardline);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
299
312
|
}
|
|
313
|
+
|
|
300
314
|
return blockDocument;
|
|
301
315
|
}
|
|
302
316
|
|