eslint-plugin-sfmc 0.1.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/LICENSE +21 -0
- package/README.md +100 -0
- package/docs/rules/amp/function-arity.md +53 -0
- package/docs/rules/amp/naming-convention.md +76 -0
- package/docs/rules/amp/no-deprecated-function.md +78 -0
- package/docs/rules/amp/no-email-excluded-function.md +69 -0
- package/docs/rules/amp/no-empty-block.md +59 -0
- package/docs/rules/amp/no-empty-then.md +62 -0
- package/docs/rules/amp/no-html-comment.md +67 -0
- package/docs/rules/amp/no-inline-statement.md +51 -0
- package/docs/rules/amp/no-js-line-comment.md +54 -0
- package/docs/rules/amp/no-loop-counter-assign.md +52 -0
- package/docs/rules/amp/no-nested-ampscript-delimiter.md +69 -0
- package/docs/rules/amp/no-nested-script-tag.md +58 -0
- package/docs/rules/amp/no-smart-quotes.md +60 -0
- package/docs/rules/amp/no-unknown-function.md +50 -0
- package/docs/rules/amp/no-var-redeclaration.md +52 -0
- package/docs/rules/amp/prefer-attribute-value.md +64 -0
- package/docs/rules/amp/require-rowcount-check.md +62 -0
- package/docs/rules/amp/require-variable-declaration.md +51 -0
- package/docs/rules/amp/set-requires-target.md +47 -0
- package/docs/rules/ssjs/cache-loop-length.md +59 -0
- package/docs/rules/ssjs/no-hardcoded-credentials.md +52 -0
- package/docs/rules/ssjs/no-switch-default.md +37 -0
- package/docs/rules/ssjs/no-treatascontent-injection.md +38 -0
- package/docs/rules/ssjs/no-unavailable-method.md +134 -0
- package/docs/rules/ssjs/no-unknown-core-method.md +46 -0
- package/docs/rules/ssjs/no-unknown-http-method.md +44 -0
- package/docs/rules/ssjs/no-unknown-platform-client-browser.md +44 -0
- package/docs/rules/ssjs/no-unknown-platform-function.md +44 -0
- package/docs/rules/ssjs/no-unknown-platform-request.md +43 -0
- package/docs/rules/ssjs/no-unknown-platform-response.md +43 -0
- package/docs/rules/ssjs/no-unknown-platform-variable.md +43 -0
- package/docs/rules/ssjs/no-unknown-wsproxy-method.md +46 -0
- package/docs/rules/ssjs/no-unsupported-syntax.md +83 -0
- package/docs/rules/ssjs/platform-function-arity.md +45 -0
- package/docs/rules/ssjs/prefer-parsejson-safe-arg.md +42 -0
- package/docs/rules/ssjs/prefer-platform-load-version.md +68 -0
- package/docs/rules/ssjs/require-hasownproperty.md +57 -0
- package/docs/rules/ssjs/require-platform-load-order.md +46 -0
- package/docs/rules/ssjs/require-platform-load.md +57 -0
- package/package.json +52 -0
- package/src/ampscript-parser.js +159 -0
- package/src/ampscript-processor.js +132 -0
- package/src/index.js +355 -0
- package/src/processor.js +147 -0
- package/src/rules/amp/function-arity.js +51 -0
- package/src/rules/amp/naming-convention.js +58 -0
- package/src/rules/amp/no-deprecated-function.js +75 -0
- package/src/rules/amp/no-email-excluded-function.js +72 -0
- package/src/rules/amp/no-empty-block.js +37 -0
- package/src/rules/amp/no-empty-then.js +34 -0
- package/src/rules/amp/no-html-comment.js +57 -0
- package/src/rules/amp/no-inline-statement.js +45 -0
- package/src/rules/amp/no-js-line-comment.js +54 -0
- package/src/rules/amp/no-loop-counter-assign.js +71 -0
- package/src/rules/amp/no-nested-ampscript-delimiter.js +102 -0
- package/src/rules/amp/no-nested-script-tag.js +65 -0
- package/src/rules/amp/no-smart-quotes.js +78 -0
- package/src/rules/amp/no-unknown-function.js +31 -0
- package/src/rules/amp/no-var-redeclaration.js +36 -0
- package/src/rules/amp/prefer-attribute-value.js +94 -0
- package/src/rules/amp/require-rowcount-check.js +116 -0
- package/src/rules/amp/require-variable-declaration.js +49 -0
- package/src/rules/amp/set-requires-target.js +27 -0
- package/src/rules/ssjs/cache-loop-length.js +92 -0
- package/src/rules/ssjs/no-hardcoded-credentials.js +66 -0
- package/src/rules/ssjs/no-switch-default.js +32 -0
- package/src/rules/ssjs/no-treatascontent-injection.js +76 -0
- package/src/rules/ssjs/no-unavailable-method.js +190 -0
- package/src/rules/ssjs/no-unknown-core-method.js +103 -0
- package/src/rules/ssjs/no-unknown-http-method.js +41 -0
- package/src/rules/ssjs/no-unknown-platform-client-browser.js +50 -0
- package/src/rules/ssjs/no-unknown-platform-function.js +49 -0
- package/src/rules/ssjs/no-unknown-platform-request.js +50 -0
- package/src/rules/ssjs/no-unknown-platform-response.js +50 -0
- package/src/rules/ssjs/no-unknown-platform-variable.js +50 -0
- package/src/rules/ssjs/no-unknown-wsproxy-method.js +71 -0
- package/src/rules/ssjs/no-unsupported-syntax.js +122 -0
- package/src/rules/ssjs/platform-function-arity.js +70 -0
- package/src/rules/ssjs/prefer-parsejson-safe-arg.js +94 -0
- package/src/rules/ssjs/prefer-platform-load-version.js +96 -0
- package/src/rules/ssjs/require-hasownproperty.js +142 -0
- package/src/rules/ssjs/require-platform-load-order.js +112 -0
- package/src/rules/ssjs/require-platform-load.js +124 -0
- package/src/ssjs-processor.js +48 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# `sfmc/ssjs-no-unsupported-syntax`
|
|
2
|
+
|
|
3
|
+
> Disallow ES6+ syntax features not supported by the SFMC SSJS engine.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `problem` |
|
|
8
|
+
| **Default severity** | `error` in `recommended` and `strict` |
|
|
9
|
+
| **Fixable** | **Auto-fix** for `let`/`const`; **Suggestion** for `??` |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
SFMC executes Server-Side JavaScript using JINT, a .NET-based engine that implements ECMAScript 3 with some ES5 additions. Modern JavaScript syntax introduced in ES6 and later — including `let`, `const`, template literals, arrow functions, destructuring, classes, `async/await`, optional chaining, and nullish coalescing — is not available and causes runtime errors. Code that works in a browser or Node.js will silently fail or produce cryptic errors in SFMC.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"error"` |
|
|
20
|
+
| `allow` | Array of feature names to suppress | `[]` |
|
|
21
|
+
|
|
22
|
+
#### Known feature names for `allow`
|
|
23
|
+
|
|
24
|
+
`LetDeclaration`, `ConstDeclaration`, `ArrowFunction`, `TemplateLiteral`, `DestructuringAssignment`, `SpreadOperator`, `ClassDeclaration`, `AsyncFunction`, `OptionalChaining`, `NullishCoalescing`, `ForOfStatement`, `GeneratorFunction`, `Symbol`, `Proxy`, `WeakMap`, `WeakSet`, `BigInt`
|
|
25
|
+
|
|
26
|
+
### Default behavior (no `allow`)
|
|
27
|
+
|
|
28
|
+
**Not allowed:**
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const greeting = `Hello, ${name}`;
|
|
32
|
+
let count = 0;
|
|
33
|
+
const double = (x) => x * 2;
|
|
34
|
+
const value = obj?.prop ?? "default";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Allowed:**
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
var greeting = "Hello, " + name;
|
|
41
|
+
var count = 0;
|
|
42
|
+
function double(x) { return x * 2; }
|
|
43
|
+
var value = (obj && obj.prop !== null && obj.prop !== undefined) ? obj.prop : "default";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### With `allow: ["LetDeclaration", "ConstDeclaration"]`
|
|
47
|
+
|
|
48
|
+
`let` and `const` are no longer flagged, but all other unsupported syntax is still reported.
|
|
49
|
+
|
|
50
|
+
## Fix
|
|
51
|
+
|
|
52
|
+
**Auto-fix** for `let` and `const` declarations. Applied by:
|
|
53
|
+
|
|
54
|
+
- `eslint --fix` on the command line
|
|
55
|
+
- **Fix this issue** / **Fix all auto-fixable problems** in VS Code (requires the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint))
|
|
56
|
+
- On save via `editor.codeActionsOnSave: { "source.fixAll.eslint": "explicit" }`
|
|
57
|
+
|
|
58
|
+
What the auto-fix does: replaces the `let` or `const` keyword with `var`, leaving the rest of the declaration unchanged.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
**Suggestion** for nullish coalescing (`??`). Applied by:
|
|
63
|
+
|
|
64
|
+
- Click the **lightbulb** / press `Ctrl+.` on the flagged code in VS Code (requires the ESLint extension)
|
|
65
|
+
- `eslint --fix` does **not** apply suggestions (`--fix-type suggestion` filters fixable rules by rule category, it does **not** apply `hasSuggestions` suggestions)
|
|
66
|
+
|
|
67
|
+
What the suggestion does: replaces `??` with `||`. Note that `||` has different semantics — it also treats `0`, `""`, and `false` as falsy, unlike `??` which only replaces `null` and `undefined`.
|
|
68
|
+
|
|
69
|
+
## Configuration Example
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// eslint.config.js
|
|
73
|
+
rules: {
|
|
74
|
+
'sfmc/ssjs-no-unsupported-syntax': ['error', { allow: ['LetDeclaration'] }]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## When to Disable
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// eslint.config.js
|
|
82
|
+
rules: { 'sfmc/ssjs-no-unsupported-syntax': 'off' }
|
|
83
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# `sfmc/ssjs-platform-function-arity`
|
|
2
|
+
|
|
3
|
+
> Enforce correct argument counts for `Platform.Function.*` calls.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `problem` |
|
|
8
|
+
| **Default severity** | `error` in `recommended` and `strict` |
|
|
9
|
+
| **Fixable** | — |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
`Platform.Function` methods have documented required and optional argument counts. Passing too few arguments causes a runtime error; passing too many may silently truncate or fail. This rule validates every `Platform.Function.*` call against the known min/max argument counts.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"error"` |
|
|
20
|
+
|
|
21
|
+
This rule has no configuration options.
|
|
22
|
+
|
|
23
|
+
### Examples
|
|
24
|
+
|
|
25
|
+
**Not allowed:**
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
Platform.Load("core", "1.1.5");
|
|
29
|
+
/* Lookup requires at least 4 arguments */
|
|
30
|
+
var val = Platform.Function.Lookup("MyDE", "Value");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Allowed:**
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
Platform.Load("core", "1.1.5");
|
|
37
|
+
var val = Platform.Function.Lookup("MyDE", "Value", "Key", keyValue);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## When to Disable
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// eslint.config.js
|
|
44
|
+
rules: { 'sfmc/ssjs-platform-function-arity': 'off' }
|
|
45
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ssjs-prefer-parsejson-safe-arg
|
|
2
|
+
|
|
3
|
+
Require concatenating an empty string to `Platform.Function.ParseJSON()` arguments.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
`Platform.Function.ParseJSON()` throws a 500 error at runtime if its argument is `undefined` or not a string. This commonly happens when the value comes from a lookup, request parameter, or variable that may be empty.
|
|
8
|
+
|
|
9
|
+
Concatenating an empty string (`+ ''`) is a widely-used SFMC pattern that safely coerces the argument to a string:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// Unsafe -- throws 500 if someVar is undefined
|
|
13
|
+
Platform.Function.ParseJSON(someVar);
|
|
14
|
+
|
|
15
|
+
// Safe
|
|
16
|
+
Platform.Function.ParseJSON(someVar + '');
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Rule details
|
|
20
|
+
|
|
21
|
+
This rule flags any call to `ParseJSON` (or `Platform.Function.ParseJSON`) where the first argument is not already concatenated with an empty string literal.
|
|
22
|
+
|
|
23
|
+
**Auto-fix:** The rule wraps the argument with `+ ''`.
|
|
24
|
+
|
|
25
|
+
### Valid
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
Platform.Function.ParseJSON(someVar + '');
|
|
29
|
+
Platform.Function.ParseJSON('' + someVar);
|
|
30
|
+
Platform.Function.ParseJSON('{"key": "value"}');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Invalid
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
Platform.Function.ParseJSON(someVar);
|
|
37
|
+
Platform.Function.ParseJSON(getData());
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Settings
|
|
41
|
+
|
|
42
|
+
This rule has no options.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# `sfmc/ssjs-prefer-platform-load-version`
|
|
2
|
+
|
|
3
|
+
> Enforce a minimum version string in `Platform.Load()` calls.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `suggestion` |
|
|
8
|
+
| **Default severity** | `warn` in `recommended`; `error` in `strict` (not in `recommended` table — enabled in `strict`) |
|
|
9
|
+
| **Fixable** | **Auto-fix** (`eslint --fix`) |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
`Platform.Load("core", "1")` or `"1.1.1"` loads an older version of the Core library that is missing bug fixes present in `"1.1.5"`. Using the latest stable version is documented as best practice in Mateusz Dąbrowski's SSJS style guide. This rule flags any version string other than the configured target and also flags calls where the version argument is missing entirely.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"warn"` |
|
|
20
|
+
| `version` | Any version string | `"1.1.5"` |
|
|
21
|
+
|
|
22
|
+
### Default behavior (enforce `"1.1.5"`)
|
|
23
|
+
|
|
24
|
+
**Not allowed:**
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
Platform.Load("core", "1");
|
|
28
|
+
Platform.Load("core", "1.1.1");
|
|
29
|
+
Platform.Load("core"); /* missing version argument */
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Allowed:**
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
Platform.Load("core", "1.1.5");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### With `version: "1.1.6"` (custom minimum)
|
|
39
|
+
|
|
40
|
+
Flags anything that is not `"1.1.6"`, including `"1.1.5"`.
|
|
41
|
+
|
|
42
|
+
## Fix
|
|
43
|
+
|
|
44
|
+
This rule provides an **auto-fix**. Applied by:
|
|
45
|
+
|
|
46
|
+
- `eslint --fix` on the command line (because `meta.type` is `"suggestion"`, you can also target it specifically with `--fix-type suggestion`)
|
|
47
|
+
- **Fix this issue** / **Fix all auto-fixable problems** in VS Code (requires the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint))
|
|
48
|
+
- On save via `editor.codeActionsOnSave: { "source.fixAll.eslint": "explicit" }`
|
|
49
|
+
|
|
50
|
+
What the fix does:
|
|
51
|
+
- When the version argument is present but wrong: replaces the version string literal with the required version.
|
|
52
|
+
- When the version argument is missing entirely: inserts `, "1.1.5"` after the `"core"` argument.
|
|
53
|
+
|
|
54
|
+
## Configuration Example
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
// eslint.config.js
|
|
58
|
+
rules: {
|
|
59
|
+
'sfmc/ssjs-prefer-platform-load-version': ['warn', { version: '1.1.5' }]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## When to Disable
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// eslint.config.js
|
|
67
|
+
rules: { 'sfmc/ssjs-prefer-platform-load-version': 'off' }
|
|
68
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# `sfmc/ssjs-require-hasownproperty`
|
|
2
|
+
|
|
3
|
+
> Require a `hasOwnProperty` guard in `for…in` loops.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `suggestion` |
|
|
8
|
+
| **Default severity** | `warn` in `recommended`; `error` in `strict` |
|
|
9
|
+
| **Fixable** | **Suggestion** (manual, via VS Code lightbulb) |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
In SFMC's SSJS runtime, objects frequently carry inherited properties such as `_type` that are visible in `for…in` iterations. Without a `hasOwnProperty` guard, these prototype properties are processed alongside the object's own data, leading to unexpected behaviour or errors. This is an amplified version of the general JavaScript best practice that applies specifically to SFMC because Core library objects are particularly prone to having non-enumerable inherited properties exposed.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"warn"` |
|
|
20
|
+
|
|
21
|
+
This rule has no configuration options.
|
|
22
|
+
|
|
23
|
+
### Examples
|
|
24
|
+
|
|
25
|
+
**Not allowed:**
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
for (var key in myObject) {
|
|
29
|
+
write(key + ": " + myObject[key]);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Allowed:**
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
for (var key in myObject) {
|
|
37
|
+
if (myObject.hasOwnProperty(key)) {
|
|
38
|
+
write(key + ": " + myObject[key]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Fix
|
|
44
|
+
|
|
45
|
+
This rule provides a **suggestion** (not applied automatically). To apply it:
|
|
46
|
+
|
|
47
|
+
- Click the **lightbulb** / press `Ctrl+.` on the flagged code in VS Code (requires the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint))
|
|
48
|
+
- `eslint --fix` does **not** apply suggestions (`--fix-type suggestion` filters fixable rules by rule category, it does **not** apply `hasSuggestions` suggestions)
|
|
49
|
+
|
|
50
|
+
What the suggestion does: wraps the entire loop body in an `if (obj.hasOwnProperty(key)) { … }` guard. When the body is already a block statement, the existing content is preserved inside the new `if`; when it is a single statement, a new block is created around it.
|
|
51
|
+
|
|
52
|
+
## When to Disable
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// eslint.config.js
|
|
56
|
+
rules: { 'sfmc/ssjs-require-hasownproperty': 'off' }
|
|
57
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `sfmc/ssjs-require-platform-load-order`
|
|
2
|
+
|
|
3
|
+
> Require `Platform.Load()` to appear before Core library usage in source order.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `problem` |
|
|
8
|
+
| **Default severity** | `error` in `recommended` and `strict` |
|
|
9
|
+
| **Fixable** | — |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
Even when `Platform.Load("core", "1.1.5")` is present in the file, placing it *after* the first Core library call still causes a runtime crash. SFMC executes the file top-to-bottom; a `DataExtension.Init()` call on line 1 runs before the `Platform.Load` on line 10 has had a chance to initialise the Core library. This rule is complementary to [`ssjs-require-platform-load`](require-platform-load.md) — it specifically checks that the call appears in the correct position.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"error"` |
|
|
20
|
+
|
|
21
|
+
This rule has no configuration options.
|
|
22
|
+
|
|
23
|
+
### Examples
|
|
24
|
+
|
|
25
|
+
**Not allowed:**
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
var de = DataExtension.Init("MyDE"); /* Core used before Platform.Load */
|
|
29
|
+
Platform.Load("core", "1.1.5");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Allowed:**
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
Platform.Load("core", "1.1.5");
|
|
36
|
+
var de = DataExtension.Init("MyDE");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## When to Disable
|
|
40
|
+
|
|
41
|
+
Only disable if you have a deliberate top-of-file initialisation pattern that reorders statements at runtime.
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
// eslint.config.js
|
|
45
|
+
rules: { 'sfmc/ssjs-require-platform-load-order': 'off' }
|
|
46
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# `sfmc/ssjs-require-platform-load`
|
|
2
|
+
|
|
3
|
+
> Require `Platform.Load("core", ...)` before using Core library objects.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Type** | `problem` |
|
|
8
|
+
| **Default severity** | `error` in `recommended` and `strict` |
|
|
9
|
+
| **Fixable** | **Auto-fix** (`eslint --fix`) |
|
|
10
|
+
|
|
11
|
+
## Why This Rule Exists
|
|
12
|
+
|
|
13
|
+
SFMC's SSJS runtime requires `Platform.Load("core", "1.1.5")` to be called before any Core library object (`DataExtension`, `Subscriber`, `Email`, `SFMC`, etc.) is used. Without it, calls to `DataExtension.Init()` and similar methods crash at runtime with an object-not-found error. The Platform.Load call initialises the Core library and makes its classes available.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
| Setting | Values | Default |
|
|
18
|
+
|---------|--------|---------|
|
|
19
|
+
| severity | `"error"` \| `"warn"` \| `"off"` | `"error"` |
|
|
20
|
+
|
|
21
|
+
This rule has no configuration options.
|
|
22
|
+
|
|
23
|
+
### Examples
|
|
24
|
+
|
|
25
|
+
**Not allowed:**
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
var de = DataExtension.Init("MyDE");
|
|
29
|
+
var rows = de.Rows.Retrieve();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Allowed:**
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
Platform.Load("core", "1.1.5");
|
|
36
|
+
var de = DataExtension.Init("MyDE");
|
|
37
|
+
var rows = de.Rows.Retrieve();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Fix
|
|
41
|
+
|
|
42
|
+
This rule provides an **auto-fix**. Applied by:
|
|
43
|
+
|
|
44
|
+
- `eslint --fix` on the command line (applies ALL fixable rules regardless of `meta.type`; use `--fix-type problem` to target only problem-type rules)
|
|
45
|
+
- **Fix this issue** / **Fix all auto-fixable problems** in VS Code (requires the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint))
|
|
46
|
+
- On save via `editor.codeActionsOnSave: { "source.fixAll.eslint": "explicit" }`
|
|
47
|
+
|
|
48
|
+
What the fix does: inserts `Platform.Load("core", "1.1.5");` at the very beginning of the file. The fix is attached to the first reported violation only, so it is inserted exactly once even when multiple Core objects are used.
|
|
49
|
+
|
|
50
|
+
## When to Disable
|
|
51
|
+
|
|
52
|
+
Only disable this rule if you are intentionally writing SSJS that does not use Core library objects.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// eslint.config.js
|
|
56
|
+
rules: { 'sfmc/ssjs-require-platform-load': 'off' }
|
|
57
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-sfmc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ESLint plugin for Salesforce Marketing Cloud — AMPscript and Server-Side JavaScript (SSJS)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./parser": "./src/ampscript-parser.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"docs",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/JoernBerkefeld/eslint-plugin-sfmc.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/JoernBerkefeld/eslint-plugin-sfmc/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/JoernBerkefeld/eslint-plugin-sfmc#readme",
|
|
24
|
+
"author": "Joern Berkefeld",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"eslint",
|
|
27
|
+
"eslintplugin",
|
|
28
|
+
"ampscript",
|
|
29
|
+
"ssjs",
|
|
30
|
+
"salesforce",
|
|
31
|
+
"marketing-cloud",
|
|
32
|
+
"sfmc"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"ampscript-data": "^0.1.1",
|
|
36
|
+
"ampscript-parser": "^0.1.0",
|
|
37
|
+
"ssjs-data": "^0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"eslint": ">=9.0.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "node --test tests/rules.test.js"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"eslint": "^10.1.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-compatible parser for AMPscript.
|
|
3
|
+
*
|
|
4
|
+
* Reuses the shared ampscript-parser package for tokenization and statement
|
|
5
|
+
* parsing, then wraps the resulting AST with ESLint-required metadata:
|
|
6
|
+
* - `range: [start, end]` on every node
|
|
7
|
+
* - `loc: { start: {line,column}, end: {line,column} }` on every node
|
|
8
|
+
* - Root node typed as "Program" with `body`, `tokens`, `comments`
|
|
9
|
+
* - `visitorKeys` so ESLint knows how to traverse custom node types
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parse as prettierParse } from 'ampscript-parser';
|
|
13
|
+
|
|
14
|
+
// ── Visitor keys for all AMPscript AST node types ─────────────────────────
|
|
15
|
+
//
|
|
16
|
+
// Node types that collide with ESTree names (IfStatement, ForStatement,
|
|
17
|
+
// ExpressionStatement) are prefixed with "Amp" so ESLint's built-in
|
|
18
|
+
// JavaScript code-path analysis does not try to process them.
|
|
19
|
+
|
|
20
|
+
const NODE_RENAME = {
|
|
21
|
+
IfStatement: 'AmpIfStatement',
|
|
22
|
+
ForStatement: 'AmpForStatement',
|
|
23
|
+
ExpressionStatement: 'AmpExpressionStatement',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const visitorKeys = {
|
|
27
|
+
Program: ['body'],
|
|
28
|
+
Content: [],
|
|
29
|
+
Block: ['statements'],
|
|
30
|
+
InlineExpression: ['expression'],
|
|
31
|
+
Comment: [],
|
|
32
|
+
VarDeclaration: ['variables'],
|
|
33
|
+
SetStatement: ['target', 'value'],
|
|
34
|
+
AmpIfStatement: ['condition', 'consequent', 'alternates'],
|
|
35
|
+
ElseIfClause: ['condition', 'body'],
|
|
36
|
+
ElseClause: ['body'],
|
|
37
|
+
AmpForStatement: ['counter', 'startExpr', 'endExpr', 'body'],
|
|
38
|
+
AmpExpressionStatement: ['expression'],
|
|
39
|
+
FunctionCall: ['arguments'],
|
|
40
|
+
Variable: [],
|
|
41
|
+
Identifier: [],
|
|
42
|
+
StringLiteral: [],
|
|
43
|
+
NumberLiteral: [],
|
|
44
|
+
BooleanLiteral: [],
|
|
45
|
+
BinaryExpression: ['left', 'right'],
|
|
46
|
+
UnaryExpression: ['argument'],
|
|
47
|
+
ParenExpression: ['expression'],
|
|
48
|
+
RawStatement: [],
|
|
49
|
+
Raw: [],
|
|
50
|
+
Empty: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ── Offset → line/column mapping ──────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function buildLineTable(text) {
|
|
56
|
+
const starts = [0];
|
|
57
|
+
for (let index = 0; index < text.length; index++) {
|
|
58
|
+
if (text[index] === '\n') {
|
|
59
|
+
starts.push(index + 1);
|
|
60
|
+
} else if (text[index] === '\r') {
|
|
61
|
+
if (text[index + 1] === '\n') {
|
|
62
|
+
starts.push(index + 2);
|
|
63
|
+
index++;
|
|
64
|
+
} else {
|
|
65
|
+
starts.push(index + 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return starts;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function offsetToLoc(offset, lineStarts) {
|
|
73
|
+
let lo = 0;
|
|
74
|
+
let hi = lineStarts.length - 1;
|
|
75
|
+
while (lo < hi) {
|
|
76
|
+
const mid = (lo + hi + 1) >> 1;
|
|
77
|
+
if (lineStarts[mid] <= offset) lo = mid;
|
|
78
|
+
else hi = mid - 1;
|
|
79
|
+
}
|
|
80
|
+
return { line: lo + 1, column: offset - lineStarts[lo] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── AST annotation ────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function annotateNode(node, lineStarts, comments) {
|
|
86
|
+
if (!node || typeof node !== 'object') return;
|
|
87
|
+
|
|
88
|
+
// Rename colliding ESTree types to Amp-prefixed equivalents
|
|
89
|
+
if (node.type && NODE_RENAME[node.type]) {
|
|
90
|
+
node.type = NODE_RENAME[node.type];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof node.start === 'number' && typeof node.end === 'number') {
|
|
94
|
+
node.range = [node.start, node.end];
|
|
95
|
+
node.loc = {
|
|
96
|
+
start: offsetToLoc(node.start, lineStarts),
|
|
97
|
+
end: offsetToLoc(node.end, lineStarts),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (node.type === 'Comment') {
|
|
102
|
+
comments.push(node);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const keys = visitorKeys[node.type];
|
|
106
|
+
if (!keys) return;
|
|
107
|
+
|
|
108
|
+
for (const key of keys) {
|
|
109
|
+
const child = node[key];
|
|
110
|
+
if (Array.isArray(child)) {
|
|
111
|
+
for (const item of child) {
|
|
112
|
+
if (item && typeof item === 'object') {
|
|
113
|
+
annotateNode(item, lineStarts, comments);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else if (child && typeof child === 'object') {
|
|
117
|
+
annotateNode(child, lineStarts, comments);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Public ESLint parser interface ────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export function parseForESLint(code) {
|
|
125
|
+
const prettierAst = prettierParse(code);
|
|
126
|
+
const lineStarts = buildLineTable(code);
|
|
127
|
+
const comments = [];
|
|
128
|
+
|
|
129
|
+
const ast = {
|
|
130
|
+
type: 'Program',
|
|
131
|
+
body: prettierAst.children || [],
|
|
132
|
+
start: 0,
|
|
133
|
+
end: code.length,
|
|
134
|
+
range: [0, code.length],
|
|
135
|
+
loc: {
|
|
136
|
+
start: { line: 1, column: 0 },
|
|
137
|
+
end: offsetToLoc(code.length, lineStarts),
|
|
138
|
+
},
|
|
139
|
+
tokens: [],
|
|
140
|
+
comments: [],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
for (const child of ast.body) {
|
|
144
|
+
annotateNode(child, lineStarts, comments);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ast.comments = comments;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
ast,
|
|
151
|
+
visitorKeys,
|
|
152
|
+
services: {},
|
|
153
|
+
scopeManager: null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function parse(code) {
|
|
158
|
+
return parseForESLint(code).ast;
|
|
159
|
+
}
|