eslint-plugin-sfmc 4.0.1 → 4.1.1

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 CHANGED
@@ -73,15 +73,17 @@ Add the following to your `.vscode/settings.json`:
73
73
 
74
74
  ### Marketing Cloud Next
75
75
 
76
- Use the `-next` config variants when targeting **Marketing Cloud Next (MCN)**. MCN supports only a subset of AMPscript functions and does **not** support SSJS at all. Handlebars is MCN's templating language, so the `embedded-next` and `strict-next` configs also lint the `{{...}}` helpers and `{!$...}` bindings extracted from HTML (see [Handlebars Rules](#handlebars-rules-hbs-)).
76
+ Use the `-next` config variants when targeting **Marketing Cloud Next (MCN)**. MCN supports only a subset of AMPscript functions and does **not** support SSJS at all. Handlebars is MCN's templating language, so the `-next` configs also lint the `{{...}}` helpers and `{!$...}` bindings extracted from HTML **and** standalone `.hbs` files (see [Handlebars Rules](#handlebars-rules-hbs-)).
77
77
 
78
- | Config | Files | What it does |
79
- | ---------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
80
- | `sfmc.configs['ampscript-next']` | `**/*.ampscript`, `**/*.amp` | AMPscript rules + flags functions unsupported in MCN (single config object) |
81
- | `sfmc.configs['ssjs-next']` | `**/*.ssjs` | Flags all SSJS API calls as MCN-unsupported; all other SSJS quality rules disabled |
82
- | `sfmc.configs['recommended-next']` | Both of the above | AMPscript MCN-aware + SSJS flagged for standalone files |
83
- | `sfmc.configs['embedded-next']` | `**/*.html` | AMPscript MCN-aware + SSJS flagged + Handlebars rules for HTML-embedded code |
84
- | `sfmc.configs['strict-next']` | All of the above + HTML | All AMPscript rules at `error` severity + MCN flag; SSJS fully flagged; Handlebars rules on |
78
+ | Config | Files | What it does |
79
+ | ---------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------- |
80
+ | `sfmc.configs['ampscript-next']` | `**/*.ampscript`, `**/*.amp` | AMPscript rules + flags functions unsupported in MCN (single config object) |
81
+ | `sfmc.configs['ssjs-next']` | `**/*.ssjs` | Flags all SSJS API calls as MCN-unsupported; all other SSJS quality rules disabled |
82
+ | `sfmc.configs['recommended-next']` | Both of the above + `**/*.hbs` | AMPscript MCN-aware + SSJS flagged + Handlebars rules for standalone `.hbs` files |
83
+ | `sfmc.configs['embedded-next']` | `**/*.html`, `**/*.hbs` | AMPscript MCN-aware + SSJS flagged + Handlebars rules for HTML-embedded code and `.hbs` |
84
+ | `sfmc.configs['strict-next']` | All of the above + HTML + `**/*.hbs` | All AMPscript rules at `error` severity + MCN flag; SSJS fully flagged; Handlebars rules on |
85
+
86
+ Standalone `.hbs` files (VS Code's built-in **Handlebars** language) are treated as MCN by default — Handlebars only runs on Marketing Cloud Next, so a `.hbs` file is always linted with the full Handlebars rule set. This is deliberately wired **only** into the `-next` configs; the classic `recommended` / `strict` (Engagement) configs never lint `.hbs`.
85
87
 
86
88
  `recommended-next`, `embedded-next`, and `strict-next` are arrays — spread them with `...`.
87
89
 
@@ -143,7 +145,7 @@ export default [...sfmc.configs['recommended-next'], ...sfmc.configs['embedded-n
143
145
 
144
146
  ## Handlebars Rules (`hbs-*`)
145
147
 
146
- Handlebars is the templating language for **Marketing Cloud Next (MCN)** only. These rules are enabled at `error` severity in the `-next` configs and are `off` in the classic (Engagement) configs — in classic SFMC, `{{...}}` is plain content and must not be flagged.
148
+ Handlebars is the templating language for **Marketing Cloud Next (MCN)** only. These rules are enabled at `error` severity in the `-next` configs — applied both to `{{...}}` extracted from HTML and to standalone `.hbs` files — and are `off` in the classic (Engagement) configs — in classic SFMC, `{{...}}` is plain content and must not be flagged.
147
149
 
148
150
  | Rule | Default (`-next`) | Description |
149
151
  | --------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------ |
@@ -153,13 +155,59 @@ Handlebars is the templating language for **Marketing Cloud Next (MCN)** only. T
153
155
  | [`sfmc/hbs-no-unsupported-construct`](docs/rules/hbs/no-unsupported-construct.md) | `error` | Disallow constructs unsupported by the MCN engine (partials, decorators, `log`) |
154
156
  | [`sfmc/hbs-no-mcn-unsupported`](docs/rules/hbs/no-mcn-unsupported.md) | `error` | Flag helpers and bindings unavailable in the targeted MCN API version (`apiVersion`) |
155
157
 
158
+ ## MSO / Outlook email checks
159
+
160
+ Email HTML for SFMC frequently contains **Outlook conditional comments** (`<!--[if mso]>…<![endif]-->`), MSO-only CSS, and VML fallbacks. These checks are provided by [`eslint-plugin-mso-email`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email), which ships as a **bundled dependency** of `eslint-plugin-sfmc` — you do **not** load it separately.
161
+
162
+ The MSO rules are **auto-included** in the HTML-embedded configs: `embedded`, `strict`, `embedded-next`, and `strict-next`. When you lint an `.html` file with any of these, the combined `sfmc/sfmc` processor also extracts MSO conditional comments and the document body and runs the MSO rules on them — no extra processor or config to wire up, and no processor conflict.
163
+
164
+ | Rule (`mso/*`) | Default (embedded/strict) | Description |
165
+ | ---------------------------------- | ------------------------- | ------------------------------------------------------------- |
166
+ | [`mso/valid-mso-condition`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/valid-mso-condition.md) | `error` | Validate the `[if …]` expression syntax of MSO comments |
167
+ | [`mso/matching-mso-endif`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/matching-mso-endif.md) | `error` | Require every MSO opener to have a matching `<![endif]>` |
168
+ | [`mso/matching-mso-endif-type`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/matching-mso-endif-type.md) | `warn` | Require the endif comment style to match its opener |
169
+ | [`mso/no-unknown-mso-property`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/no-unknown-mso-property.md) | `warn` | Flag unknown `mso-*` CSS properties |
170
+ | [`mso/vml-requires-namespace`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/vml-requires-namespace.md) | `warn` | Require the `v:` VML namespace declaration when VML is used |
171
+ | [`mso/no-unknown-vml-tag`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/no-unknown-vml-tag.md) | `warn` | Flag unknown `v:*` VML tags |
172
+ | [`mso/no-unknown-vml-attribute`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/no-unknown-vml-attribute.md) | `warn` | Flag unknown attributes on VML tags |
173
+ | [`mso/table-presentation-role`](https://github.com/JoernBerkefeld/eslint-plugin-mso-email/blob/main/docs/rules/table-presentation-role.md) | `warn` | Require `role="presentation"` on layout tables |
174
+
175
+ Severities above are the same in `embedded`, `strict`, `embedded-next`, and `strict-next` (MSO markup is engine-agnostic — it behaves identically for Engagement and Next). For MSO rule details and options, see the [`eslint-plugin-mso-email` docs](https://github.com/JoernBerkefeld/eslint-plugin-mso-email).
176
+
177
+ ## Using `eslint-plugin-unicorn` with SFMC
178
+
179
+ [`eslint-plugin-unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn) is a high-quality, widely used plugin that we **strongly recommend** — but it is built for modern JavaScript, not SFMC's **SSJS** runtime. SFMC SSJS runs on a JINT-based ES3/ES5-era engine that lacks many built-ins (`Array#includes`, `String#startsWith`, `Set`, `Map`, `Object.fromEntries`, `Math.trunc`, spread `...`, ES modules, `async`/`await`, …).
180
+
181
+ If you enable unicorn's `recommended` set on SSJS, about **46** of its 300 recommended rules would either **autofix your code to a missing built-in** (breaking it at runtime) or **forbid a required SFMC workaround**. `eslint-plugin-sfmc` offers an **optional** override config that turns off exactly those 46 rules for SSJS.
182
+
183
+ **Important — this is optional and only needed if you use unicorn.** `eslint-plugin-sfmc` does **not** depend on or load unicorn. The override configs are plain rules objects with **no** `plugins` key, so they only resolve when your own unicorn config (which registers the `unicorn` plugin) is loaded **earlier** in the flat-config array. Spread the sfmc override **after** it:
184
+
185
+ ```js
186
+ import sfmc from 'eslint-plugin-sfmc';
187
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
188
+
189
+ export default [
190
+ eslintPluginUnicorn.configs.recommended, // you opt in — registers the `unicorn` plugin
191
+ ...sfmc.configs.recommended,
192
+ ...sfmc.configs['unicorn-ssjs'], // OPTIONAL: off the 46 SFMC-incompatible unicorn rules for SSJS
193
+ ...sfmc.configs['unicorn-ssjs-embedded'], // OPTIONAL: same override for SSJS embedded in HTML (<script runat="server">)
194
+ ];
195
+ ```
196
+
197
+ | Config | Files | What it does |
198
+ | ------------------------------------- | -------------------- | ----------------------------------------------------------------- |
199
+ | `sfmc.configs['unicorn-ssjs']` | `**/*.ssjs` | Turns off the 46 SFMC-incompatible unicorn rules for SSJS |
200
+ | `sfmc.configs['unicorn-ssjs-embedded']` | `**/*.html/*.js` | Same 46-rule override for SSJS embedded in HTML |
201
+
202
+ Only **46** of unicorn's 300 recommended rules are overridden — the other **254** stay active. If you don't use unicorn, omit these configs entirely. For the full rule-by-rule breakdown (with rationale and SFMC evidence links, pinned to unicorn v71.1.0), see [docs/unicorn-compatibility.md](docs/unicorn-compatibility.md).
203
+
156
204
  ## Processors
157
205
 
158
206
  | Processor | Purpose |
159
207
  | ---------------- | ------------------------------------------------------------- |
160
208
  | `sfmc/ampscript` | Extract `%%[ ]%%`, `%%= =%%`, `<script language="ampscript">` |
161
209
  | `sfmc/ssjs` | Extract `<script runat="server">` (non-ampscript) |
162
- | `sfmc/sfmc` | Combined: extracts both AMPscript and SSJS from HTML |
210
+ | `sfmc/sfmc` | Combined: extracts AMPscript, SSJS, Handlebars, and MSO from HTML |
163
211
 
164
212
  ## License
165
213
 
@@ -0,0 +1,365 @@
1
+ # `eslint-plugin-unicorn` compatibility with SFMC SSJS
2
+
3
+ > **Compatibility analysis for [`eslint-plugin-unicorn@71.1.0`](https://github.com/sindresorhus/eslint-plugin-unicorn).**
4
+ >
5
+ > ⚠️ **Version warning:** This classification was done against `eslint-plugin-unicorn` **v71.1.0** (300 recommended rules). Newer versions of unicorn may add, rename, or change rules that are **not** yet classified here. Re-audit this page whenever you upgrade unicorn — a rule added in a later version could autofix SSJS into runtime-breaking code without being in the override list below.
6
+
7
+ `eslint-plugin-unicorn` is an excellent, actively maintained plugin that we **strongly recommend** — but it targets modern JavaScript engines, not the Salesforce Marketing Cloud **SSJS** runtime. SFMC SSJS runs on a JINT-based ES3/ES5-era engine that is missing many built-ins (`Array#includes`, `String#startsWith`, `Set`, `Map`, `Object.fromEntries`, `Math.trunc`, spread `...`, ES modules, `async`/`await`, …). See the SFMC evidence pages:
8
+
9
+ - **ECMAScript built-ins support** — <https://ssjs.guide/ecmascript-builtins/>
10
+ - **Engine limitations** — <https://ssjs.guide/engine-limitations/>
11
+
12
+ Of unicorn's **300** recommended rules (v71.1.0), **254** are safe to keep on for SSJS and **46** should be turned off. `eslint-plugin-sfmc` ships an **optional** override config (`unicorn-ssjs` / `unicorn-ssjs-embedded`) that turns off exactly those 46 for SSJS files. `eslint-plugin-sfmc` does **not** depend on or load unicorn — the override only takes effect when you have loaded unicorn yourself. See [Section 1](#section-1--how-to-apply).
13
+
14
+ ---
15
+
16
+ ## Section 1 — How to apply
17
+
18
+ The override is **optional** and **SSJS-scoped**. `eslint-plugin-sfmc` never loads unicorn; the override is a plain rules object that only resolves when you have loaded your own unicorn config **earlier** in the flat-config array (that config registers the `unicorn` plugin). Spread the sfmc override **after** it:
19
+
20
+ ```js
21
+ import sfmc from 'eslint-plugin-sfmc';
22
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
23
+
24
+ export default [
25
+ eslintPluginUnicorn.configs.recommended, // you opt in — registers the `unicorn` plugin
26
+ ...sfmc.configs.recommended,
27
+ ...sfmc.configs['unicorn-ssjs'], // OPTIONAL: off the 46 SFMC-incompatible unicorn rules for SSJS
28
+ ...sfmc.configs['unicorn-ssjs-embedded'], // OPTIONAL: same override for SSJS embedded in HTML (<script runat="server">)
29
+ ];
30
+ ```
31
+
32
+ | Config | Applies to |
33
+ |---|---|
34
+ | `unicorn-ssjs` | `**/*.ssjs` |
35
+ | `unicorn-ssjs-embedded` | `**/*.html/*.js` (SSJS extracted from `<script runat="server">`) |
36
+
37
+ If you don't use unicorn, omit these configs entirely — nothing else in `eslint-plugin-sfmc` references them.
38
+
39
+ ---
40
+
41
+ ## Section 2 — Rules to override for SSJS (46)
42
+
43
+ These 46 recommended rules either **autofix code to a missing built-in**, **forbid a documented SFMC workaround**, or **enforce ES-module / async / ES6-only syntax** the engine cannot run. The override config sets each to `'off'` for SSJS. All 46 are confirmed `recommended` in unicorn v71.1.0.
44
+
45
+ ### Group A — autofix to a missing built-in / forbid a SFMC workaround (41)
46
+
47
+ | Rule | Why it breaks SSJS | SFMC evidence |
48
+ |---|---|---|
49
+ | [`prefer-includes`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-includes.md) | Pushes `Array/String#includes()` — missing in SSJS | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
50
+ | [`prefer-string-starts-ends-with`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-starts-ends-with.md) | Pushes `String#startsWith/endsWith` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
51
+ | [`prefer-string-trim-start-end`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-trim-start-end.md) | Pushes `trimStart/trimEnd` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
52
+ | [`prefer-string-slice`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-slice.md) | Forbids `substring`, the SFMC-safe choice | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
53
+ | [`prefer-string-replace-all`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-replace-all.md) | Pushes `String#replaceAll` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
54
+ | [`prefer-string-repeat`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-repeat.md) | Pushes `String#repeat` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
55
+ | [`prefer-string-pad-start-end`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-pad-start-end.md) | Pushes `padStart/padEnd` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
56
+ | [`prefer-string-match-all`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-match-all.md) | Pushes `String#matchAll` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
57
+ | [`prefer-string-raw`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-raw.md) | Pushes `String.raw` tag — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
58
+ | [`prefer-code-point`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md) | Pushes `codePointAt/fromCodePoint` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
59
+ | [`prefer-array-find`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-find.md) | Pushes `Array#find/findLast` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
60
+ | [`prefer-array-some`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-some.md) | Pushes `Array#some` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
61
+ | [`prefer-array-index-of`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-index-of.md) | Pushes `findIndex/findLastIndex` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
62
+ | [`prefer-array-flat`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat.md) | Pushes `Array#flat` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
63
+ | [`prefer-array-flat-map`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat-map.md) | Pushes `Array#flatMap` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
64
+ | [`prefer-array-last-methods`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-last-methods.md) | Pushes `.at()/findLast` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
65
+ | [`prefer-array-from-async`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-from-async.md) | Pushes `Array.fromAsync` — missing + async unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
66
+ | [`no-array-reverse`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reverse.md) | Autofixes to `Array#toReversed()` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
67
+ | [`prefer-at`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-at.md) | Pushes `.at()` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
68
+ | [`prefer-negative-index`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-negative-index.md) | Pushes `.at()` — missing; forbids the `.length - i` workaround | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
69
+ | [`prefer-spread`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-spread.md) | Pushes spread `...` — ES6 syntax, throws on ES3 | [Engine limitations](https://ssjs.guide/engine-limitations/) |
70
+ | [`prefer-date-now`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-date-now.md) | Pushes `Date.now()` — missing static | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
71
+ | [`prefer-object-from-entries`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-object-from-entries.md) | Pushes `Object.fromEntries` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
72
+ | [`prefer-reflect-apply`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-reflect-apply.md) | Pushes `Reflect.apply` — `Reflect` missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
73
+ | [`prefer-number-properties`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-number-properties.md) | Pushes `Number.parseInt/parseFloat/isNaN/isFinite` statics — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
74
+ | [`prefer-number-is-safe-integer`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-number-is-safe-integer.md) | Pushes `Number.isSafeInteger` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
75
+ | [`prefer-number-coercion`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-number-coercion.md) | Number-static patterns unsafe on the ES3 engine | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
76
+ | [`prefer-global-number-constants`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-global-number-constants.md) | Pushes `Number.NaN/Number.POSITIVE_INFINITY` etc. — missing statics | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
77
+ | [`prefer-native-coercion-functions`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-native-coercion-functions.md) | Native coercion refs that are missing/unsafe | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
78
+ | [`prefer-math-trunc`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-trunc.md) | Pushes `Math.trunc` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
79
+ | [`prefer-modern-math-apis`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-math-apis.md) | Pushes `Math.log10/log2/hypot` — missing; forbids `Math.log(x)/Math.LN10` and `Math.sqrt(a*a+b*b)` workarounds | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
80
+ | [`no-instanceof-builtins`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-instanceof-builtins.md) | Autofixes `instanceof Array` to `Array.isArray` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
81
+ | [`no-for-each`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-each.md) | Assumes `.forEach` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
82
+ | [`prefer-set-has`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-has.md) | Pushes `Set` — ES6, unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
83
+ | [`prefer-set-size`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-size.md) | Pushes `Set` — ES6, unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
84
+ | [`prefer-set-methods`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-methods.md) | Pushes `Set` methods — ES2024, unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
85
+ | [`prefer-map-from-entries`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-map-from-entries.md) | Pushes `Map` + `Object.fromEntries` — unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
86
+ | [`prefer-group-by`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-group-by.md) | Pushes `Object.groupBy/Map.groupBy` — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
87
+ | [`prefer-iterator-helpers`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-iterator-helpers.md) | Pushes the iterator-helper protocol — unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
88
+ | [`prefer-structured-clone`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-structured-clone.md) | Pushes `structuredClone` global — missing | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
89
+ | [`require-array-join-separator`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-array-join-separator.md) | Safe alone, but grouped here to avoid pushing modern array idioms under the strict set | [ECMAScript built-ins](https://ssjs.guide/ecmascript-builtins/) |
90
+
91
+ ### Group B — forbid ES6+ syntax / ES-module / async constructs (5)
92
+
93
+ | Rule | Why it breaks SSJS | SFMC evidence |
94
+ |---|---|---|
95
+ | [`prefer-optional-catch-binding`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-optional-catch-binding.md) | Pushes `catch {}` (ES2019) — may not parse on ES3 | [Engine limitations](https://ssjs.guide/engine-limitations/) |
96
+ | [`prefer-module`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-module.md) | Pushes ES modules — unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
97
+ | [`prefer-node-protocol`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md) | Pushes `node:` imports — no module system | [Engine limitations](https://ssjs.guide/engine-limitations/) |
98
+ | [`prefer-top-level-await`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-top-level-await.md) | Pushes top-level `await` — async unsupported | [Engine limitations](https://ssjs.guide/engine-limitations/) |
99
+ | [`prefer-export-from`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-export-from.md) | Pushes `export … from` — ES modules | [Engine limitations](https://ssjs.guide/engine-limitations/) |
100
+
101
+ > **Note:** [`prefer-regexp-test`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md) stays **active** — `RegExp#test` exists in SFMC SSJS, so it is intentionally **not** in the override list.
102
+
103
+ ---
104
+
105
+ ## Section 3 — Rules OK as-is (254)
106
+
107
+ These recommended rules are **not** disabled by the override config. They are either genuinely SFMC-safe (readability / best-practice rules that work on the ES3/ES5 engine) or **inert** on SSJS — many target the DOM, Node.js, Promises, TypeScript, or ES modules and therefore never fire on server-side SFMC code. Each links to its official unicorn documentation.
108
+
109
+ <!-- BEGIN 254-OK-LIST -->
110
+ - [better-dom-traversing](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/better-dom-traversing.md)
111
+ - [catch-error-name](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/catch-error-name.md)
112
+ - [class-reference-in-static-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/class-reference-in-static-methods.md)
113
+ - [consistent-assert](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-assert.md)
114
+ - [consistent-boolean-name](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-boolean-name.md)
115
+ - [consistent-class-member-order](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-class-member-order.md)
116
+ - [consistent-compound-words](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-compound-words.md)
117
+ - [consistent-conditional-object-spread](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-conditional-object-spread.md)
118
+ - [consistent-date-clone](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-date-clone.md)
119
+ - [consistent-empty-array-spread](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-empty-array-spread.md)
120
+ - [consistent-existence-index-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-existence-index-check.md)
121
+ - [consistent-export-decorator-position](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-export-decorator-position.md)
122
+ - [consistent-function-scoping](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md)
123
+ - [consistent-json-file-read](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-json-file-read.md)
124
+ - [consistent-optional-chaining](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-optional-chaining.md)
125
+ - [consistent-template-literal-escape](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-template-literal-escape.md)
126
+ - [consistent-tuple-labels](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-tuple-labels.md)
127
+ - [default-export-style](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/default-export-style.md)
128
+ - [dom-node-dataset](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/dom-node-dataset.md)
129
+ - [empty-brace-spaces](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/empty-brace-spaces.md)
130
+ - [error-message](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/error-message.md)
131
+ - [escape-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/escape-case.md)
132
+ - [expiring-todo-comments](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md)
133
+ - [explicit-length-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/explicit-length-check.md)
134
+ - [explicit-timer-delay](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/explicit-timer-delay.md)
135
+ - [filename-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md)
136
+ - [import-style](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/import-style.md)
137
+ - [isolated-functions](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/isolated-functions.md)
138
+ - [logical-assignment-operators](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/logical-assignment-operators.md)
139
+ - [max-nested-calls](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/max-nested-calls.md)
140
+ - [name-replacements](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/name-replacements.md)
141
+ - [new-for-builtins](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/new-for-builtins.md)
142
+ - [no-abusive-eslint-disable](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-abusive-eslint-disable.md)
143
+ - [no-accessor-recursion](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-accessor-recursion.md)
144
+ - [no-accidental-bitwise-operator](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-accidental-bitwise-operator.md)
145
+ - [no-anonymous-default-export](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-anonymous-default-export.md)
146
+ - [no-array-callback-reference](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-callback-reference.md)
147
+ - [no-array-concat-in-loop](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-concat-in-loop.md)
148
+ - [no-array-fill-with-reference-type](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-fill-with-reference-type.md)
149
+ - [no-array-from-fill](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-from-fill.md)
150
+ - [no-array-method-this-argument](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-method-this-argument.md)
151
+ - [no-array-reduce](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md)
152
+ - [no-array-sort](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-sort.md)
153
+ - [no-array-sort-for-min-max](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-sort-for-min-max.md)
154
+ - [no-array-splice](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-splice.md)
155
+ - [no-async-promise-finally](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-async-promise-finally.md)
156
+ - [no-await-expression-member](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-await-expression-member.md)
157
+ - [no-await-in-promise-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-await-in-promise-methods.md)
158
+ - [no-blob-to-file](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-blob-to-file.md)
159
+ - [no-boolean-sort-comparator](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-boolean-sort-comparator.md)
160
+ - [no-break-in-nested-loop](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-break-in-nested-loop.md)
161
+ - [no-canvas-to-image](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-canvas-to-image.md)
162
+ - [no-chained-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-chained-comparison.md)
163
+ - [no-collection-bracket-access](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-collection-bracket-access.md)
164
+ - [no-computed-property-existence-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-computed-property-existence-check.md)
165
+ - [no-confusing-array-splice](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-confusing-array-splice.md)
166
+ - [no-confusing-array-with](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-confusing-array-with.md)
167
+ - [no-console-spaces](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-console-spaces.md)
168
+ - [no-constant-zero-expression](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-constant-zero-expression.md)
169
+ - [no-declarations-before-early-exit](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-declarations-before-early-exit.md)
170
+ - [no-document-cookie](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-document-cookie.md)
171
+ - [no-double-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-double-comparison.md)
172
+ - [no-duplicate-if-branches](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-duplicate-if-branches.md)
173
+ - [no-duplicate-logical-operands](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-duplicate-logical-operands.md)
174
+ - [no-duplicate-loops](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-duplicate-loops.md)
175
+ - [no-duplicate-set-values](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-duplicate-set-values.md)
176
+ - [no-empty-file](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-empty-file.md)
177
+ - [no-error-property-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-error-property-assignment.md)
178
+ - [no-exports-in-scripts](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-exports-in-scripts.md)
179
+ - [no-for-loop](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md)
180
+ - [no-global-object-property-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-global-object-property-assignment.md)
181
+ - [no-immediate-mutation](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-immediate-mutation.md)
182
+ - [no-impossible-length-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-impossible-length-comparison.md)
183
+ - [no-incorrect-query-selector](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-incorrect-query-selector.md)
184
+ - [no-incorrect-template-string-interpolation](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-incorrect-template-string-interpolation.md)
185
+ - [no-invalid-argument-count](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-invalid-argument-count.md)
186
+ - [no-invalid-character-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-invalid-character-comparison.md)
187
+ - [no-invalid-fetch-options](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-invalid-fetch-options.md)
188
+ - [no-invalid-remove-event-listener](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-invalid-remove-event-listener.md)
189
+ - [no-invalid-well-known-symbol-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-invalid-well-known-symbol-methods.md)
190
+ - [no-late-current-target-access](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-late-current-target-access.md)
191
+ - [no-late-event-control](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-late-event-control.md)
192
+ - [no-lonely-if](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-lonely-if.md)
193
+ - [no-loop-iterable-mutation](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-loop-iterable-mutation.md)
194
+ - [no-magic-array-flat-depth](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-magic-array-flat-depth.md)
195
+ - [no-mismatched-map-key](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-mismatched-map-key.md)
196
+ - [no-misrefactored-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-misrefactored-assignment.md)
197
+ - [no-named-default](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-named-default.md)
198
+ - [no-negated-array-predicate](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negated-array-predicate.md)
199
+ - [no-negated-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negated-comparison.md)
200
+ - [no-negated-condition](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negated-condition.md)
201
+ - [no-negation-in-equality-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negation-in-equality-check.md)
202
+ - [no-nested-ternary](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-nested-ternary.md)
203
+ - [no-new-array](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-new-array.md)
204
+ - [no-new-buffer](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-new-buffer.md)
205
+ - [no-non-function-verb-prefix](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-non-function-verb-prefix.md)
206
+ - [no-nonstandard-builtin-properties](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-nonstandard-builtin-properties.md)
207
+ - [no-null](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-null.md)
208
+ - [no-object-as-default-parameter](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-object-as-default-parameter.md)
209
+ - [no-object-methods-with-collections](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-object-methods-with-collections.md)
210
+ - [no-optional-chaining-on-undeclared-variable](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-optional-chaining-on-undeclared-variable.md)
211
+ - [no-process-exit](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-process-exit.md)
212
+ - [no-redundant-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-redundant-comparison.md)
213
+ - [no-return-array-push](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-return-array-push.md)
214
+ - [no-selector-as-dom-name](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-selector-as-dom-name.md)
215
+ - [no-single-promise-in-promise-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-single-promise-in-promise-methods.md)
216
+ - [no-static-only-class](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-static-only-class.md)
217
+ - [no-subtraction-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-subtraction-comparison.md)
218
+ - [no-thenable](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-thenable.md)
219
+ - [no-this-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-this-assignment.md)
220
+ - [no-this-outside-of-class](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-this-outside-of-class.md)
221
+ - [no-top-level-assignment-in-function](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-top-level-assignment-in-function.md)
222
+ - [no-top-level-side-effects](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-top-level-side-effects.md)
223
+ - [no-typeof-undefined](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-typeof-undefined.md)
224
+ - [no-uncalled-method](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-uncalled-method.md)
225
+ - [no-undeclared-class-members](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-undeclared-class-members.md)
226
+ - [no-unnecessary-array-flat-depth](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-array-flat-depth.md)
227
+ - [no-unnecessary-array-flat-map](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-array-flat-map.md)
228
+ - [no-unnecessary-array-splice-count](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-array-splice-count.md)
229
+ - [no-unnecessary-await](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-await.md)
230
+ - [no-unnecessary-boolean-comparison](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-boolean-comparison.md)
231
+ - [no-unnecessary-fetch-options](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-fetch-options.md)
232
+ - [no-unnecessary-global-this](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-global-this.md)
233
+ - [no-unnecessary-nested-ternary](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-nested-ternary.md)
234
+ - [no-unnecessary-polyfills](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-polyfills.md)
235
+ - [no-unnecessary-slice-end](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-slice-end.md)
236
+ - [no-unnecessary-splice](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-splice.md)
237
+ - [no-unnecessary-string-trim](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-string-trim.md)
238
+ - [no-unreadable-array-destructuring](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unreadable-array-destructuring.md)
239
+ - [no-unreadable-for-of-expression](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unreadable-for-of-expression.md)
240
+ - [no-unreadable-iife](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unreadable-iife.md)
241
+ - [no-unreadable-object-destructuring](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unreadable-object-destructuring.md)
242
+ - [no-unsafe-buffer-conversion](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unsafe-buffer-conversion.md)
243
+ - [no-unsafe-promise-all-settled-values](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unsafe-promise-all-settled-values.md)
244
+ - [no-unsafe-property-key](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unsafe-property-key.md)
245
+ - [no-unsafe-string-replacement](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unsafe-string-replacement.md)
246
+ - [no-unused-array-method-return](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unused-array-method-return.md)
247
+ - [no-useless-boolean-cast](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-boolean-cast.md)
248
+ - [no-useless-coercion](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-coercion.md)
249
+ - [no-useless-collection-argument](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-collection-argument.md)
250
+ - [no-useless-compound-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-compound-assignment.md)
251
+ - [no-useless-concat](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-concat.md)
252
+ - [no-useless-continue](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-continue.md)
253
+ - [no-useless-delete-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-delete-check.md)
254
+ - [no-useless-else](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-else.md)
255
+ - [no-useless-error-capture-stack-trace](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-error-capture-stack-trace.md)
256
+ - [no-useless-fallback-in-spread](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-fallback-in-spread.md)
257
+ - [no-useless-iterator-to-array](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-iterator-to-array.md)
258
+ - [no-useless-length-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-length-check.md)
259
+ - [no-useless-logical-operand](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-logical-operand.md)
260
+ - [no-useless-override](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-override.md)
261
+ - [no-useless-promise-resolve-reject](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md)
262
+ - [no-useless-recursion](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-recursion.md)
263
+ - [no-useless-spread](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-spread.md)
264
+ - [no-useless-switch-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-switch-case.md)
265
+ - [no-useless-template-literals](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-template-literals.md)
266
+ - [no-useless-undefined](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-undefined.md)
267
+ - [no-xor-as-exponentiation](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-xor-as-exponentiation.md)
268
+ - [no-zero-fractions](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-zero-fractions.md)
269
+ - [number-literal-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/number-literal-case.md)
270
+ - [numeric-separators-style](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/numeric-separators-style.md)
271
+ - [operator-assignment](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/operator-assignment.md)
272
+ - [prefer-abort-signal-any](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-abort-signal-any.md)
273
+ - [prefer-abort-signal-timeout](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-abort-signal-timeout.md)
274
+ - [prefer-add-event-listener](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-add-event-listener.md)
275
+ - [prefer-add-event-listener-options](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-add-event-listener-options.md)
276
+ - [prefer-aggregate-error](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-aggregate-error.md)
277
+ - [prefer-array-from-map](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-from-map.md)
278
+ - [prefer-array-from-range](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-from-range.md)
279
+ - [prefer-array-iterable-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-iterable-methods.md)
280
+ - [prefer-array-slice](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-slice.md)
281
+ - [prefer-await](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-await.md)
282
+ - [prefer-bigint-literals](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-bigint-literals.md)
283
+ - [prefer-blob-reading-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-blob-reading-methods.md)
284
+ - [prefer-block-statement-over-iife](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-block-statement-over-iife.md)
285
+ - [prefer-boolean-return](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-boolean-return.md)
286
+ - [prefer-class-fields](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-class-fields.md)
287
+ - [prefer-classlist-toggle](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-classlist-toggle.md)
288
+ - [prefer-continue](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-continue.md)
289
+ - [prefer-default-parameters](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-default-parameters.md)
290
+ - [prefer-direct-iteration](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-direct-iteration.md)
291
+ - [prefer-dom-node-append](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-append.md)
292
+ - [prefer-dom-node-remove](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-remove.md)
293
+ - [prefer-dom-node-replace-children](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-replace-children.md)
294
+ - [prefer-dom-node-text-content](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-text-content.md)
295
+ - [prefer-early-return](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-early-return.md)
296
+ - [prefer-else-if](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-else-if.md)
297
+ - [prefer-event-target](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-event-target.md)
298
+ - [prefer-flat-math-min-max](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-flat-math-min-max.md)
299
+ - [prefer-get-or-insert-computed](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-get-or-insert-computed.md)
300
+ - [prefer-global-this](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-global-this.md)
301
+ - [prefer-has-check](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-has-check.md)
302
+ - [prefer-hoisting-branch-code](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-hoisting-branch-code.md)
303
+ - [prefer-https](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-https.md)
304
+ - [prefer-identifier-import-export-specifiers](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-identifier-import-export-specifiers.md)
305
+ - [prefer-includes-over-repeated-comparisons](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-includes-over-repeated-comparisons.md)
306
+ - [prefer-iterable-in-constructor](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-iterable-in-constructor.md)
307
+ - [prefer-iterator-to-array](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-iterator-to-array.md)
308
+ - [prefer-iterator-to-array-at-end](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-iterator-to-array-at-end.md)
309
+ - [prefer-keyboard-event-key](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-keyboard-event-key.md)
310
+ - [prefer-location-assign](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-location-assign.md)
311
+ - [prefer-logical-operator-over-ternary](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-logical-operator-over-ternary.md)
312
+ - [prefer-math-abs](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-abs.md)
313
+ - [prefer-math-constants](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-constants.md)
314
+ - [prefer-math-min-max](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-min-max.md)
315
+ - [prefer-minimal-ternary](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-minimal-ternary.md)
316
+ - [prefer-modern-dom-apis](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md)
317
+ - [prefer-object-define-properties](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-object-define-properties.md)
318
+ - [prefer-object-destructuring-defaults](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-object-destructuring-defaults.md)
319
+ - [prefer-object-iterable-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-object-iterable-methods.md)
320
+ - [prefer-observer-apis](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-observer-apis.md)
321
+ - [prefer-path2d](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-path2d.md)
322
+ - [prefer-private-class-fields](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-private-class-fields.md)
323
+ - [prefer-promise-try](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-promise-try.md)
324
+ - [prefer-promise-with-resolvers](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-promise-with-resolvers.md)
325
+ - [prefer-prototype-methods](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-prototype-methods.md)
326
+ - [prefer-query-selector](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-query-selector.md)
327
+ - [prefer-queue-microtask](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-queue-microtask.md)
328
+ - [prefer-regexp-test](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md)
329
+ - [prefer-response-static-json](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-response-static-json.md)
330
+ - [prefer-scoped-selector](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-scoped-selector.md)
331
+ - [prefer-simple-condition-first](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-simple-condition-first.md)
332
+ - [prefer-simple-sort-comparator](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-simple-sort-comparator.md)
333
+ - [prefer-simplified-conditions](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-simplified-conditions.md)
334
+ - [prefer-single-array-predicate](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-array-predicate.md)
335
+ - [prefer-single-call](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-call.md)
336
+ - [prefer-single-object-destructuring](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-object-destructuring.md)
337
+ - [prefer-single-replace](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-replace.md)
338
+ - [prefer-smaller-scope](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-smaller-scope.md)
339
+ - [prefer-split-limit](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-split-limit.md)
340
+ - [prefer-switch](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-switch.md)
341
+ - [prefer-ternary](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-ternary.md)
342
+ - [prefer-toggle-attribute](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-toggle-attribute.md)
343
+ - [prefer-type-error](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-type-error.md)
344
+ - [prefer-type-literal-last](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-type-literal-last.md)
345
+ - [prefer-unary-minus](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-unary-minus.md)
346
+ - [prefer-unicode-code-point-escapes](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-unicode-code-point-escapes.md)
347
+ - [prefer-url-can-parse](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-url-can-parse.md)
348
+ - [prefer-url-href](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-url-href.md)
349
+ - [prefer-url-search-parameters](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-url-search-parameters.md)
350
+ - [prefer-while-loop-condition](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-while-loop-condition.md)
351
+ - [relative-url-style](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/relative-url-style.md)
352
+ - [require-array-sort-compare](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-array-sort-compare.md)
353
+ - [require-css-escape](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-css-escape.md)
354
+ - [require-module-attributes](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-module-attributes.md)
355
+ - [require-module-specifiers](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-module-specifiers.md)
356
+ - [require-number-to-fixed-digits-argument](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-number-to-fixed-digits-argument.md)
357
+ - [require-passive-events](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-passive-events.md)
358
+ - [require-proxy-trap-boolean-return](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/require-proxy-trap-boolean-return.md)
359
+ - [switch-case-braces](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/switch-case-braces.md)
360
+ - [switch-case-break-position](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/switch-case-break-position.md)
361
+ - [template-indent](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/template-indent.md)
362
+ - [text-encoding-identifier-case](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/text-encoding-identifier-case.md)
363
+ - [throw-new-error](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/throw-new-error.md)
364
+ <!-- END 254-OK-LIST -->
365
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-sfmc",
3
- "version": "4.0.1",
3
+ "version": "4.1.1",
4
4
  "description": "ESLint plugin for Salesforce Marketing Cloud Engagement+Next - AMPscript, Server-Side JavaScript (SSJS) and Handlebars",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,15 +27,21 @@
27
27
  "eslintplugin",
28
28
  "ampscript",
29
29
  "ssjs",
30
+ "handlebars",
31
+ "hbs",
30
32
  "salesforce",
31
33
  "marketing-cloud",
32
- "sfmc"
34
+ "sfmc",
35
+ "marketing-cloud-next",
36
+ "mcn"
33
37
  ],
34
38
  "dependencies": {
35
39
  "@handlebars/parser": "^2.2.2",
36
40
  "ampscript-data": "^3.0.0",
37
41
  "ampscript-parser": "^0.2.0",
42
+ "eslint-plugin-mso-email": "^2.0.0",
38
43
  "handlebars-data": "^0.3.0",
44
+ "mso-conditional-parser": "^2.0.1",
39
45
  "ssjs-data": "^0.14.0"
40
46
  },
41
47
  "peerDependencies": {
package/src/index.js CHANGED
@@ -71,6 +71,16 @@ import ampscriptProcessor from './ampscript-processor.js';
71
71
  import ssjsProcessor from './ssjs-processor.js';
72
72
  import combinedProcessor from './processor.js';
73
73
 
74
+ // ── MSO / Outlook email plugin (auto-included in embedded/strict configs) ──────
75
+
76
+ import msoPlugin from 'eslint-plugin-mso-email';
77
+ import msoEslintParser from 'eslint-plugin-mso-email/mso-parser';
78
+ import htmlEslintParser from 'eslint-plugin-mso-email/html-parser';
79
+ import {
80
+ MSO_VIRTUAL_BASENAME,
81
+ DOCUMENT_VIRTUAL_BASENAME,
82
+ } from 'eslint-plugin-mso-email/comment-pattern';
83
+
74
84
  // ── Data imports ──────────────────────────────────────────────────────────────
75
85
  import { SSJS_GLOBALS_MAP } from 'ssjs-data';
76
86
 
@@ -291,6 +301,143 @@ const hbsOffRules = {
291
301
  /** Shared languageOptions for linting virtual `.hbs` files. */
292
302
  const hbsLanguageOptions = { parser: handlebarsParser };
293
303
 
304
+ /**
305
+ * Config block(s) for **standalone** `.hbs` files (a real Handlebars file on
306
+ * disk, opened under VS Code's built-in `handlebars` language id). Handlebars
307
+ * only runs on Marketing Cloud Next, so a `.hbs` file is always treated as MCN
308
+ * and the full `hbsNextRules` set applies — no processor is needed because the
309
+ * whole file is Handlebars and is parsed directly. This is deliberately wired
310
+ * only into the `-next` configs; `recommended`/`strict` (MCE) never lint `.hbs`.
311
+ *
312
+ * @param {string} configName - the owning config, used for a unique block name
313
+ * @returns {object[]} a single-entry array with the standalone `.hbs` config
314
+ */
315
+ function standaloneHandlebarsConfigs(configName) {
316
+ return [
317
+ {
318
+ name: `sfmc/${configName}-standalone-handlebars`,
319
+ plugins: { sfmc: plugin },
320
+ languageOptions: { ...hbsLanguageOptions },
321
+ files: ['**/*.hbs'],
322
+ rules: { ...hbsNextRules },
323
+ },
324
+ ];
325
+ }
326
+
327
+ // ── Optional eslint-plugin-unicorn override for SSJS ──────────────────────────
328
+
329
+ /**
330
+ * The 46 `eslint-plugin-unicorn` recommended rules (analysed against
331
+ * unicorn v71.1.0) that are incompatible with the SFMC SSJS (JINT / ES3-ES5)
332
+ * engine — they either autofix code to a missing built-in or forbid a required
333
+ * SFMC workaround, or enforce ES-module / async / ES6-only syntax that the
334
+ * engine cannot run. Each is mapped to `'off'`.
335
+ *
336
+ * This is exposed via the optional `unicorn-ssjs` / `unicorn-ssjs-embedded`
337
+ * configs. eslint-plugin-sfmc does NOT depend on or load unicorn; these configs
338
+ * are plain rules objects (no `plugins` key) and only resolve when the user has
339
+ * loaded their own unicorn config earlier in the flat-config array. See
340
+ * docs/unicorn-compatibility.md for the full rationale and evidence links.
341
+ */
342
+ const unicornSsjsOffRules = {
343
+ // Group A — autofix to a missing built-in / forbid a documented SFMC workaround
344
+ 'unicorn/prefer-includes': 'off',
345
+ 'unicorn/prefer-string-starts-ends-with': 'off',
346
+ 'unicorn/prefer-string-trim-start-end': 'off',
347
+ 'unicorn/prefer-string-slice': 'off',
348
+ 'unicorn/prefer-string-replace-all': 'off',
349
+ 'unicorn/prefer-string-repeat': 'off',
350
+ 'unicorn/prefer-string-pad-start-end': 'off',
351
+ 'unicorn/prefer-string-match-all': 'off',
352
+ 'unicorn/prefer-string-raw': 'off',
353
+ 'unicorn/prefer-code-point': 'off',
354
+ 'unicorn/prefer-array-find': 'off',
355
+ 'unicorn/prefer-array-some': 'off',
356
+ 'unicorn/prefer-array-index-of': 'off',
357
+ 'unicorn/prefer-array-flat': 'off',
358
+ 'unicorn/prefer-array-flat-map': 'off',
359
+ 'unicorn/prefer-array-last-methods': 'off',
360
+ 'unicorn/prefer-array-from-async': 'off',
361
+ 'unicorn/no-array-reverse': 'off',
362
+ 'unicorn/prefer-at': 'off',
363
+ 'unicorn/prefer-negative-index': 'off',
364
+ 'unicorn/prefer-spread': 'off',
365
+ 'unicorn/prefer-date-now': 'off',
366
+ 'unicorn/prefer-object-from-entries': 'off',
367
+ 'unicorn/prefer-reflect-apply': 'off',
368
+ 'unicorn/prefer-number-properties': 'off',
369
+ 'unicorn/prefer-number-is-safe-integer': 'off',
370
+ 'unicorn/prefer-number-coercion': 'off',
371
+ 'unicorn/prefer-global-number-constants': 'off',
372
+ 'unicorn/prefer-native-coercion-functions': 'off',
373
+ 'unicorn/prefer-math-trunc': 'off',
374
+ 'unicorn/prefer-modern-math-apis': 'off',
375
+ 'unicorn/no-instanceof-builtins': 'off',
376
+ 'unicorn/no-for-each': 'off',
377
+ 'unicorn/prefer-set-has': 'off',
378
+ 'unicorn/prefer-set-size': 'off',
379
+ 'unicorn/prefer-set-methods': 'off',
380
+ 'unicorn/prefer-map-from-entries': 'off',
381
+ 'unicorn/prefer-group-by': 'off',
382
+ 'unicorn/prefer-iterator-helpers': 'off',
383
+ 'unicorn/prefer-structured-clone': 'off',
384
+ 'unicorn/require-array-join-separator': 'off',
385
+ // Group B — forbid ES6+ syntax / ES-module / async constructs
386
+ 'unicorn/prefer-optional-catch-binding': 'off',
387
+ 'unicorn/prefer-module': 'off',
388
+ 'unicorn/prefer-node-protocol': 'off',
389
+ 'unicorn/prefer-top-level-await': 'off',
390
+ 'unicorn/prefer-export-from': 'off',
391
+ };
392
+
393
+ // ── MSO / Outlook email rule configs (auto-included, no separate plugin load) ──
394
+
395
+ /**
396
+ * Virtual-file globs for the MSO blocks the combined processor appends.
397
+ * sfmc's processor only runs on `**\/*.html`, so MSO blocks are emitted as
398
+ * `email.html/{index}_mso-comments.mso` and `email.html/{index}_document.msohtml`.
399
+ */
400
+ const msoVirtualFiles = [`**/*.html/*_${MSO_VIRTUAL_BASENAME}`];
401
+ const msoDocumentFiles = [`**/*.html/*_${DOCUMENT_VIRTUAL_BASENAME}`];
402
+
403
+ /**
404
+ * Returns MSO's two rule-config entries (conditional-comment rules on `.mso`
405
+ * virtual files, document rules on `.msohtml` virtual files) wired to the MSO
406
+ * parsers. The `mso/html-processor` entry from eslint-plugin-mso-email is
407
+ * intentionally omitted — sfmc's own combined processor already emits the MSO
408
+ * blocks, so registering MSO's processor too would create a processor conflict.
409
+ *
410
+ * @returns {import('eslint').Linter.Config[]} MSO conditional + document rule configs.
411
+ */
412
+ function msoRuleConfigs() {
413
+ return [
414
+ {
415
+ name: 'sfmc/mso-conditional-rules',
416
+ plugins: { mso: msoPlugin },
417
+ files: msoVirtualFiles,
418
+ languageOptions: { parser: msoEslintParser },
419
+ rules: {
420
+ 'mso/valid-mso-condition': 'error',
421
+ 'mso/matching-mso-endif': 'error',
422
+ 'mso/matching-mso-endif-type': 'warn',
423
+ },
424
+ },
425
+ {
426
+ name: 'sfmc/mso-document-rules',
427
+ plugins: { mso: msoPlugin },
428
+ files: msoDocumentFiles,
429
+ languageOptions: { parser: htmlEslintParser },
430
+ rules: {
431
+ 'mso/no-unknown-mso-property': 'warn',
432
+ 'mso/vml-requires-namespace': 'warn',
433
+ 'mso/no-unknown-vml-tag': 'warn',
434
+ 'mso/no-unknown-vml-attribute': 'warn',
435
+ 'mso/table-presentation-role': 'warn',
436
+ },
437
+ },
438
+ ];
439
+ }
440
+
294
441
  // ── Configs (defined after plugin so they can reference it) ───────────────────
295
442
 
296
443
  plugin.configs = {
@@ -384,6 +531,8 @@ plugin.configs = {
384
531
  files: ['**/*.html/*.hbs'],
385
532
  rules: { ...hbsOffRules },
386
533
  },
534
+ // MSO / Outlook conditional-comment + VML/CSS checks (auto-included).
535
+ ...msoRuleConfigs(),
387
536
  ],
388
537
 
389
538
  /**
@@ -440,6 +589,8 @@ plugin.configs = {
440
589
  files: ['**/*.html/*.hbs'],
441
590
  rules: { ...hbsOffRules },
442
591
  },
592
+ // MSO / Outlook conditional-comment + VML/CSS checks (auto-included).
593
+ ...msoRuleConfigs(),
443
594
  ],
444
595
 
445
596
  // ── Marketing Cloud Next configs ──────────────────────────────────────────
@@ -500,6 +651,8 @@ plugin.configs = {
500
651
  },
501
652
  rules: { ...ssjsMcnRules },
502
653
  },
654
+ // Standalone .hbs files are Handlebars templates — always MCN.
655
+ ...standaloneHandlebarsConfigs('recommended-next'),
503
656
  ],
504
657
 
505
658
  /**
@@ -541,6 +694,10 @@ plugin.configs = {
541
694
  files: ['**/*.html/*.hbs'],
542
695
  rules: { ...hbsNextRules },
543
696
  },
697
+ // Standalone .hbs files are Handlebars templates — always MCN.
698
+ ...standaloneHandlebarsConfigs('embedded-next'),
699
+ // MSO / Outlook conditional-comment + VML/CSS checks (auto-included).
700
+ ...msoRuleConfigs(),
544
701
  ],
545
702
 
546
703
  /**
@@ -603,7 +760,37 @@ plugin.configs = {
603
760
  files: ['**/*.html/*.hbs'],
604
761
  rules: { ...hbsNextRules },
605
762
  },
763
+ // Standalone .hbs files are Handlebars templates — always MCN.
764
+ ...standaloneHandlebarsConfigs('strict-next'),
765
+ // MSO / Outlook conditional-comment + VML/CSS checks (auto-included).
766
+ ...msoRuleConfigs(),
606
767
  ],
768
+
769
+ // ── Optional eslint-plugin-unicorn override configs ───────────────────────
770
+
771
+ /**
772
+ * OPTIONAL: turns off the 46 unicorn recommended rules incompatible with
773
+ * SFMC SSJS, for standalone `.ssjs` files. Plain rules object with NO
774
+ * `plugins` key — eslint-plugin-sfmc does not load unicorn. Spread this
775
+ * AFTER your own unicorn config (which registers the `unicorn` plugin);
776
+ * otherwise ESLint cannot resolve the `unicorn/*` keys. If you don't use
777
+ * unicorn, omit this config entirely.
778
+ */
779
+ 'unicorn-ssjs': {
780
+ name: 'sfmc/unicorn-ssjs',
781
+ files: ['**/*.ssjs'],
782
+ rules: { ...unicornSsjsOffRules },
783
+ },
784
+
785
+ /**
786
+ * OPTIONAL: same 46-rule override for SSJS embedded in HTML
787
+ * (virtual `**\/*.html/*.js` files). Spread AFTER your unicorn config.
788
+ */
789
+ 'unicorn-ssjs-embedded': {
790
+ name: 'sfmc/unicorn-ssjs-embedded',
791
+ files: ['**/*.html/*.js'],
792
+ rules: { ...unicornSsjsOffRules },
793
+ },
607
794
  };
608
795
 
609
796
  export default plugin;
package/src/processor.js CHANGED
@@ -13,8 +13,16 @@
13
13
  * Returns extracted regions as virtual files for ESLint to lint with the
14
14
  * appropriate parser/rules based on file extension matching.
15
15
  * Line offsets are preserved so ESLint reports errors at correct locations.
16
+ *
17
+ * MSO / Outlook conditional-comment checks are delegated to
18
+ * eslint-plugin-mso-email: when the document contains MSO markup, that plugin's
19
+ * extractor is called and its virtual blocks are appended verbatim, so a single
20
+ * processor (this one) runs on every HTML file and no processor conflict arises.
16
21
  */
17
22
 
23
+ import { preprocess as msoPreprocess } from 'eslint-plugin-mso-email/processor';
24
+ import { MSO_COMMENT_PATTERN } from 'eslint-plugin-mso-email/comment-pattern';
25
+
18
26
  const AMPSCRIPT_SCRIPT_OPEN_RE =
19
27
  /^<script\b(?=[^>]*\brunat\s*=\s*['"]server['"])(?=[^>]*\blanguage\s*=\s*['"]ampscript['"])[^>]*>/i;
20
28
  const SSJS_SCRIPT_OPEN_RE =
@@ -201,6 +209,18 @@ export function preprocess(text, filename) {
201
209
  });
202
210
  }
203
211
 
212
+ // --- Pass 4: delegate MSO / Outlook conditional-comment extraction ---
213
+ // Only when the document actually contains MSO markup, so plain HTML/AMPscript
214
+ // files don't get an extra full-document virtual block. MSO_COMMENT_PATTERN is
215
+ // a global regex with lastIndex state — reset before testing.
216
+ MSO_COMMENT_PATTERN.lastIndex = 0;
217
+ if (MSO_COMMENT_PATTERN.test(text)) {
218
+ // MSO already preserves line offsets and returns bare virtual basenames
219
+ // (mso-comments.mso, document.msohtml); append verbatim so ESLint routes
220
+ // them to eslint-plugin-mso-email's rule configs via their own globs.
221
+ blocks.push(...msoPreprocess(text, filename));
222
+ }
223
+
204
224
  if (blocks.length === 0) {
205
225
  return [text];
206
226
  }