eslint-plugin-sfmc 4.3.0 → 4.4.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 +2 -1
- package/docs/rules/ssjs/no-deprecated-function.md +4 -2
- package/docs/rules/ssjs/no-nonexistent-global.md +37 -0
- package/package.json +2 -2
- package/src/index.js +5 -0
- package/src/rules/ssjs/no-deprecated-function.js +29 -1
- package/src/rules/ssjs/no-nonexistent-global.js +67 -0
package/README.md
CHANGED
|
@@ -128,7 +128,8 @@ export default [...sfmc.configs['recommended-next'], ...sfmc.configs['embedded-n
|
|
|
128
128
|
| [`sfmc/ssjs-no-unsupported-syntax`](docs/rules/ssjs/no-unsupported-syntax.md) | `error` | Flag ES6+ syntax not supported by SFMC |
|
|
129
129
|
| [`sfmc/ssjs-no-unknown-function`](docs/rules/ssjs/no-unknown-function.md) | `error` | Disallow unknown methods on Platform.\*, HTTP, Core Library, and WSProxy |
|
|
130
130
|
| [`sfmc/ssjs-no-mcn-unsupported`](docs/rules/ssjs/no-mcn-unsupported.md) | off (`error` in `-next`) | Flag all SSJS API usage as unsupported in Marketing Cloud Next |
|
|
131
|
-
| [`sfmc/ssjs-no-deprecated-function`](docs/rules/ssjs/no-deprecated-function.md) | `error` | Flag use of deprecated SFMC SSJS APIs (e.g. ContentArea,
|
|
131
|
+
| [`sfmc/ssjs-no-deprecated-function`](docs/rules/ssjs/no-deprecated-function.md) | `error` | Flag use of deprecated SFMC SSJS APIs (e.g. ContentArea, ErrorUtil) |
|
|
132
|
+
| [`sfmc/ssjs-no-nonexistent-global`](docs/rules/ssjs/no-nonexistent-global.md) | `error` | Flag documented SSJS globals that throw ReferenceError at runtime |
|
|
132
133
|
| [`sfmc/ssjs-no-property-call`](docs/rules/ssjs/no-property-call.md) | `error` | Disallow calling Platform.Request/Response properties as functions |
|
|
133
134
|
| [`sfmc/ssjs-no-clr-header-access`](docs/rules/ssjs/no-clr-header-access.md) | `error` | Disallow CLR-unsafe reads of `HttpResponse.headers`; read via `for..in` |
|
|
134
135
|
| [`sfmc/ssjs-require-string-clr-content`](docs/rules/ssjs/require-string-clr-content.md) | `error` | Require wrapping `HttpResponse.content` with `String()` before use |
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# `sfmc/ssjs-no-deprecated-function`
|
|
2
2
|
|
|
3
|
-
Flags calls to deprecated SFMC SSJS APIs
|
|
4
|
-
which has been retired and no longer allows creating or updating content
|
|
3
|
+
Flags calls to deprecated SFMC SSJS APIs, chiefly the **Content Areas** feature,
|
|
4
|
+
which has been retired and no longer allows creating or updating content, plus the
|
|
5
|
+
legacy `ErrorUtil` helper that only exists under the oldest Core version.
|
|
5
6
|
|
|
6
7
|
## What is flagged
|
|
7
8
|
|
|
@@ -16,6 +17,7 @@ which has been retired and no longer allows creating or updating content.
|
|
|
16
17
|
| `ContentAreaObj.Retrieve(…)` | ContentAreaObj class is deprecated |
|
|
17
18
|
| `<contentAreaVar>.Update(…)` | Instance method on a deprecated ContentAreaObj variable |
|
|
18
19
|
| `<contentAreaVar>.Remove()` | Instance method on a deprecated ContentAreaObj variable |
|
|
20
|
+
| `ErrorUtil.ThrowWSProxyError(…)` | Only exists under `Platform.Load("Core", "1")`; undefined in newer Core versions — check `result.Status` and `throw new Error(…)` instead |
|
|
19
21
|
|
|
20
22
|
## Examples
|
|
21
23
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# `sfmc/ssjs-no-nonexistent-global`
|
|
2
|
+
|
|
3
|
+
Flags bare-name SSJS globals that are officially documented but proven **not to exist
|
|
4
|
+
at runtime** — calling them throws a `ReferenceError`. Unlike deprecated APIs (which
|
|
5
|
+
still run), these never work.
|
|
6
|
+
|
|
7
|
+
## What is flagged
|
|
8
|
+
|
|
9
|
+
| API | Reason |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `Redirect(url, movedPermanently)` | Not injected by `Platform.Load` under any Core version; calling it throws a `ReferenceError`. Use `Platform.Response.Redirect(url, movedPermanently)` instead. |
|
|
12
|
+
|
|
13
|
+
The list is driven by `ssjs-data`'s `notDefinedAtRuntime` flag, so new phantom globals
|
|
14
|
+
are picked up automatically without editing this rule.
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
### ❌ Incorrect
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// Bare-name Redirect does not exist at runtime — throws ReferenceError
|
|
22
|
+
Redirect('https://example.com', false);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### ✅ Correct
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// Platform.Response.Redirect is always available in CloudPages
|
|
29
|
+
Platform.Response.Redirect('https://example.com', false);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Rule details
|
|
33
|
+
|
|
34
|
+
- **Type:** `problem`
|
|
35
|
+
- **Fixable:** No
|
|
36
|
+
- **Recommended:** Yes
|
|
37
|
+
- **Strict:** Yes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-sfmc",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"eslint-plugin-mso-email": "^2.0.0",
|
|
43
43
|
"handlebars-data": "^0.3.0",
|
|
44
44
|
"mso-conditional-parser": "^2.0.1",
|
|
45
|
-
"ssjs-data": "^0.
|
|
45
|
+
"ssjs-data": "^0.20.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"eslint": ">=9.0.0"
|
package/src/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import ssjsNoUnsupportedSyntax from './rules/ssjs/no-unsupported-syntax.js';
|
|
|
43
43
|
import ssjsNoUnknownFunction from './rules/ssjs/no-unknown-function.js';
|
|
44
44
|
import ssjsNoMcnUnsupported from './rules/ssjs/no-mcn-unsupported.js';
|
|
45
45
|
import ssjsNoDeprecatedFunction from './rules/ssjs/no-deprecated-function.js';
|
|
46
|
+
import ssjsNoNonexistentGlobal from './rules/ssjs/no-nonexistent-global.js';
|
|
46
47
|
import ssjsNoPropertyCall from './rules/ssjs/no-property-call.js';
|
|
47
48
|
import ssjsPlatformFunctionArity from './rules/ssjs/platform-function-arity.js';
|
|
48
49
|
import ssjsCacheLoopLength from './rules/ssjs/cache-loop-length.js';
|
|
@@ -125,6 +126,7 @@ const plugin = {
|
|
|
125
126
|
'ssjs-no-unknown-function': ssjsNoUnknownFunction,
|
|
126
127
|
'ssjs-no-mcn-unsupported': ssjsNoMcnUnsupported,
|
|
127
128
|
'ssjs-no-deprecated-function': ssjsNoDeprecatedFunction,
|
|
129
|
+
'ssjs-no-nonexistent-global': ssjsNoNonexistentGlobal,
|
|
128
130
|
'ssjs-no-property-call': ssjsNoPropertyCall,
|
|
129
131
|
'ssjs-platform-function-arity': ssjsPlatformFunctionArity,
|
|
130
132
|
'ssjs-cache-loop-length': ssjsCacheLoopLength,
|
|
@@ -171,6 +173,7 @@ const ssjsMcnRules = {
|
|
|
171
173
|
'sfmc/ssjs-require-platform-load': 'off',
|
|
172
174
|
'sfmc/ssjs-no-unsupported-syntax': 'off',
|
|
173
175
|
'sfmc/ssjs-no-deprecated-function': 'off',
|
|
176
|
+
'sfmc/ssjs-no-nonexistent-global': 'off',
|
|
174
177
|
'sfmc/ssjs-no-property-call': 'off',
|
|
175
178
|
'sfmc/ssjs-platform-function-arity': 'off',
|
|
176
179
|
'sfmc/ssjs-cache-loop-length': 'off',
|
|
@@ -242,6 +245,7 @@ const ssjsRecommendedRules = {
|
|
|
242
245
|
'sfmc/ssjs-no-unsupported-syntax': 'error',
|
|
243
246
|
'sfmc/ssjs-no-unknown-function': 'error',
|
|
244
247
|
'sfmc/ssjs-no-deprecated-function': 'error',
|
|
248
|
+
'sfmc/ssjs-no-nonexistent-global': 'error',
|
|
245
249
|
'sfmc/ssjs-no-property-call': 'error',
|
|
246
250
|
'sfmc/ssjs-platform-function-arity': 'error',
|
|
247
251
|
'sfmc/ssjs-cache-loop-length': 'warn',
|
|
@@ -266,6 +270,7 @@ const ssjsStrictRules = {
|
|
|
266
270
|
'sfmc/ssjs-no-unsupported-syntax': 'error',
|
|
267
271
|
'sfmc/ssjs-no-unknown-function': 'error',
|
|
268
272
|
'sfmc/ssjs-no-deprecated-function': 'error',
|
|
273
|
+
'sfmc/ssjs-no-nonexistent-global': 'error',
|
|
269
274
|
'sfmc/ssjs-no-property-call': 'error',
|
|
270
275
|
'sfmc/ssjs-platform-function-arity': 'error',
|
|
271
276
|
'sfmc/ssjs-cache-loop-length': 'error',
|
|
@@ -12,9 +12,15 @@
|
|
|
12
12
|
* - ContentAreaObj.Retrieve(...) — static method
|
|
13
13
|
* - <contentAreaObjVar>.Update(...) — instance method on a tracked ContentAreaObj variable
|
|
14
14
|
* - <contentAreaObjVar>.Remove() — instance method on a tracked ContentAreaObj variable
|
|
15
|
+
* - ErrorUtil.ThrowWSProxyError(...) — deprecated; only exists under Platform.Load("Core", "1")
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
platformFunctionLookup,
|
|
20
|
+
SSJS_GLOBALS,
|
|
21
|
+
CONTENT_AREA_OBJ_METHODS,
|
|
22
|
+
ERROR_UTIL_METHODS,
|
|
23
|
+
} from 'ssjs-data';
|
|
18
24
|
|
|
19
25
|
// Lookup Map: lowercase name → entry, for SSJS_GLOBALS entries that are deprecated.
|
|
20
26
|
// Used to flag bare calls like ContentArea(...) and ContentAreaByName(...).
|
|
@@ -38,6 +44,12 @@ const CONTENT_AREA_INSTANCE_DEPRECATED = new Set(
|
|
|
38
44
|
),
|
|
39
45
|
);
|
|
40
46
|
|
|
47
|
+
// Deprecated ErrorUtil methods (e.g. ThrowWSProxyError). Used to flag calls like
|
|
48
|
+
// ErrorUtil.ThrowWSProxyError(result), which only exists under Platform.Load("Core", "1").
|
|
49
|
+
const ERRORUTIL_DEPRECATED = new Set(
|
|
50
|
+
ERROR_UTIL_METHODS.filter((m) => m.deprecated).map((m) => m.name.toLowerCase()),
|
|
51
|
+
);
|
|
52
|
+
|
|
41
53
|
export default {
|
|
42
54
|
meta: {
|
|
43
55
|
type: 'suggestion',
|
|
@@ -52,6 +64,8 @@ export default {
|
|
|
52
64
|
"'ContentAreaObj.{{name}}' is deprecated. Content Areas are no longer supported.",
|
|
53
65
|
deprecatedCoreInstance:
|
|
54
66
|
"'{{method}}' called on a ContentAreaObj variable is deprecated. Content Areas are no longer supported.",
|
|
67
|
+
deprecatedErrorUtil:
|
|
68
|
+
"'ErrorUtil.{{name}}' is deprecated — it only exists under Platform.Load(\"Core\", \"1\") and is undefined in newer Core versions. Check 'result.Status' and 'throw new Error(...)' instead.",
|
|
55
69
|
},
|
|
56
70
|
schema: [],
|
|
57
71
|
},
|
|
@@ -143,6 +157,20 @@ export default {
|
|
|
143
157
|
return;
|
|
144
158
|
}
|
|
145
159
|
|
|
160
|
+
// ── ErrorUtil.ThrowWSProxyError(…) — deprecated ───────────────
|
|
161
|
+
if (
|
|
162
|
+
callee.object.type === 'Identifier' &&
|
|
163
|
+
callee.object.name === 'ErrorUtil' &&
|
|
164
|
+
ERRORUTIL_DEPRECATED.has(methodName.toLowerCase())
|
|
165
|
+
) {
|
|
166
|
+
context.report({
|
|
167
|
+
node: property,
|
|
168
|
+
messageId: 'deprecatedErrorUtil',
|
|
169
|
+
data: { name: methodName },
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
// ── <contentAreaVar>.Update/Remove() — instance deprecated ─────
|
|
147
175
|
if (
|
|
148
176
|
callee.object.type === 'Identifier' &&
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: ssjs-no-nonexistent-global
|
|
3
|
+
*
|
|
4
|
+
* Flags bare-name SSJS globals that are officially documented but proven NOT to
|
|
5
|
+
* exist at runtime as callable identifiers — calling them throws a ReferenceError.
|
|
6
|
+
*
|
|
7
|
+
* The offending names come from ssjs-data's `notDefinedAtRuntime` flag, so phantom
|
|
8
|
+
* globals are picked up automatically without editing this rule. Only bare-name
|
|
9
|
+
* callable phantoms (Identifier callees) are flagged here; object-namespace
|
|
10
|
+
* phantoms accessed via member calls (e.g. `Recipient.GetAttributeValue(...)`) are
|
|
11
|
+
* out of scope for this rule.
|
|
12
|
+
*
|
|
13
|
+
* Note: many bare-name Core globals (Write, Stringify, Base64Encode, Format,
|
|
14
|
+
* Redirect, …) are NOT phantom — they exist after `Platform.Load("core")` when
|
|
15
|
+
* called in the same scope as the load, so they are intentionally not flagged.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { notDefinedAtRuntimeGlobalLookup } from 'ssjs-data';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract a runtime-safe replacement suggestion for a phantom global from its
|
|
22
|
+
* ssjs-data entry. Prefers the `Platform.*` call named in the officialDocsNote,
|
|
23
|
+
* falling back to a generic hint.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} entry - The ssjs-data global entry.
|
|
26
|
+
* @returns {string} A replacement suggestion (e.g. `Platform.Response.Redirect(...)`).
|
|
27
|
+
*/
|
|
28
|
+
function replacementFor(entry) {
|
|
29
|
+
const source = `${entry.officialDocsNote ?? ''} ${entry.description ?? ''}`;
|
|
30
|
+
const match = source.match(/Platform\.[A-Za-z.]+\([^)]*\)/);
|
|
31
|
+
return match ? match[0] : 'a supported alternative';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
meta: {
|
|
36
|
+
type: 'problem',
|
|
37
|
+
docs: {
|
|
38
|
+
description:
|
|
39
|
+
'Disallow SSJS globals that are documented but do not exist at runtime (throw ReferenceError)',
|
|
40
|
+
},
|
|
41
|
+
messages: {
|
|
42
|
+
nonexistentGlobal:
|
|
43
|
+
"'{{name}}' does not exist at runtime (calling it throws a ReferenceError). Use {{replacement}} instead.",
|
|
44
|
+
},
|
|
45
|
+
schema: [],
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
create(context) {
|
|
49
|
+
return {
|
|
50
|
+
CallExpression(node) {
|
|
51
|
+
const callee = node.callee;
|
|
52
|
+
if (callee.type !== 'Identifier') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const entry = notDefinedAtRuntimeGlobalLookup.get(callee.name.toLowerCase());
|
|
56
|
+
if (!entry) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
context.report({
|
|
60
|
+
node: callee,
|
|
61
|
+
messageId: 'nonexistentGlobal',
|
|
62
|
+
data: { name: callee.name, replacement: replacementFor(entry) },
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|