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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/docs/rules/amp/function-arity.md +53 -0
  4. package/docs/rules/amp/naming-convention.md +76 -0
  5. package/docs/rules/amp/no-deprecated-function.md +78 -0
  6. package/docs/rules/amp/no-email-excluded-function.md +69 -0
  7. package/docs/rules/amp/no-empty-block.md +59 -0
  8. package/docs/rules/amp/no-empty-then.md +62 -0
  9. package/docs/rules/amp/no-html-comment.md +67 -0
  10. package/docs/rules/amp/no-inline-statement.md +51 -0
  11. package/docs/rules/amp/no-js-line-comment.md +54 -0
  12. package/docs/rules/amp/no-loop-counter-assign.md +52 -0
  13. package/docs/rules/amp/no-nested-ampscript-delimiter.md +69 -0
  14. package/docs/rules/amp/no-nested-script-tag.md +58 -0
  15. package/docs/rules/amp/no-smart-quotes.md +60 -0
  16. package/docs/rules/amp/no-unknown-function.md +50 -0
  17. package/docs/rules/amp/no-var-redeclaration.md +52 -0
  18. package/docs/rules/amp/prefer-attribute-value.md +64 -0
  19. package/docs/rules/amp/require-rowcount-check.md +62 -0
  20. package/docs/rules/amp/require-variable-declaration.md +51 -0
  21. package/docs/rules/amp/set-requires-target.md +47 -0
  22. package/docs/rules/ssjs/cache-loop-length.md +59 -0
  23. package/docs/rules/ssjs/no-hardcoded-credentials.md +52 -0
  24. package/docs/rules/ssjs/no-switch-default.md +37 -0
  25. package/docs/rules/ssjs/no-treatascontent-injection.md +38 -0
  26. package/docs/rules/ssjs/no-unavailable-method.md +134 -0
  27. package/docs/rules/ssjs/no-unknown-core-method.md +46 -0
  28. package/docs/rules/ssjs/no-unknown-http-method.md +44 -0
  29. package/docs/rules/ssjs/no-unknown-platform-client-browser.md +44 -0
  30. package/docs/rules/ssjs/no-unknown-platform-function.md +44 -0
  31. package/docs/rules/ssjs/no-unknown-platform-request.md +43 -0
  32. package/docs/rules/ssjs/no-unknown-platform-response.md +43 -0
  33. package/docs/rules/ssjs/no-unknown-platform-variable.md +43 -0
  34. package/docs/rules/ssjs/no-unknown-wsproxy-method.md +46 -0
  35. package/docs/rules/ssjs/no-unsupported-syntax.md +83 -0
  36. package/docs/rules/ssjs/platform-function-arity.md +45 -0
  37. package/docs/rules/ssjs/prefer-parsejson-safe-arg.md +42 -0
  38. package/docs/rules/ssjs/prefer-platform-load-version.md +68 -0
  39. package/docs/rules/ssjs/require-hasownproperty.md +57 -0
  40. package/docs/rules/ssjs/require-platform-load-order.md +46 -0
  41. package/docs/rules/ssjs/require-platform-load.md +57 -0
  42. package/package.json +52 -0
  43. package/src/ampscript-parser.js +159 -0
  44. package/src/ampscript-processor.js +132 -0
  45. package/src/index.js +355 -0
  46. package/src/processor.js +147 -0
  47. package/src/rules/amp/function-arity.js +51 -0
  48. package/src/rules/amp/naming-convention.js +58 -0
  49. package/src/rules/amp/no-deprecated-function.js +75 -0
  50. package/src/rules/amp/no-email-excluded-function.js +72 -0
  51. package/src/rules/amp/no-empty-block.js +37 -0
  52. package/src/rules/amp/no-empty-then.js +34 -0
  53. package/src/rules/amp/no-html-comment.js +57 -0
  54. package/src/rules/amp/no-inline-statement.js +45 -0
  55. package/src/rules/amp/no-js-line-comment.js +54 -0
  56. package/src/rules/amp/no-loop-counter-assign.js +71 -0
  57. package/src/rules/amp/no-nested-ampscript-delimiter.js +102 -0
  58. package/src/rules/amp/no-nested-script-tag.js +65 -0
  59. package/src/rules/amp/no-smart-quotes.js +78 -0
  60. package/src/rules/amp/no-unknown-function.js +31 -0
  61. package/src/rules/amp/no-var-redeclaration.js +36 -0
  62. package/src/rules/amp/prefer-attribute-value.js +94 -0
  63. package/src/rules/amp/require-rowcount-check.js +116 -0
  64. package/src/rules/amp/require-variable-declaration.js +49 -0
  65. package/src/rules/amp/set-requires-target.js +27 -0
  66. package/src/rules/ssjs/cache-loop-length.js +92 -0
  67. package/src/rules/ssjs/no-hardcoded-credentials.js +66 -0
  68. package/src/rules/ssjs/no-switch-default.js +32 -0
  69. package/src/rules/ssjs/no-treatascontent-injection.js +76 -0
  70. package/src/rules/ssjs/no-unavailable-method.js +190 -0
  71. package/src/rules/ssjs/no-unknown-core-method.js +103 -0
  72. package/src/rules/ssjs/no-unknown-http-method.js +41 -0
  73. package/src/rules/ssjs/no-unknown-platform-client-browser.js +50 -0
  74. package/src/rules/ssjs/no-unknown-platform-function.js +49 -0
  75. package/src/rules/ssjs/no-unknown-platform-request.js +50 -0
  76. package/src/rules/ssjs/no-unknown-platform-response.js +50 -0
  77. package/src/rules/ssjs/no-unknown-platform-variable.js +50 -0
  78. package/src/rules/ssjs/no-unknown-wsproxy-method.js +71 -0
  79. package/src/rules/ssjs/no-unsupported-syntax.js +122 -0
  80. package/src/rules/ssjs/platform-function-arity.js +70 -0
  81. package/src/rules/ssjs/prefer-parsejson-safe-arg.js +94 -0
  82. package/src/rules/ssjs/prefer-platform-load-version.js +96 -0
  83. package/src/rules/ssjs/require-hasownproperty.js +142 -0
  84. package/src/rules/ssjs/require-platform-load-order.js +112 -0
  85. package/src/rules/ssjs/require-platform-load.js +124 -0
  86. package/src/ssjs-processor.js +48 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Rule: no-treatascontent-injection
3
+ *
4
+ * Flags string concatenation inside TreatAsContent() calls. When dynamic
5
+ * values are concatenated into the AMPscript string, an attacker (or
6
+ * unexpected data) can inject arbitrary AMPscript code.
7
+ *
8
+ * Safe pattern: use Variable.SetValue() to pass values into AMPscript
9
+ * variables, then reference those variables inside TreatAsContent().
10
+ */
11
+
12
+ export default {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description:
17
+ 'Disallow string concatenation in TreatAsContent() to prevent AMPscript injection',
18
+ },
19
+ messages: {
20
+ injection:
21
+ 'Concatenating dynamic values into TreatAsContent() risks AMPscript injection. ' +
22
+ "Use Variable.SetValue() to pass values into AMPscript variables, then reference " +
23
+ "them with @varName inside the TreatAsContent string.",
24
+ },
25
+ schema: [],
26
+ },
27
+
28
+ create(context) {
29
+ return {
30
+ CallExpression(node) {
31
+ if (!isTreatAsContentCall(node)) return;
32
+ if (node.arguments.length === 0) return;
33
+
34
+ const arg = node.arguments[0];
35
+ if (containsConcatenation(arg)) {
36
+ context.report({ node: arg, messageId: 'injection' });
37
+ }
38
+ },
39
+ };
40
+ },
41
+ };
42
+
43
+ function isTreatAsContentCall(node) {
44
+ const callee = node.callee;
45
+
46
+ // TreatAsContent(...)
47
+ if (callee.type === 'Identifier' && callee.name === 'TreatAsContent') {
48
+ return true;
49
+ }
50
+
51
+ // Platform.Function.TreatAsContent(...)
52
+ if (
53
+ callee.type === 'MemberExpression' &&
54
+ callee.property.type === 'Identifier' &&
55
+ callee.property.name === 'TreatAsContent' &&
56
+ callee.object.type === 'MemberExpression' &&
57
+ callee.object.property.type === 'Identifier' &&
58
+ callee.object.property.name === 'Function' &&
59
+ callee.object.object.type === 'Identifier' &&
60
+ callee.object.object.name === 'Platform'
61
+ ) {
62
+ return true;
63
+ }
64
+
65
+ return false;
66
+ }
67
+
68
+ function containsConcatenation(node) {
69
+ if (node.type === 'BinaryExpression' && node.operator === '+') {
70
+ return true;
71
+ }
72
+ if (node.type === 'TemplateLiteral' && node.expressions.length > 0) {
73
+ return true;
74
+ }
75
+ return false;
76
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Rule: no-unavailable-method
3
+ *
4
+ * Flags calls to Array methods that are either missing or broken in SFMC's
5
+ * legacy ECMAScript engine (Jint/ECMAScript 3) and suggests inserting a
6
+ * polyfill at the end of the file.
7
+ *
8
+ * - category 'unavailable': method does not exist; calling it throws a runtime error.
9
+ * - category 'broken': method exists natively but returns incorrect results.
10
+ *
11
+ * No auto-fix is applied because prototype assignments are not hoisted —
12
+ * the suggestion inserts the polyfill at the end of the file, and users
13
+ * should verify placement before the first call (or load via Content Block).
14
+ */
15
+
16
+ import { polyfillByPrototypeName, polyfillByStaticName } from 'ssjs-data';
17
+
18
+ // SFMC-specific top-level objects whose methods must never be flagged even
19
+ // when their method names collide with Array.prototype (e.g. Platform.find).
20
+ const SFMC_RECEIVERS = new Set([
21
+ 'Platform',
22
+ 'HTTP',
23
+ 'WSProxy',
24
+ 'Variable',
25
+ 'Request',
26
+ 'Response',
27
+ 'Automation',
28
+ 'DataExtension',
29
+ 'Subscriber',
30
+ 'Email',
31
+ 'List',
32
+ 'Query',
33
+ ]);
34
+
35
+ export default {
36
+ meta: {
37
+ type: 'problem',
38
+ hasSuggestions: true,
39
+ docs: {
40
+ description:
41
+ 'Flag Array methods unavailable or broken in SFMC SSJS and suggest polyfills',
42
+ },
43
+ messages: {
44
+ unavailable:
45
+ "'{{owner}}.{{method}}' is not available in SFMC SSJS (ECMAScript 3). " +
46
+ 'Add a polyfill before using it.',
47
+ broken:
48
+ "'{{owner}}.{{method}}' exists in SFMC SSJS but produces incorrect results. " +
49
+ 'Add a polyfill to get the correct behavior.',
50
+ addPolyfill: "Insert '{{owner}}.{{method}}' polyfill at end of file",
51
+ },
52
+ schema: [
53
+ {
54
+ type: 'object',
55
+ properties: {
56
+ ignore: {
57
+ type: 'array',
58
+ items: { type: 'string' },
59
+ uniqueItems: true,
60
+ description: 'Method names to ignore (e.g. when loaded via Content Block).',
61
+ },
62
+ },
63
+ additionalProperties: false,
64
+ },
65
+ ],
66
+ },
67
+
68
+ create(context) {
69
+ const options = context.options[0] || {};
70
+ const ignored = new Set(options.ignore || []);
71
+
72
+ // Track which methods already have a polyfill assignment in this file.
73
+ // We detect: Array.prototype.X = function ... or Array.X = function ...
74
+ const alreadyPolyfilled = new Set();
75
+
76
+ // Defer reports until Program:exit so we can suppress ones that have
77
+ // a polyfill definition earlier in the file.
78
+ const pendingReports = [];
79
+
80
+ function isAlreadyPolyfilled(method) {
81
+ return alreadyPolyfilled.has(method);
82
+ }
83
+
84
+ function buildSuggestFix(entry) {
85
+ return function fix(fixer) {
86
+ const src = context.sourceCode;
87
+ const end = src.ast.range[1];
88
+ return fixer.insertTextAfterRange([end, end], '\n\n' + entry.polyfill);
89
+ };
90
+ }
91
+
92
+ return {
93
+ // Detect polyfill assignments already present in the file:
94
+ // Array.prototype.map = ... / Array.isArray = ... / String.prototype.trim = ...
95
+ AssignmentExpression(node) {
96
+ const left = node.left;
97
+ if (left.type !== 'MemberExpression') return;
98
+
99
+ const obj = left.object;
100
+ const prop = left.property;
101
+ if (prop.type !== 'Identifier') return;
102
+
103
+ // Array.X = ...
104
+ if (obj.type === 'Identifier' && obj.name === 'Array') {
105
+ alreadyPolyfilled.add(prop.name);
106
+ return;
107
+ }
108
+
109
+ // Array.prototype.X = ... or String.prototype.X = ...
110
+ if (
111
+ obj.type === 'MemberExpression' &&
112
+ obj.object.type === 'Identifier' &&
113
+ (obj.object.name === 'Array' || obj.object.name === 'String') &&
114
+ obj.property.type === 'Identifier' &&
115
+ obj.property.name === 'prototype'
116
+ ) {
117
+ alreadyPolyfilled.add(prop.name);
118
+ }
119
+ },
120
+
121
+ CallExpression(node) {
122
+ const callee = node.callee;
123
+ if (callee.type !== 'MemberExpression') return;
124
+
125
+ const prop = callee.property;
126
+ if (prop.type !== 'Identifier') return;
127
+
128
+ const methodName = prop.name;
129
+ const receiver = callee.object;
130
+
131
+ // ── Static Array methods: Array.isArray(), Array.of() ──────────
132
+ if (
133
+ receiver.type === 'Identifier' &&
134
+ receiver.name === 'Array' &&
135
+ polyfillByStaticName.has(methodName)
136
+ ) {
137
+ if (ignored.has(methodName)) return;
138
+ const entry = polyfillByStaticName.get(methodName);
139
+ pendingReports.push({ node: prop, entry });
140
+ return;
141
+ }
142
+
143
+ // ── Prototype methods ─────────────────────────────────────────
144
+ if (!polyfillByPrototypeName.has(methodName)) return;
145
+ if (ignored.has(methodName)) return;
146
+
147
+ const entry = polyfillByPrototypeName.get(methodName);
148
+
149
+ // Skip known SFMC top-level objects to avoid false positives.
150
+ if (receiver.type === 'Identifier' && SFMC_RECEIVERS.has(receiver.name)) return;
151
+
152
+ // For methods that also exist on String.prototype in ES3
153
+ // (indexOf, lastIndexOf), only flag when the receiver is
154
+ // a literal array or the call is chained from an array method
155
+ // we already know about — otherwise we'd falsely flag string usage.
156
+ if (entry.ambiguousWithString) {
157
+ const isDefinitelyArray =
158
+ receiver.type === 'ArrayExpression' ||
159
+ // arr.filter(...).indexOf(...) — left side is a CallExpression
160
+ // whose callee refers to another Array method
161
+ (receiver.type === 'CallExpression' &&
162
+ receiver.callee.type === 'MemberExpression' &&
163
+ polyfillByPrototypeName.has(receiver.callee.property.name));
164
+ if (!isDefinitelyArray) return;
165
+ }
166
+
167
+ pendingReports.push({ node: prop, entry });
168
+ },
169
+
170
+ 'Program:exit'() {
171
+ for (const { node, entry } of pendingReports) {
172
+ if (isAlreadyPolyfilled(entry.method)) continue;
173
+
174
+ context.report({
175
+ node,
176
+ messageId: entry.category,
177
+ data: { owner: entry.owner, method: entry.method },
178
+ suggest: [
179
+ {
180
+ messageId: 'addPolyfill',
181
+ data: { owner: entry.owner, method: entry.method },
182
+ fix: buildSuggestFix(entry),
183
+ },
184
+ ],
185
+ });
186
+ }
187
+ },
188
+ };
189
+ },
190
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Rule: no-unknown-core-method
3
+ *
4
+ * Flags calls to Core library object methods that don't exist on that
5
+ * object type, when the object type can be inferred from an Init call.
6
+ *
7
+ * Example:
8
+ * var de = DataExtension.Init("MyDE");
9
+ * de.Foo(); // error: 'Foo' is not a method of DataExtension
10
+ */
11
+
12
+ import { coreObjectLookup, coreObjectNames } from 'ssjs-data';
13
+
14
+ export default {
15
+ meta: {
16
+ type: 'problem',
17
+ docs: {
18
+ description: "Disallow calls to methods that don't exist on a Core library object",
19
+ },
20
+ messages: {
21
+ unknownMethod:
22
+ "'{{method}}' is not a known method of {{objectType}}. Available methods: {{available}}.",
23
+ },
24
+ schema: [],
25
+ },
26
+
27
+ create(context) {
28
+ const variableTypes = new Map();
29
+
30
+ return {
31
+ VariableDeclaration(node) {
32
+ for (const decl of node.declarations) {
33
+ if (!decl.init || !decl.id || decl.id.type !== 'Identifier') continue;
34
+ const coreType = getCoreInitType(decl.init);
35
+ if (coreType) {
36
+ variableTypes.set(decl.id.name, coreType);
37
+ }
38
+ }
39
+ },
40
+
41
+ AssignmentExpression(node) {
42
+ if (node.left.type !== 'Identifier') return;
43
+ const coreType = getCoreInitType(node.right);
44
+ if (coreType) {
45
+ variableTypes.set(node.left.name, coreType);
46
+ }
47
+ },
48
+
49
+ CallExpression(node) {
50
+ const callee = node.callee;
51
+ if (callee.type !== 'MemberExpression') return;
52
+ if (callee.object.type !== 'Identifier') return;
53
+ if (callee.property.type !== 'Identifier') return;
54
+
55
+ const objectName = callee.object.name;
56
+ const method = callee.property.name;
57
+
58
+ const coreType = variableTypes.get(objectName);
59
+ if (!coreType) return;
60
+
61
+ const objectDef = coreObjectLookup.get(coreType);
62
+ if (!objectDef) return;
63
+
64
+ const knownMethods = new Set(objectDef.methods.map((m) => m.toLowerCase()));
65
+ if (!knownMethods.has(method.toLowerCase())) {
66
+ context.report({
67
+ node: callee.property,
68
+ messageId: 'unknownMethod',
69
+ data: {
70
+ method,
71
+ objectType: coreType,
72
+ available: objectDef.methods.join(', '),
73
+ },
74
+ });
75
+ }
76
+ },
77
+ };
78
+ },
79
+ };
80
+
81
+ function getCoreInitType(node) {
82
+ if (!node || node.type !== 'CallExpression') return null;
83
+ const callee = node.callee;
84
+ if (callee.type !== 'MemberExpression') return null;
85
+
86
+ if (callee.property.type === 'Identifier' && callee.property.name === 'Init') {
87
+ if (callee.object.type === 'Identifier' && coreObjectNames.has(callee.object.name)) {
88
+ return callee.object.name;
89
+ }
90
+ if (
91
+ callee.object.type === 'MemberExpression' &&
92
+ callee.object.object.type === 'Identifier' &&
93
+ callee.object.property.type === 'Identifier'
94
+ ) {
95
+ const fullName = `${callee.object.object.name}.${callee.object.property.name}`;
96
+ if (coreObjectNames.has(fullName)) {
97
+ return fullName;
98
+ }
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Rule: no-unknown-http-method
3
+ *
4
+ * Flags calls to HTTP.* where the method name does not exist
5
+ * in the known SFMC HTTP method catalog.
6
+ */
7
+
8
+ import { httpMethodNames } from 'ssjs-data';
9
+
10
+ export default {
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'Disallow calls to unknown HTTP object methods',
15
+ },
16
+ messages: {
17
+ unknownMethod: "'HTTP.{{name}}' is not a recognized SFMC HTTP method.",
18
+ },
19
+ schema: [],
20
+ },
21
+
22
+ create(context) {
23
+ return {
24
+ CallExpression(node) {
25
+ const callee = node.callee;
26
+ if (callee.type !== 'MemberExpression') return;
27
+ if (callee.object.type !== 'Identifier' || callee.object.name !== 'HTTP') return;
28
+ if (callee.property.type !== 'Identifier') return;
29
+
30
+ const methodName = callee.property.name;
31
+ if (!httpMethodNames.has(methodName.toLowerCase())) {
32
+ context.report({
33
+ node: callee.property,
34
+ messageId: 'unknownMethod',
35
+ data: { name: methodName },
36
+ });
37
+ }
38
+ },
39
+ };
40
+ },
41
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Rule: no-unknown-platform-client-browser
3
+ *
4
+ * Flags calls to Platform.ClientBrowser.* where the method name does not
5
+ * exist in the known SFMC Platform.ClientBrowser catalog.
6
+ */
7
+
8
+ import { PLATFORM_CLIENT_BROWSER_METHODS } from 'ssjs-data';
9
+
10
+ const knownMethods = new Set(PLATFORM_CLIENT_BROWSER_METHODS.map((m) => m.name.toLowerCase()));
11
+
12
+ export default {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'Disallow calls to unknown Platform.ClientBrowser methods',
17
+ },
18
+ messages: {
19
+ unknownMethod: "'Platform.ClientBrowser.{{name}}' is not a recognized method.",
20
+ },
21
+ schema: [],
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ const callee = node.callee;
28
+ if (callee.type !== 'MemberExpression') return;
29
+
30
+ if (
31
+ callee.object.type === 'MemberExpression' &&
32
+ callee.object.object.type === 'Identifier' &&
33
+ callee.object.object.name === 'Platform' &&
34
+ callee.object.property.type === 'Identifier' &&
35
+ callee.object.property.name === 'ClientBrowser' &&
36
+ callee.property.type === 'Identifier'
37
+ ) {
38
+ const methodName = callee.property.name;
39
+ if (!knownMethods.has(methodName.toLowerCase())) {
40
+ context.report({
41
+ node: callee.property,
42
+ messageId: 'unknownMethod',
43
+ data: { name: methodName },
44
+ });
45
+ }
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Rule: no-unknown-platform-function
3
+ *
4
+ * Flags calls to Platform.Function.* where the method name does not
5
+ * exist in the known SFMC Platform.Function catalog.
6
+ */
7
+
8
+ import { platformFunctionNames } from 'ssjs-data';
9
+
10
+ export default {
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'Disallow calls to unknown Platform.Function methods',
15
+ },
16
+ messages: {
17
+ unknownFunction:
18
+ "'Platform.Function.{{name}}' is not a recognized SFMC Platform function.",
19
+ },
20
+ schema: [],
21
+ },
22
+
23
+ create(context) {
24
+ return {
25
+ CallExpression(node) {
26
+ const callee = node.callee;
27
+ if (callee.type !== 'MemberExpression') return;
28
+
29
+ if (
30
+ callee.object.type === 'MemberExpression' &&
31
+ callee.object.object.type === 'Identifier' &&
32
+ callee.object.object.name === 'Platform' &&
33
+ callee.object.property.type === 'Identifier' &&
34
+ callee.object.property.name === 'Function' &&
35
+ callee.property.type === 'Identifier'
36
+ ) {
37
+ const methodName = callee.property.name;
38
+ if (!platformFunctionNames.has(methodName.toLowerCase())) {
39
+ context.report({
40
+ node: callee.property,
41
+ messageId: 'unknownFunction',
42
+ data: { name: methodName },
43
+ });
44
+ }
45
+ }
46
+ },
47
+ };
48
+ },
49
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Rule: no-unknown-platform-request
3
+ *
4
+ * Flags calls to Platform.Request.* where the method name does not
5
+ * exist in the known SFMC Platform.Request catalog.
6
+ */
7
+
8
+ import { PLATFORM_REQUEST_METHODS } from 'ssjs-data';
9
+
10
+ const knownMethods = new Set(PLATFORM_REQUEST_METHODS.map((m) => m.name.toLowerCase()));
11
+
12
+ export default {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'Disallow calls to unknown Platform.Request methods',
17
+ },
18
+ messages: {
19
+ unknownMethod: "'Platform.Request.{{name}}' is not a recognized method.",
20
+ },
21
+ schema: [],
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ const callee = node.callee;
28
+ if (callee.type !== 'MemberExpression') return;
29
+
30
+ if (
31
+ callee.object.type === 'MemberExpression' &&
32
+ callee.object.object.type === 'Identifier' &&
33
+ callee.object.object.name === 'Platform' &&
34
+ callee.object.property.type === 'Identifier' &&
35
+ callee.object.property.name === 'Request' &&
36
+ callee.property.type === 'Identifier'
37
+ ) {
38
+ const methodName = callee.property.name;
39
+ if (!knownMethods.has(methodName.toLowerCase())) {
40
+ context.report({
41
+ node: callee.property,
42
+ messageId: 'unknownMethod',
43
+ data: { name: methodName },
44
+ });
45
+ }
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Rule: no-unknown-platform-response
3
+ *
4
+ * Flags calls to Platform.Response.* where the method name does not
5
+ * exist in the known SFMC Platform.Response catalog.
6
+ */
7
+
8
+ import { PLATFORM_RESPONSE_METHODS } from 'ssjs-data';
9
+
10
+ const knownMethods = new Set(PLATFORM_RESPONSE_METHODS.map((m) => m.name.toLowerCase()));
11
+
12
+ export default {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'Disallow calls to unknown Platform.Response methods',
17
+ },
18
+ messages: {
19
+ unknownMethod: "'Platform.Response.{{name}}' is not a recognized method.",
20
+ },
21
+ schema: [],
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ const callee = node.callee;
28
+ if (callee.type !== 'MemberExpression') return;
29
+
30
+ if (
31
+ callee.object.type === 'MemberExpression' &&
32
+ callee.object.object.type === 'Identifier' &&
33
+ callee.object.object.name === 'Platform' &&
34
+ callee.object.property.type === 'Identifier' &&
35
+ callee.object.property.name === 'Response' &&
36
+ callee.property.type === 'Identifier'
37
+ ) {
38
+ const methodName = callee.property.name;
39
+ if (!knownMethods.has(methodName.toLowerCase())) {
40
+ context.report({
41
+ node: callee.property,
42
+ messageId: 'unknownMethod',
43
+ data: { name: methodName },
44
+ });
45
+ }
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Rule: no-unknown-platform-variable
3
+ *
4
+ * Flags calls to Platform.Variable.* where the method name does not
5
+ * exist in the known SFMC Platform.Variable catalog.
6
+ */
7
+
8
+ import { PLATFORM_VARIABLE_METHODS } from 'ssjs-data';
9
+
10
+ const knownMethods = new Set(PLATFORM_VARIABLE_METHODS.map((m) => m.name.toLowerCase()));
11
+
12
+ export default {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'Disallow calls to unknown Platform.Variable methods',
17
+ },
18
+ messages: {
19
+ unknownMethod: "'Platform.Variable.{{name}}' is not a recognized method.",
20
+ },
21
+ schema: [],
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ const callee = node.callee;
28
+ if (callee.type !== 'MemberExpression') return;
29
+
30
+ if (
31
+ callee.object.type === 'MemberExpression' &&
32
+ callee.object.object.type === 'Identifier' &&
33
+ callee.object.object.name === 'Platform' &&
34
+ callee.object.property.type === 'Identifier' &&
35
+ callee.object.property.name === 'Variable' &&
36
+ callee.property.type === 'Identifier'
37
+ ) {
38
+ const methodName = callee.property.name;
39
+ if (!knownMethods.has(methodName.toLowerCase())) {
40
+ context.report({
41
+ node: callee.property,
42
+ messageId: 'unknownMethod',
43
+ data: { name: methodName },
44
+ });
45
+ }
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };