eslint-plugin-sfmc 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -9
- package/docs/rules/ssjs/no-deprecated-function.md +56 -0
- package/docs/rules/ssjs/no-property-call.md +95 -0
- package/docs/rules/ssjs/no-unknown-function.md +57 -0
- package/package.json +1 -1
- package/src/index.js +15 -33
- package/src/rules/ssjs/no-deprecated-function.js +181 -0
- package/src/rules/ssjs/no-property-call.js +136 -0
- package/src/rules/ssjs/no-unknown-function.js +236 -0
- package/src/rules/ssjs/require-platform-load.js +28 -9
- package/docs/rules/ssjs/no-unknown-core-method.md +0 -46
- package/docs/rules/ssjs/no-unknown-http-method.md +0 -44
- package/docs/rules/ssjs/no-unknown-platform-client-browser.md +0 -44
- package/docs/rules/ssjs/no-unknown-platform-function.md +0 -44
- package/docs/rules/ssjs/no-unknown-platform-request.md +0 -43
- package/docs/rules/ssjs/no-unknown-platform-response.md +0 -43
- package/docs/rules/ssjs/no-unknown-platform-variable.md +0 -43
- package/docs/rules/ssjs/no-unknown-wsproxy-method.md +0 -46
- package/src/rules/ssjs/no-unknown-core-method.js +0 -121
- package/src/rules/ssjs/no-unknown-http-method.js +0 -47
- package/src/rules/ssjs/no-unknown-platform-client-browser.js +0 -53
- package/src/rules/ssjs/no-unknown-platform-function.js +0 -51
- package/src/rules/ssjs/no-unknown-platform-request.js +0 -52
- package/src/rules/ssjs/no-unknown-platform-response.js +0 -52
- package/src/rules/ssjs/no-unknown-platform-variable.js +0 -52
- package/src/rules/ssjs/no-unknown-wsproxy-method.js +0 -88
package/README.md
CHANGED
|
@@ -65,19 +65,14 @@ export default [
|
|
|
65
65
|
|
|
66
66
|
| Rule | Default | Description |
|
|
67
67
|
| ------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------- |
|
|
68
|
-
| [`sfmc/ssjs-require-platform-load`](docs/rules/ssjs/require-platform-load.md) | `error` | Require `Platform.Load("core")` before Core
|
|
68
|
+
| [`sfmc/ssjs-require-platform-load`](docs/rules/ssjs/require-platform-load.md) | `error` | Require `Platform.Load("core")` before Core or requiresCoreLoad globals |
|
|
69
69
|
| [`sfmc/ssjs-no-unsupported-syntax`](docs/rules/ssjs/no-unsupported-syntax.md) | `error` | Flag ES6+ syntax not supported by SFMC |
|
|
70
|
-
| [`sfmc/ssjs-no-unknown-
|
|
70
|
+
| [`sfmc/ssjs-no-unknown-function`](docs/rules/ssjs/no-unknown-function.md) | `error` | Disallow unknown methods on Platform.*, HTTP, Core Library, and WSProxy |
|
|
71
|
+
| [`sfmc/ssjs-no-deprecated-function`](docs/rules/ssjs/no-deprecated-function.md) | `error` | Flag use of deprecated SFMC SSJS APIs (e.g. ContentArea, ContentAreaObj) |
|
|
72
|
+
| [`sfmc/ssjs-no-property-call`](docs/rules/ssjs/no-property-call.md) | `error` | Disallow calling Platform.Request/Response properties as functions |
|
|
71
73
|
| [`sfmc/ssjs-platform-function-arity`](docs/rules/ssjs/platform-function-arity.md) | `error` | Enforce correct arity for `Platform.Function.*` |
|
|
72
|
-
| [`sfmc/ssjs-no-unknown-http-method`](docs/rules/ssjs/no-unknown-http-method.md) | `error` | Disallow unknown `HTTP.*` methods |
|
|
73
|
-
| [`sfmc/ssjs-no-unknown-platform-variable`](docs/rules/ssjs/no-unknown-platform-variable.md) | `error` | Disallow unknown `Platform.Variable.*` methods |
|
|
74
|
-
| [`sfmc/ssjs-no-unknown-platform-response`](docs/rules/ssjs/no-unknown-platform-response.md) | `error` | Disallow unknown `Platform.Response.*` methods |
|
|
75
|
-
| [`sfmc/ssjs-no-unknown-platform-request`](docs/rules/ssjs/no-unknown-platform-request.md) | `error` | Disallow unknown `Platform.Request.*` methods |
|
|
76
74
|
| [`sfmc/ssjs-require-platform-load-order`](docs/rules/ssjs/require-platform-load-order.md) | `error` | Require `Platform.Load()` before Core usage in order |
|
|
77
75
|
| [`sfmc/ssjs-no-hardcoded-credentials`](docs/rules/ssjs/no-hardcoded-credentials.md) | `error` | Flag hardcoded keys in encryption calls |
|
|
78
|
-
| [`sfmc/ssjs-no-unknown-platform-client-browser`](docs/rules/ssjs/no-unknown-platform-client-browser.md) | `error` | Disallow unknown `Platform.ClientBrowser.*` methods |
|
|
79
|
-
| [`sfmc/ssjs-no-unknown-core-method`](docs/rules/ssjs/no-unknown-core-method.md) | `warn` | Disallow unknown methods on Core library objects |
|
|
80
|
-
| [`sfmc/ssjs-no-unknown-wsproxy-method`](docs/rules/ssjs/no-unknown-wsproxy-method.md) | `warn` | Disallow unknown WSProxy methods |
|
|
81
76
|
| [`sfmc/ssjs-cache-loop-length`](docs/rules/ssjs/cache-loop-length.md) | `warn` | Require caching `.length` in for-loops |
|
|
82
77
|
| [`sfmc/ssjs-require-hasownproperty`](docs/rules/ssjs/require-hasownproperty.md) | `warn` | Require `hasOwnProperty` guard in for-in loops |
|
|
83
78
|
| [`sfmc/ssjs-prefer-platform-load-version`](docs/rules/ssjs/prefer-platform-load-version.md) | `warn` | Enforce a minimum `Platform.Load` version string |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ssjs-no-deprecated-function
|
|
2
|
+
|
|
3
|
+
Flags calls to deprecated SFMC SSJS APIs. Currently focuses on the **Content Areas** feature,
|
|
4
|
+
which has been retired and no longer allows creating or updating content.
|
|
5
|
+
|
|
6
|
+
## What is flagged
|
|
7
|
+
|
|
8
|
+
| API | Reason |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `ContentArea(…)` | Global alias; Content Areas are deprecated |
|
|
11
|
+
| `ContentAreaByName(…)` | Global alias; Content Areas are deprecated |
|
|
12
|
+
| `Platform.Function.ContentArea(…)` | Content Areas are deprecated |
|
|
13
|
+
| `Platform.Function.ContentAreaByName(…)` | Content Areas are deprecated |
|
|
14
|
+
| `ContentAreaObj.Init(…)` | ContentAreaObj class is deprecated |
|
|
15
|
+
| `ContentAreaObj.Add(…)` | ContentAreaObj class is deprecated |
|
|
16
|
+
| `ContentAreaObj.Retrieve(…)` | ContentAreaObj class is deprecated |
|
|
17
|
+
| `<contentAreaVar>.Update(…)` | Instance method on a deprecated ContentAreaObj variable |
|
|
18
|
+
| `<contentAreaVar>.Remove()` | Instance method on a deprecated ContentAreaObj variable |
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### ❌ Incorrect
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
// Global alias — deprecated
|
|
26
|
+
var html = ContentAreaByName('Public Content/MyBlock');
|
|
27
|
+
|
|
28
|
+
// Platform.Function — deprecated
|
|
29
|
+
var content = Platform.Function.ContentArea(12345);
|
|
30
|
+
|
|
31
|
+
// ContentAreaObj static method — deprecated
|
|
32
|
+
var results = ContentAreaObj.Retrieve({
|
|
33
|
+
Property: 'CustomerKey',
|
|
34
|
+
SimpleOperator: 'equals',
|
|
35
|
+
Value: 'myCA',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ContentAreaObj instance — deprecated
|
|
39
|
+
var area = ContentAreaObj.Init('myCA');
|
|
40
|
+
var status = area.Update({ Name: 'Updated Name' });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ✅ Correct
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// Use Content Builder blocks via Platform.Function.ContentBlockByKey / ContentBlockById
|
|
47
|
+
var html = Platform.Function.ContentBlockByKey('Public Content/MyBlock');
|
|
48
|
+
var content = Platform.Function.ContentBlockById(12345);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Rule details
|
|
52
|
+
|
|
53
|
+
- **Type:** `suggestion`
|
|
54
|
+
- **Fixable:** No
|
|
55
|
+
- **Recommended:** Yes
|
|
56
|
+
- **Strict:** Yes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# ssjs-no-property-call
|
|
2
|
+
|
|
3
|
+
Flags `Platform.Request` and `Platform.Response` members that are **properties**, not functions.
|
|
4
|
+
Calling them with parentheses `()` is a runtime error — they must be accessed without parentheses.
|
|
5
|
+
|
|
6
|
+
The rule is fixable:
|
|
7
|
+
|
|
8
|
+
- **No-arg calls** (`Property()`) → removes the `()` on both `Request` and `Response` properties.
|
|
9
|
+
- **Single-arg calls on `Platform.Response.*`** (`ContentType("text/html")`) → rewrites to an assignment (`= value`), because those two properties are read/write.
|
|
10
|
+
- **Any-arg calls on `Platform.Request.*`** → reported as an error with no auto-fix, because `Request` properties are read-only.
|
|
11
|
+
|
|
12
|
+
## Properties affected
|
|
13
|
+
|
|
14
|
+
### `Platform.Request` — read-only
|
|
15
|
+
|
|
16
|
+
| Property |
|
|
17
|
+
|---|
|
|
18
|
+
| `Browser` |
|
|
19
|
+
| `ClientIP` |
|
|
20
|
+
| `HasSSL` |
|
|
21
|
+
| `IsSSL` |
|
|
22
|
+
| `Method` |
|
|
23
|
+
| `QueryString` |
|
|
24
|
+
| `ReferrerURL` |
|
|
25
|
+
| `RequestURL` |
|
|
26
|
+
| `UserAgent` |
|
|
27
|
+
|
|
28
|
+
### `Platform.Response` — read/write
|
|
29
|
+
|
|
30
|
+
| Property |
|
|
31
|
+
|---|
|
|
32
|
+
| `ContentType` |
|
|
33
|
+
| `CharacterSet` |
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
### ❌ Incorrect — reading a property with `()`
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
var method = Platform.Request.Method(); // ← runtime error
|
|
41
|
+
var ip = Platform.Request.ClientIP(); // ← runtime error
|
|
42
|
+
var ct = Platform.Response.ContentType(); // ← runtime error
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Auto-fix removes the parentheses:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
var method = Platform.Request.Method;
|
|
49
|
+
var ip = Platform.Request.ClientIP;
|
|
50
|
+
var ct = Platform.Response.ContentType;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### ❌ Incorrect — setting a `Platform.Response` property via function call
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
Platform.Response.ContentType("application/json"); // ← should be an assignment
|
|
57
|
+
Platform.Response.CharacterSet("UTF-8"); // ← should be an assignment
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Auto-fix converts to an assignment statement:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
Platform.Response.ContentType = "application/json";
|
|
64
|
+
Platform.Response.CharacterSet = "UTF-8";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### ❌ Incorrect — attempting to set a read-only `Platform.Request` property (no fix)
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
Platform.Request.Method("POST"); // ← read-only, cannot be set
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### ✅ Correct
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// Reading properties — no parentheses
|
|
77
|
+
var method = Platform.Request.Method;
|
|
78
|
+
var ip = Platform.Request.ClientIP;
|
|
79
|
+
var ct = Platform.Response.ContentType;
|
|
80
|
+
|
|
81
|
+
// Setting writable Response properties — use assignment
|
|
82
|
+
Platform.Response.ContentType = "application/json";
|
|
83
|
+
Platform.Response.CharacterSet = "UTF-8";
|
|
84
|
+
|
|
85
|
+
// Calling real methods — parentheses are correct
|
|
86
|
+
Platform.Request.GetQueryStringParameter("id");
|
|
87
|
+
Platform.Response.Write("<p>Hello</p>");
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Rule details
|
|
91
|
+
|
|
92
|
+
- **Type:** `problem`
|
|
93
|
+
- **Fixable:** Yes (`code`)
|
|
94
|
+
- **Recommended:** Yes
|
|
95
|
+
- **Strict:** Yes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ssjs-no-unknown-function
|
|
2
|
+
|
|
3
|
+
Flags calls to SFMC API methods that do not exist in the catalog. This single rule replaces the seven narrow `no-unknown-*` rules from earlier versions of the plugin.
|
|
4
|
+
|
|
5
|
+
## Covered namespaces
|
|
6
|
+
|
|
7
|
+
| Namespace | Example |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `Platform.Function` | `Platform.Function.LookupRows(…)` |
|
|
10
|
+
| `Platform.Variable` | `Platform.Variable.GetValue(…)` |
|
|
11
|
+
| `Platform.Request` | `Platform.Request.GetQueryStringParameter(…)` |
|
|
12
|
+
| `Platform.Response` | `Platform.Response.Write(…)` |
|
|
13
|
+
| `Platform.Recipient` | `Platform.Recipient.GetAttributeValue(…)` |
|
|
14
|
+
| `HTTP` | `HTTP.Get(…)` |
|
|
15
|
+
| Core Library instance methods | `de.Rows.Retrieve()` (tracked from `DataExtension.Init(…)`) |
|
|
16
|
+
| WSProxy instance methods | `api.retrieve(…)` (tracked from `new Script.Util.WSProxy()`) |
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### ❌ Incorrect
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// Typo in Platform.Function method name
|
|
24
|
+
Platform.Function.FetchRows('MyDE', 'Status', 'Active');
|
|
25
|
+
|
|
26
|
+
// Non-existent WSProxy method
|
|
27
|
+
var api = new Script.Util.WSProxy();
|
|
28
|
+
api.getAllResults({ ObjectType: 'DataExtension' });
|
|
29
|
+
|
|
30
|
+
// Unknown Core Library method on a DataExtension instance
|
|
31
|
+
var de = DataExtension.Init('CustomerData');
|
|
32
|
+
de.ExportRows();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### ✅ Correct
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
Platform.Function.LookupRows('MyDE', 'Status', 'Active');
|
|
39
|
+
|
|
40
|
+
var api = new Script.Util.WSProxy();
|
|
41
|
+
api.retrieve({ ObjectType: 'DataExtension' }, ['CustomerKey', 'Name'], {});
|
|
42
|
+
|
|
43
|
+
var de = DataExtension.Init('CustomerData');
|
|
44
|
+
de.Rows.Retrieve();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## When to use
|
|
48
|
+
|
|
49
|
+
Enable this rule to catch typos, outdated method names, and unsupported API calls before
|
|
50
|
+
they cause runtime failures.
|
|
51
|
+
|
|
52
|
+
## Rule details
|
|
53
|
+
|
|
54
|
+
- **Type:** `problem`
|
|
55
|
+
- **Fixable:** No
|
|
56
|
+
- **Recommended:** Yes
|
|
57
|
+
- **Strict:** Yes
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* - vscode-sfmc-language (IntelliSense, diagnostics)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { SSJS_GLOBALS_MAP } from 'ssjs-data';
|
|
13
12
|
import * as ampscriptParser from './ampscript-parser.js';
|
|
14
13
|
|
|
15
14
|
// ── AMPscript rules ───────────────────────────────────────────────────────────
|
|
@@ -38,15 +37,10 @@ import ampNoNestedAmpscriptDelimiter from './rules/amp/no-nested-ampscript-delim
|
|
|
38
37
|
|
|
39
38
|
import ssjsRequirePlatformLoad from './rules/ssjs/require-platform-load.js';
|
|
40
39
|
import ssjsNoUnsupportedSyntax from './rules/ssjs/no-unsupported-syntax.js';
|
|
41
|
-
import
|
|
42
|
-
import
|
|
40
|
+
import ssjsNoUnknownFunction from './rules/ssjs/no-unknown-function.js';
|
|
41
|
+
import ssjsNoDeprecatedFunction from './rules/ssjs/no-deprecated-function.js';
|
|
42
|
+
import ssjsNoPropertyCall from './rules/ssjs/no-property-call.js';
|
|
43
43
|
import ssjsPlatformFunctionArity from './rules/ssjs/platform-function-arity.js';
|
|
44
|
-
import ssjsNoUnknownHttpMethod from './rules/ssjs/no-unknown-http-method.js';
|
|
45
|
-
import ssjsNoUnknownWsproxyMethod from './rules/ssjs/no-unknown-wsproxy-method.js';
|
|
46
|
-
import ssjsNoUnknownPlatformVariable from './rules/ssjs/no-unknown-platform-variable.js';
|
|
47
|
-
import ssjsNoUnknownPlatformResponse from './rules/ssjs/no-unknown-platform-response.js';
|
|
48
|
-
import ssjsNoUnknownPlatformRequest from './rules/ssjs/no-unknown-platform-request.js';
|
|
49
|
-
import ssjsNoUnknownPlatformClientBrowser from './rules/ssjs/no-unknown-platform-client-browser.js';
|
|
50
44
|
import ssjsCacheLoopLength from './rules/ssjs/cache-loop-length.js';
|
|
51
45
|
import ssjsRequireHasownproperty from './rules/ssjs/require-hasownproperty.js';
|
|
52
46
|
import ssjsRequirePlatformLoadOrder from './rules/ssjs/require-platform-load-order.js';
|
|
@@ -65,6 +59,9 @@ import ampscriptProcessor from './ampscript-processor.js';
|
|
|
65
59
|
import ssjsProcessor from './ssjs-processor.js';
|
|
66
60
|
import combinedProcessor from './processor.js';
|
|
67
61
|
|
|
62
|
+
// ── Data imports ──────────────────────────────────────────────────────────────
|
|
63
|
+
import { SSJS_GLOBALS_MAP } from 'ssjs-data';
|
|
64
|
+
|
|
68
65
|
// ── Plugin definition ─────────────────────────────────────────────────────────
|
|
69
66
|
|
|
70
67
|
const plugin = {
|
|
@@ -98,15 +95,10 @@ const plugin = {
|
|
|
98
95
|
// SSJS rules (ssjs- prefix)
|
|
99
96
|
'ssjs-require-platform-load': ssjsRequirePlatformLoad,
|
|
100
97
|
'ssjs-no-unsupported-syntax': ssjsNoUnsupportedSyntax,
|
|
101
|
-
'ssjs-no-unknown-
|
|
102
|
-
'ssjs-no-
|
|
98
|
+
'ssjs-no-unknown-function': ssjsNoUnknownFunction,
|
|
99
|
+
'ssjs-no-deprecated-function': ssjsNoDeprecatedFunction,
|
|
100
|
+
'ssjs-no-property-call': ssjsNoPropertyCall,
|
|
103
101
|
'ssjs-platform-function-arity': ssjsPlatformFunctionArity,
|
|
104
|
-
'ssjs-no-unknown-http-method': ssjsNoUnknownHttpMethod,
|
|
105
|
-
'ssjs-no-unknown-wsproxy-method': ssjsNoUnknownWsproxyMethod,
|
|
106
|
-
'ssjs-no-unknown-platform-variable': ssjsNoUnknownPlatformVariable,
|
|
107
|
-
'ssjs-no-unknown-platform-response': ssjsNoUnknownPlatformResponse,
|
|
108
|
-
'ssjs-no-unknown-platform-request': ssjsNoUnknownPlatformRequest,
|
|
109
|
-
'ssjs-no-unknown-platform-client-browser': ssjsNoUnknownPlatformClientBrowser,
|
|
110
102
|
'ssjs-cache-loop-length': ssjsCacheLoopLength,
|
|
111
103
|
'ssjs-require-hasownproperty': ssjsRequireHasownproperty,
|
|
112
104
|
'ssjs-require-platform-load-order': ssjsRequirePlatformLoadOrder,
|
|
@@ -177,15 +169,10 @@ const ampStrictRules = {
|
|
|
177
169
|
const ssjsRecommendedRules = {
|
|
178
170
|
'sfmc/ssjs-require-platform-load': 'error',
|
|
179
171
|
'sfmc/ssjs-no-unsupported-syntax': 'error',
|
|
180
|
-
'sfmc/ssjs-no-unknown-
|
|
181
|
-
'sfmc/ssjs-no-
|
|
172
|
+
'sfmc/ssjs-no-unknown-function': 'error',
|
|
173
|
+
'sfmc/ssjs-no-deprecated-function': 'error',
|
|
174
|
+
'sfmc/ssjs-no-property-call': 'error',
|
|
182
175
|
'sfmc/ssjs-platform-function-arity': 'error',
|
|
183
|
-
'sfmc/ssjs-no-unknown-http-method': 'error',
|
|
184
|
-
'sfmc/ssjs-no-unknown-wsproxy-method': 'warn',
|
|
185
|
-
'sfmc/ssjs-no-unknown-platform-variable': 'error',
|
|
186
|
-
'sfmc/ssjs-no-unknown-platform-response': 'error',
|
|
187
|
-
'sfmc/ssjs-no-unknown-platform-request': 'error',
|
|
188
|
-
'sfmc/ssjs-no-unknown-platform-client-browser': 'error',
|
|
189
176
|
'sfmc/ssjs-cache-loop-length': 'warn',
|
|
190
177
|
'sfmc/ssjs-require-hasownproperty': 'warn',
|
|
191
178
|
'sfmc/ssjs-require-platform-load-order': 'error',
|
|
@@ -203,15 +190,10 @@ const ssjsRecommendedRules = {
|
|
|
203
190
|
const ssjsStrictRules = {
|
|
204
191
|
'sfmc/ssjs-require-platform-load': 'error',
|
|
205
192
|
'sfmc/ssjs-no-unsupported-syntax': 'error',
|
|
206
|
-
'sfmc/ssjs-no-unknown-
|
|
207
|
-
'sfmc/ssjs-no-
|
|
193
|
+
'sfmc/ssjs-no-unknown-function': 'error',
|
|
194
|
+
'sfmc/ssjs-no-deprecated-function': 'error',
|
|
195
|
+
'sfmc/ssjs-no-property-call': 'error',
|
|
208
196
|
'sfmc/ssjs-platform-function-arity': 'error',
|
|
209
|
-
'sfmc/ssjs-no-unknown-http-method': 'error',
|
|
210
|
-
'sfmc/ssjs-no-unknown-wsproxy-method': 'error',
|
|
211
|
-
'sfmc/ssjs-no-unknown-platform-variable': 'error',
|
|
212
|
-
'sfmc/ssjs-no-unknown-platform-response': 'error',
|
|
213
|
-
'sfmc/ssjs-no-unknown-platform-request': 'error',
|
|
214
|
-
'sfmc/ssjs-no-unknown-platform-client-browser': 'error',
|
|
215
197
|
'sfmc/ssjs-cache-loop-length': 'error',
|
|
216
198
|
'sfmc/ssjs-require-hasownproperty': 'error',
|
|
217
199
|
'sfmc/ssjs-require-platform-load-order': 'error',
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: ssjs-no-deprecated-function
|
|
3
|
+
*
|
|
4
|
+
* Flags use of deprecated SFMC SSJS APIs. Currently covers:
|
|
5
|
+
*
|
|
6
|
+
* - ContentArea(...) — bare alias (use Platform.Function.ContentAreaByName instead)
|
|
7
|
+
* - ContentAreaByName(...) — bare alias (use Platform.Function.ContentAreaByName instead)
|
|
8
|
+
* - Platform.Function.ContentArea(...)
|
|
9
|
+
* - Platform.Function.ContentAreaByName(...)
|
|
10
|
+
* - ContentAreaObj.Init(...) — static method (class is deprecated)
|
|
11
|
+
* - ContentAreaObj.Add(...) — static method
|
|
12
|
+
* - ContentAreaObj.Retrieve(...) — static method
|
|
13
|
+
* - <contentAreaObjVar>.Update(...) — instance method on a tracked ContentAreaObj variable
|
|
14
|
+
* - <contentAreaObjVar>.Remove() — instance method on a tracked ContentAreaObj variable
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { platformFunctionLookup, SSJS_GLOBALS, CONTENT_AREA_OBJ_METHODS } from 'ssjs-data';
|
|
18
|
+
|
|
19
|
+
// Lookup Map: lowercase name → entry, for SSJS_GLOBALS entries that are deprecated.
|
|
20
|
+
// Used to flag bare calls like ContentArea(...) and ContentAreaByName(...).
|
|
21
|
+
const DEPRECATED_GLOBALS = new Map(
|
|
22
|
+
SSJS_GLOBALS.filter((g) => g.deprecated).map((g) => [g.name.toLowerCase(), g]),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Build sets of deprecated static and instance methods from ssjs-data.
|
|
26
|
+
// Exclude 'Init' — that call is already implicitly covered when we track the
|
|
27
|
+
// returned instance and flag its instance methods. Reporting it here as well
|
|
28
|
+
// would produce a duplicate error on the same statement.
|
|
29
|
+
const CONTENT_AREA_STATIC_DEPRECATED = new Set(
|
|
30
|
+
CONTENT_AREA_OBJ_METHODS.filter(
|
|
31
|
+
(m) => m.deprecated && m.isStatic && m.name.toLowerCase() !== 'init',
|
|
32
|
+
).map((m) => m.name.toLowerCase()),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const CONTENT_AREA_INSTANCE_DEPRECATED = new Set(
|
|
36
|
+
CONTENT_AREA_OBJ_METHODS.filter((m) => m.deprecated && !m.isStatic).map((m) =>
|
|
37
|
+
m.name.toLowerCase(),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
meta: {
|
|
43
|
+
type: 'suggestion',
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Disallow use of deprecated SFMC SSJS APIs',
|
|
46
|
+
},
|
|
47
|
+
messages: {
|
|
48
|
+
deprecatedGlobal: "'{{name}}' is deprecated. {{replacement}}",
|
|
49
|
+
deprecatedPlatformFunction:
|
|
50
|
+
"'Platform.Function.{{name}}' is deprecated. Use a supported alternative.",
|
|
51
|
+
deprecatedCoreStatic:
|
|
52
|
+
"'ContentAreaObj.{{name}}' is deprecated. Content Areas are no longer supported.",
|
|
53
|
+
deprecatedCoreInstance:
|
|
54
|
+
"'{{method}}' called on a ContentAreaObj variable is deprecated. Content Areas are no longer supported.",
|
|
55
|
+
},
|
|
56
|
+
schema: [],
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
create(context) {
|
|
60
|
+
// Track variable names assigned via ContentAreaObj.Init()
|
|
61
|
+
const contentAreaVariables = new Set();
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
VariableDeclaration(node) {
|
|
65
|
+
for (const decl of node.declarations) {
|
|
66
|
+
if (
|
|
67
|
+
decl.init &&
|
|
68
|
+
decl.id &&
|
|
69
|
+
decl.id.type === 'Identifier' &&
|
|
70
|
+
isContentAreaObjInit(decl.init)
|
|
71
|
+
) {
|
|
72
|
+
contentAreaVariables.add(decl.id.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
AssignmentExpression(node) {
|
|
78
|
+
if (node.left.type === 'Identifier' && isContentAreaObjInit(node.right)) {
|
|
79
|
+
contentAreaVariables.add(node.left.name);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
CallExpression(node) {
|
|
84
|
+
const callee = node.callee;
|
|
85
|
+
|
|
86
|
+
// ── Bare globals: ContentArea(…) and ContentAreaByName(…) ──────
|
|
87
|
+
if (callee.type === 'Identifier') {
|
|
88
|
+
const entry = DEPRECATED_GLOBALS.get(callee.name.toLowerCase());
|
|
89
|
+
if (entry && entry.deprecated) {
|
|
90
|
+
const replacement = entry.aliasOf
|
|
91
|
+
? `Use '${entry.aliasOf}' instead.`
|
|
92
|
+
: 'Use a supported alternative.';
|
|
93
|
+
context.report({
|
|
94
|
+
node: callee,
|
|
95
|
+
messageId: 'deprecatedGlobal',
|
|
96
|
+
data: { name: callee.name, replacement },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (callee.type !== 'MemberExpression') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const property = callee.property;
|
|
107
|
+
if (property.type !== 'Identifier') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const methodName = property.name;
|
|
112
|
+
|
|
113
|
+
// ── Platform.Function.ContentArea(…) / ContentAreaByName(…) ───
|
|
114
|
+
if (
|
|
115
|
+
callee.object.type === 'MemberExpression' &&
|
|
116
|
+
callee.object.object.type === 'Identifier' &&
|
|
117
|
+
callee.object.object.name === 'Platform' &&
|
|
118
|
+
callee.object.property.type === 'Identifier' &&
|
|
119
|
+
callee.object.property.name === 'Function'
|
|
120
|
+
) {
|
|
121
|
+
const entry = platformFunctionLookup.get(methodName.toLowerCase());
|
|
122
|
+
if (entry && entry.deprecated) {
|
|
123
|
+
context.report({
|
|
124
|
+
node: property,
|
|
125
|
+
messageId: 'deprecatedPlatformFunction',
|
|
126
|
+
data: { name: methodName },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── ContentAreaObj.Init/Add/Retrieve(…) — static deprecated ───
|
|
133
|
+
if (
|
|
134
|
+
callee.object.type === 'Identifier' &&
|
|
135
|
+
callee.object.name === 'ContentAreaObj' &&
|
|
136
|
+
CONTENT_AREA_STATIC_DEPRECATED.has(methodName.toLowerCase())
|
|
137
|
+
) {
|
|
138
|
+
context.report({
|
|
139
|
+
node: property,
|
|
140
|
+
messageId: 'deprecatedCoreStatic',
|
|
141
|
+
data: { name: methodName },
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── <contentAreaVar>.Update/Remove() — instance deprecated ─────
|
|
147
|
+
if (
|
|
148
|
+
callee.object.type === 'Identifier' &&
|
|
149
|
+
contentAreaVariables.has(callee.object.name) &&
|
|
150
|
+
CONTENT_AREA_INSTANCE_DEPRECATED.has(methodName.toLowerCase())
|
|
151
|
+
) {
|
|
152
|
+
context.report({
|
|
153
|
+
node: property,
|
|
154
|
+
messageId: 'deprecatedCoreInstance',
|
|
155
|
+
data: { method: methodName },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Returns true if `node` is a `ContentAreaObj.Init(…)` call.
|
|
165
|
+
*
|
|
166
|
+
* @param {import('eslint').Rule.Node} node
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
169
|
+
function isContentAreaObjInit(node) {
|
|
170
|
+
if (!node || node.type !== 'CallExpression') {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const callee = node.callee;
|
|
174
|
+
return (
|
|
175
|
+
callee.type === 'MemberExpression' &&
|
|
176
|
+
callee.object.type === 'Identifier' &&
|
|
177
|
+
callee.object.name === 'ContentAreaObj' &&
|
|
178
|
+
callee.property.type === 'Identifier' &&
|
|
179
|
+
callee.property.name === 'Init'
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: ssjs-no-property-call
|
|
3
|
+
*
|
|
4
|
+
* Flags Platform.Request and Platform.Response members that are properties,
|
|
5
|
+
* not methods. Calling them with `()` is a runtime error.
|
|
6
|
+
*
|
|
7
|
+
* Three cases are handled:
|
|
8
|
+
*
|
|
9
|
+
* 1. Read with parens (both namespaces, no args):
|
|
10
|
+
* Platform.Request.Method() → Platform.Request.Method
|
|
11
|
+
*
|
|
12
|
+
* 2. Write via function call (Platform.Response.* only, single arg):
|
|
13
|
+
* Platform.Response.ContentType("text/html")
|
|
14
|
+
* → Platform.Response.ContentType = "text/html"
|
|
15
|
+
*
|
|
16
|
+
* 3. Attempt to set a read-only property (Platform.Request.*, with args):
|
|
17
|
+
* Platform.Request.Method("POST") → no auto-fix (read-only)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { PLATFORM_RESPONSE_METHODS, PLATFORM_REQUEST_METHODS } from 'ssjs-data';
|
|
21
|
+
|
|
22
|
+
// Platform.Response properties — readable AND writable
|
|
23
|
+
const RESPONSE_PROPERTIES = new Set(
|
|
24
|
+
PLATFORM_RESPONSE_METHODS.filter((m) => m.isProperty).map((m) => m.name.toLowerCase()),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Platform.Request properties — read-only
|
|
28
|
+
const REQUEST_PROPERTIES = new Set(
|
|
29
|
+
PLATFORM_REQUEST_METHODS.filter((m) => m.isProperty).map((m) => m.name.toLowerCase()),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
meta: {
|
|
34
|
+
type: 'problem',
|
|
35
|
+
fixable: 'code',
|
|
36
|
+
docs: {
|
|
37
|
+
description: 'Disallow calling Platform.Request/Response properties as functions',
|
|
38
|
+
},
|
|
39
|
+
messages: {
|
|
40
|
+
propertyReadWithCall:
|
|
41
|
+
"'Platform.{{ns}}.{{name}}' is a property, not a function. " +
|
|
42
|
+
"Remove the '()' to read its value.",
|
|
43
|
+
readOnlyPropertySet:
|
|
44
|
+
"'Platform.Request.{{name}}' is a read-only property. It cannot be assigned a value.",
|
|
45
|
+
writablePropertySet:
|
|
46
|
+
"'Platform.Response.{{name}}' is a property. " +
|
|
47
|
+
'Use `Platform.Response.{{name}} = value` to assign instead of calling it as a function.',
|
|
48
|
+
},
|
|
49
|
+
schema: [],
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
create(context) {
|
|
53
|
+
const sourceCode = context.sourceCode;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
CallExpression(node) {
|
|
57
|
+
const callee = node.callee;
|
|
58
|
+
|
|
59
|
+
// Must be Platform.<NS>.<property>()
|
|
60
|
+
if (
|
|
61
|
+
callee.type !== 'MemberExpression' ||
|
|
62
|
+
callee.object.type !== 'MemberExpression' ||
|
|
63
|
+
callee.object.object.type !== 'Identifier' ||
|
|
64
|
+
callee.object.object.name !== 'Platform' ||
|
|
65
|
+
callee.object.property.type !== 'Identifier' ||
|
|
66
|
+
callee.property.type !== 'Identifier'
|
|
67
|
+
) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ns = callee.object.property.name.toLowerCase();
|
|
72
|
+
const propName = callee.property.name.toLowerCase();
|
|
73
|
+
const displayNs = callee.object.property.name;
|
|
74
|
+
const displayName = callee.property.name;
|
|
75
|
+
|
|
76
|
+
if (ns === 'response' && RESPONSE_PROPERTIES.has(propName)) {
|
|
77
|
+
if (node.arguments.length === 0) {
|
|
78
|
+
// Reading with parens — remove ()
|
|
79
|
+
context.report({
|
|
80
|
+
node,
|
|
81
|
+
messageId: 'propertyReadWithCall',
|
|
82
|
+
data: { ns: displayNs, name: displayName },
|
|
83
|
+
fix(fixer) {
|
|
84
|
+
return fixer.replaceText(node, sourceCode.getText(callee));
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
} else if (node.arguments.length === 1) {
|
|
88
|
+
// Setting via function call — convert to assignment
|
|
89
|
+
context.report({
|
|
90
|
+
node,
|
|
91
|
+
messageId: 'writablePropertySet',
|
|
92
|
+
data: { ns: displayNs, name: displayName },
|
|
93
|
+
fix(fixer) {
|
|
94
|
+
// Only safe when the call is a standalone expression statement
|
|
95
|
+
if (node.parent.type !== 'ExpressionStatement') {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const argText = sourceCode.getText(node.arguments[0]);
|
|
99
|
+
return fixer.replaceText(
|
|
100
|
+
node,
|
|
101
|
+
`${sourceCode.getText(callee)} = ${argText}`,
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
// >1 args — unusual, flag without a fix
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: 'writablePropertySet',
|
|
110
|
+
data: { ns: displayNs, name: displayName },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} else if (ns === 'request' && REQUEST_PROPERTIES.has(propName)) {
|
|
114
|
+
if (node.arguments.length === 0) {
|
|
115
|
+
// Reading with parens — remove ()
|
|
116
|
+
context.report({
|
|
117
|
+
node,
|
|
118
|
+
messageId: 'propertyReadWithCall',
|
|
119
|
+
data: { ns: displayNs, name: displayName },
|
|
120
|
+
fix(fixer) {
|
|
121
|
+
return fixer.replaceText(node, sourceCode.getText(callee));
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
// Attempting to set a read-only property — no fix
|
|
126
|
+
context.report({
|
|
127
|
+
node,
|
|
128
|
+
messageId: 'readOnlyPropertySet',
|
|
129
|
+
data: { name: displayName },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|