eslint-plugin-sfmc 2.6.1 → 3.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.
Files changed (43) hide show
  1. package/README.md +72 -63
  2. package/docs/rules/hbs/helper-arity.md +55 -0
  3. package/docs/rules/hbs/helper-too-new-for-target.md +62 -0
  4. package/docs/rules/hbs/no-unknown-binding.md +49 -0
  5. package/docs/rules/hbs/no-unknown-helper.md +60 -0
  6. package/docs/rules/hbs/no-unsupported-construct.md +65 -0
  7. package/package.json +14 -12
  8. package/src/ampscript-parser.js +1 -1
  9. package/src/ampscript-processor.js +22 -16
  10. package/src/handlebars-parser.js +195 -0
  11. package/src/index.js +90 -11
  12. package/src/processor.js +76 -16
  13. package/src/rules/amp/{arg-types.js → argument-types.js} +16 -12
  14. package/src/rules/amp/function-arity.js +9 -9
  15. package/src/rules/amp/no-inline-statement.js +5 -5
  16. package/src/rules/amp/no-loop-counter-assign.js +7 -7
  17. package/src/rules/amp/no-nested-ampscript-delimiter.js +1 -1
  18. package/src/rules/amp/no-smart-quotes.js +18 -18
  19. package/src/rules/amp/no-unknown-function.js +16 -3
  20. package/src/rules/amp/prefer-attribute-value.js +7 -10
  21. package/src/rules/amp/require-rowcount-check.js +12 -15
  22. package/src/rules/amp/require-variable-declaration.js +3 -3
  23. package/src/rules/hbs/_shared.js +116 -0
  24. package/src/rules/hbs/helper-arity.js +97 -0
  25. package/src/rules/hbs/helper-too-new-for-target.js +94 -0
  26. package/src/rules/hbs/no-unknown-binding.js +74 -0
  27. package/src/rules/hbs/no-unknown-helper.js +79 -0
  28. package/src/rules/hbs/no-unsupported-construct.js +70 -0
  29. package/src/rules/ssjs/cache-loop-length.js +14 -17
  30. package/src/rules/ssjs/no-deprecated-function.js +8 -8
  31. package/src/rules/ssjs/no-hardcoded-credentials.js +3 -6
  32. package/src/rules/ssjs/no-property-call.js +6 -9
  33. package/src/rules/ssjs/no-treatascontent-injection.js +6 -13
  34. package/src/rules/ssjs/no-unavailable-method.js +22 -22
  35. package/src/rules/ssjs/no-unknown-function.js +16 -12
  36. package/src/rules/ssjs/no-unsupported-syntax.js +5 -5
  37. package/src/rules/ssjs/{prefer-parsejson-safe-arg.js → prefer-parsejson-safe-argument.js} +13 -21
  38. package/src/rules/ssjs/require-hasownproperty.js +54 -42
  39. package/src/rules/ssjs/require-platform-load-order.js +1 -1
  40. package/src/rules/ssjs/require-platform-load.js +6 -6
  41. package/src/rules/ssjs/{ssjs-arg-types.js → ssjs-argument-types.js} +59 -46
  42. package/src/rules/ssjs/ssjs-core-method-arity.js +19 -15
  43. /package/src/rules/amp/{no-var-redeclaration.js → no-variable-redeclaration.js} +0 -0
@@ -0,0 +1,79 @@
1
+ import { isHelper, helperNames } from 'handlebars-data';
2
+
3
+ import { simpleHelperName, isInvocation, closestMatch } from './_shared.js';
4
+
5
+ /**
6
+ * Flags Handlebars helper invocations whose name is not part of the Marketing
7
+ * Cloud Next catalog. The MCN engine is locked down and cannot register custom
8
+ * helpers, so any unknown helper is reported.
9
+ *
10
+ * Mirrors the `handlebars/unknown-helper` diagnostic emitted by
11
+ * sfmc-language-lsp. A bare `{{foo}}` mustache with no arguments is treated as a
12
+ * data binding (not a helper) and is not flagged here.
13
+ */
14
+ export default {
15
+ meta: {
16
+ type: 'problem',
17
+ docs: {
18
+ description:
19
+ 'Disallow Handlebars helper invocations that are not part of the Marketing Cloud Next catalog',
20
+ recommended: true,
21
+ },
22
+ messages: {
23
+ unknownHelper:
24
+ "Unknown Handlebars {{kind}} '{{name}}'. It is not part of the Marketing Cloud Next catalog, and the MCN engine cannot register custom helpers.",
25
+ unknownHelperSuggest:
26
+ "Unknown Handlebars {{kind}} '{{name}}'. It is not part of the Marketing Cloud Next catalog, and the MCN engine cannot register custom helpers. Did you mean '{{suggestion}}'?",
27
+ },
28
+ schema: [],
29
+ },
30
+
31
+ create(context) {
32
+ /**
33
+ * Reports an unknown helper, attaching a "did you mean" suggestion when
34
+ * a close catalog match exists.
35
+ *
36
+ * @param {object} node - The offending AST node.
37
+ * @param {string} name - The unknown helper name.
38
+ * @param {boolean} isBlock - True when the node is a block helper.
39
+ * @returns {void}
40
+ */
41
+ function reportUnknown(node, name, isBlock) {
42
+ const kind = isBlock ? 'block helper' : 'helper';
43
+ const suggestion = closestMatch(name, helperNames);
44
+ if (suggestion) {
45
+ context.report({
46
+ node,
47
+ messageId: 'unknownHelperSuggest',
48
+ data: { kind, name, suggestion },
49
+ });
50
+ } else {
51
+ context.report({ node, messageId: 'unknownHelper', data: { kind, name } });
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Checks an inline mustache or subexpression invocation.
57
+ *
58
+ * @param {object} node - The AST node.
59
+ * @returns {void}
60
+ */
61
+ function checkInline(node) {
62
+ const helperName = simpleHelperName(node.path);
63
+ if (helperName && isInvocation(node) && !isHelper(helperName)) {
64
+ reportUnknown(node, helperName, false);
65
+ }
66
+ }
67
+
68
+ return {
69
+ MustacheStatement: checkInline,
70
+ SubExpression: checkInline,
71
+ HbsBlockStatement(node) {
72
+ const helperName = simpleHelperName(node.path);
73
+ if (helperName && !isHelper(helperName)) {
74
+ reportUnknown(node, helperName, true);
75
+ }
76
+ },
77
+ };
78
+ },
79
+ };
@@ -0,0 +1,70 @@
1
+ import { unsupportedByNodeType } from 'handlebars-data';
2
+
3
+ import { simpleHelperName } from './_shared.js';
4
+
5
+ /**
6
+ * Flags Handlebars constructs that the locked-down Marketing Cloud Next engine
7
+ * cannot support: partials, partial blocks, decorators, inline partials, and the
8
+ * handlebars.js-only `{{log}}` debugging helper.
9
+ *
10
+ * Mirrors the `handlebars/unsupported-construct` diagnostic emitted by
11
+ * sfmc-language-lsp.
12
+ */
13
+
14
+ /** AST node types that may carry an unsupported construct. */
15
+ const NODE_TYPES = unsupportedByNodeType.keys().toArray();
16
+
17
+ // The handlebars-data messages contain literal `{{> ...}}`, `{{log}}`, etc. as
18
+ // examples. ESLint treats `{{...}}` in a message template as an interpolation
19
+ // placeholder, so the literal text is passed through a single `{{text}}`
20
+ // placeholder instead of being baked into the template.
21
+ /** messageId per construct id → passthrough template, declared statically. */
22
+ const MESSAGES = Object.fromEntries(
23
+ unsupportedByNodeType
24
+ .values()
25
+ .toArray()
26
+ .flat()
27
+ .map((entry) => [entry.id, '{{text}}']),
28
+ );
29
+
30
+ export default {
31
+ meta: {
32
+ type: 'problem',
33
+ docs: {
34
+ description:
35
+ 'Disallow Handlebars constructs unsupported by the Marketing Cloud Next engine (partials, decorators, log)',
36
+ recommended: true,
37
+ },
38
+ messages: MESSAGES,
39
+ schema: [],
40
+ },
41
+
42
+ create(context) {
43
+ /**
44
+ * Reports the first unsupported-construct entry matching a node.
45
+ *
46
+ * @param {object} node - The Handlebars AST node.
47
+ * @returns {void}
48
+ */
49
+ function check(node) {
50
+ const candidates = unsupportedByNodeType.get(node.type);
51
+ if (!candidates) {
52
+ return;
53
+ }
54
+ const helperName = simpleHelperName(node.path);
55
+ for (const entry of candidates) {
56
+ if (entry.helperName !== null && entry.helperName !== helperName) {
57
+ continue;
58
+ }
59
+ context.report({ node, messageId: entry.id, data: { text: entry.message } });
60
+ return;
61
+ }
62
+ }
63
+
64
+ const visitors = {};
65
+ for (const nodeType of NODE_TYPES) {
66
+ visitors[nodeType] = check;
67
+ }
68
+ return visitors;
69
+ },
70
+ };
@@ -34,23 +34,23 @@ export default {
34
34
  return;
35
35
  }
36
36
 
37
- let lengthExpr = null;
37
+ let lengthExpression = null;
38
38
  if (containsMemberLength(test.right)) {
39
- lengthExpr = test.right;
39
+ lengthExpression = test.right;
40
40
  } else if (containsMemberLength(test.left)) {
41
- lengthExpr = test.left;
41
+ lengthExpression = test.left;
42
42
  }
43
43
 
44
- if (!lengthExpr) {
44
+ if (!lengthExpression) {
45
45
  return;
46
46
  }
47
47
 
48
- const fix = buildCacheFix(node, lengthExpr, context);
48
+ const fix = buildCacheFix(node, lengthExpression, context);
49
49
 
50
50
  context.report({
51
51
  node: test,
52
52
  messageId: 'cacheLength',
53
- ...(fix ? { fix } : {}),
53
+ ...(fix && { fix }),
54
54
  });
55
55
  },
56
56
  };
@@ -61,17 +61,14 @@ function containsMemberLength(node) {
61
61
  if (!node) {
62
62
  return false;
63
63
  }
64
- if (
64
+ return (
65
65
  node.type === 'MemberExpression' &&
66
66
  node.property.type === 'Identifier' &&
67
67
  node.property.name === 'length'
68
- ) {
69
- return true;
70
- }
71
- return false;
68
+ );
72
69
  }
73
70
 
74
- function buildCacheFix(forNode, lengthExpr, context) {
71
+ function buildCacheFix(forNode, lengthExpression, context) {
75
72
  const init = forNode.init;
76
73
  // Only fix when init is a VariableDeclaration so we can safely
77
74
  // append a new declarator without restructuring the loop header.
@@ -79,14 +76,14 @@ function buildCacheFix(forNode, lengthExpr, context) {
79
76
  return null;
80
77
  }
81
78
 
82
- const objText = context.sourceCode.getText(lengthExpr.object);
83
- const lenVar = '_len';
79
+ const objectText = context.sourceCode.getText(lengthExpression.object);
80
+ const lengthVariable = '_len';
84
81
 
85
82
  return function fix(fixer) {
86
- const lastDecl = init.declarations.at(-1);
83
+ const lastDeclaration = init.declarations.at(-1);
87
84
  return [
88
- fixer.insertTextAfter(lastDecl, `, ${lenVar} = ${objText}.length`),
89
- fixer.replaceText(lengthExpr, lenVar),
85
+ fixer.insertTextAfter(lastDeclaration, `, ${lengthVariable} = ${objectText}.length`),
86
+ fixer.replaceText(lengthExpression, lengthVariable),
90
87
  ];
91
88
  };
92
89
  }
@@ -62,20 +62,20 @@ export default {
62
62
 
63
63
  return {
64
64
  VariableDeclaration(node) {
65
- for (const decl of node.declarations) {
65
+ for (const declaration of node.declarations) {
66
66
  if (
67
- decl.init &&
68
- decl.id &&
69
- decl.id.type === 'Identifier' &&
70
- isContentAreaObjInit(decl.init)
67
+ declaration.init &&
68
+ declaration.id &&
69
+ declaration.id.type === 'Identifier' &&
70
+ isContentAreaObjectInit(declaration.init)
71
71
  ) {
72
- contentAreaVariables.add(decl.id.name);
72
+ contentAreaVariables.add(declaration.id.name);
73
73
  }
74
74
  }
75
75
  },
76
76
 
77
77
  AssignmentExpression(node) {
78
- if (node.left.type === 'Identifier' && isContentAreaObjInit(node.right)) {
78
+ if (node.left.type === 'Identifier' && isContentAreaObjectInit(node.right)) {
79
79
  contentAreaVariables.add(node.left.name);
80
80
  }
81
81
  },
@@ -166,7 +166,7 @@ export default {
166
166
  * @param {import('eslint').Rule.Node} node - AST node to inspect
167
167
  * @returns {boolean} true when the node is a ContentAreaObj.Init call
168
168
  */
169
- function isContentAreaObjInit(node) {
169
+ function isContentAreaObjectInit(node) {
170
170
  if (!node || node.type !== 'CallExpression') {
171
171
  return false;
172
172
  }
@@ -29,9 +29,7 @@ export default {
29
29
  CallExpression(node) {
30
30
  const callee = node.callee;
31
31
 
32
- let functionName = null;
33
-
34
- if (
32
+ const functionName =
35
33
  callee.type === 'MemberExpression' &&
36
34
  callee.object.type === 'MemberExpression' &&
37
35
  callee.object.object.type === 'Identifier' &&
@@ -39,9 +37,8 @@ export default {
39
37
  callee.object.property.type === 'Identifier' &&
40
38
  callee.object.property.name === 'Function' &&
41
39
  callee.property.type === 'Identifier'
42
- ) {
43
- functionName = callee.property.name;
44
- }
40
+ ? callee.property.name
41
+ : null;
45
42
 
46
43
  if (!functionName || !ENCRYPT_FUNCTIONS.has(functionName.toLowerCase())) {
47
44
  return;
@@ -38,10 +38,7 @@ function canReplaceCallWithAssignment(node) {
38
38
  if (parent.type === 'ExpressionStatement' && parent.expression === node) {
39
39
  return true;
40
40
  }
41
- if (parent.type === 'SequenceExpression' && parent.expressions.includes(node)) {
42
- return true;
43
- }
44
- return false;
41
+ return Boolean(parent.type === 'SequenceExpression' && parent.expressions.includes(node));
45
42
  }
46
43
 
47
44
  export default {
@@ -83,11 +80,11 @@ export default {
83
80
  }
84
81
 
85
82
  const ns = callee.object.property.name.toLowerCase();
86
- const propName = callee.property.name.toLowerCase();
83
+ const propertyName = callee.property.name.toLowerCase();
87
84
  const displayNs = callee.object.property.name;
88
85
  const displayName = callee.property.name;
89
86
 
90
- if (ns === 'response' && RESPONSE_PROPERTIES.has(propName)) {
87
+ if (ns === 'response' && RESPONSE_PROPERTIES.has(propertyName)) {
91
88
  if (node.arguments.length === 0) {
92
89
  context.report({
93
90
  node,
@@ -106,10 +103,10 @@ export default {
106
103
  if (!canReplaceCallWithAssignment(node)) {
107
104
  return null;
108
105
  }
109
- const argText = sourceCode.getText(node.arguments[0]);
106
+ const argumentText = sourceCode.getText(node.arguments[0]);
110
107
  return fixer.replaceText(
111
108
  node,
112
- `${sourceCode.getText(callee)} = ${argText}`,
109
+ `${sourceCode.getText(callee)} = ${argumentText}`,
113
110
  );
114
111
  },
115
112
  });
@@ -120,7 +117,7 @@ export default {
120
117
  data: { ns: displayNs, name: displayName },
121
118
  });
122
119
  }
123
- } else if (ns === 'request' && REQUEST_PROPERTIES.has(propName)) {
120
+ } else if (ns === 'request' && REQUEST_PROPERTIES.has(propertyName)) {
124
121
  if (node.arguments.length === 0) {
125
122
  context.report({
126
123
  node,
@@ -35,9 +35,9 @@ export default {
35
35
  return;
36
36
  }
37
37
 
38
- const arg = node.arguments[0];
39
- if (containsConcatenation(arg)) {
40
- context.report({ node: arg, messageId: 'injection' });
38
+ const argument = node.arguments[0];
39
+ if (containsConcatenation(argument)) {
40
+ context.report({ node: argument, messageId: 'injection' });
41
41
  }
42
42
  },
43
43
  };
@@ -53,7 +53,7 @@ function isTreatAsContentCall(node) {
53
53
  }
54
54
 
55
55
  // Platform.Function.TreatAsContent(...)
56
- if (
56
+ return (
57
57
  callee.type === 'MemberExpression' &&
58
58
  callee.property.type === 'Identifier' &&
59
59
  callee.property.name === 'TreatAsContent' &&
@@ -62,19 +62,12 @@ function isTreatAsContentCall(node) {
62
62
  callee.object.property.name === 'Function' &&
63
63
  callee.object.object.type === 'Identifier' &&
64
64
  callee.object.object.name === 'Platform'
65
- ) {
66
- return true;
67
- }
68
-
69
- return false;
65
+ );
70
66
  }
71
67
 
72
68
  function containsConcatenation(node) {
73
69
  if (node.type === 'BinaryExpression' && node.operator === '+') {
74
70
  return true;
75
71
  }
76
- if (node.type === 'TemplateLiteral' && node.expressions.length > 0) {
77
- return true;
78
- }
79
- return false;
72
+ return node.type === 'TemplateLiteral' && node.expressions.length > 0;
80
73
  }
@@ -98,8 +98,8 @@ export default {
98
98
 
99
99
  function buildSuggestFix(entry) {
100
100
  return function fix(fixer) {
101
- const src = context.sourceCode;
102
- const end = src.ast.range[1];
101
+ const source = context.sourceCode;
102
+ const end = source.ast.range[1];
103
103
  return fixer.insertTextAfterRange([end, end], '\n\n' + entry.polyfill);
104
104
  };
105
105
  }
@@ -113,27 +113,27 @@ export default {
113
113
  return;
114
114
  }
115
115
 
116
- const obj = left.object;
117
- const prop = left.property;
118
- if (prop.type !== 'Identifier') {
116
+ const object = left.object;
117
+ const property = left.property;
118
+ if (property.type !== 'Identifier') {
119
119
  return;
120
120
  }
121
121
 
122
122
  // Array.X = ...
123
- if (obj.type === 'Identifier' && obj.name === 'Array') {
124
- alreadyPolyfilled.add(prop.name);
123
+ if (object.type === 'Identifier' && object.name === 'Array') {
124
+ alreadyPolyfilled.add(property.name);
125
125
  return;
126
126
  }
127
127
 
128
128
  // Array.prototype.X = ... or String.prototype.X = ...
129
129
  if (
130
- obj.type === 'MemberExpression' &&
131
- obj.object.type === 'Identifier' &&
132
- (obj.object.name === 'Array' || obj.object.name === 'String') &&
133
- obj.property.type === 'Identifier' &&
134
- obj.property.name === 'prototype'
130
+ object.type === 'MemberExpression' &&
131
+ object.object.type === 'Identifier' &&
132
+ (object.object.name === 'Array' || object.object.name === 'String') &&
133
+ object.property.type === 'Identifier' &&
134
+ object.property.name === 'prototype'
135
135
  ) {
136
- alreadyPolyfilled.add(prop.name);
136
+ alreadyPolyfilled.add(property.name);
137
137
  }
138
138
  },
139
139
 
@@ -143,12 +143,12 @@ export default {
143
143
  return;
144
144
  }
145
145
 
146
- const prop = callee.property;
147
- if (prop.type !== 'Identifier') {
146
+ const property = callee.property;
147
+ if (property.type !== 'Identifier') {
148
148
  return;
149
149
  }
150
150
 
151
- const methodName = prop.name;
151
+ const methodName = property.name;
152
152
  const receiver = callee.object;
153
153
 
154
154
  const lowerMethod = methodName.toLowerCase();
@@ -163,7 +163,7 @@ export default {
163
163
  return;
164
164
  }
165
165
  const entry = polyfillByStaticName.get(methodName);
166
- pendingReports.push({ node: prop, entry });
166
+ pendingReports.push({ node: property, entry });
167
167
  return;
168
168
  }
169
169
 
@@ -176,7 +176,7 @@ export default {
176
176
  const entry = knownUnsupportedByStaticName.get(lowerMethod);
177
177
  const ownerBase = entry.owner.replace(/\.prototype$/, '');
178
178
  if (receiver.name === ownerBase && !ignored.has(methodName)) {
179
- pendingReports.push({ node: prop, entry, noPolyfill: true });
179
+ pendingReports.push({ node: property, entry, noPolyfill: true });
180
180
  return;
181
181
  }
182
182
  }
@@ -187,13 +187,13 @@ export default {
187
187
  return;
188
188
  }
189
189
 
190
- const entry = polyfillByPrototypeName.get(methodName);
191
-
192
190
  // Skip known SFMC top-level objects to avoid false positives.
193
191
  if (receiver.type === 'Identifier' && SFMC_RECEIVERS.has(receiver.name)) {
194
192
  return;
195
193
  }
196
194
 
195
+ const entry = polyfillByPrototypeName.get(methodName);
196
+
197
197
  // For methods that also exist on String.prototype in ES3
198
198
  // (indexOf, lastIndexOf), only flag when the receiver is
199
199
  // a literal array or the call is chained from an array method
@@ -211,7 +211,7 @@ export default {
211
211
  }
212
212
  }
213
213
 
214
- pendingReports.push({ node: prop, entry });
214
+ pendingReports.push({ node: property, entry });
215
215
  return;
216
216
  }
217
217
 
@@ -225,7 +225,7 @@ export default {
225
225
  return;
226
226
  }
227
227
  const entry = knownUnsupportedByPrototypeName.get(lowerMethod);
228
- pendingReports.push({ node: prop, entry, noPolyfill: true });
228
+ pendingReports.push({ node: property, entry, noPolyfill: true });
229
229
  }
230
230
  },
231
231
 
@@ -70,7 +70,7 @@ export default {
70
70
 
71
71
  create(context) {
72
72
  const options = context.options[0] ?? {};
73
- const targetNext = options.target === 'next';
73
+ const isTargetNext = options.target === 'next';
74
74
 
75
75
  // Track variable name → Core Library type name (assigned via TypeName.Init())
76
76
  const coreVariables = new Map();
@@ -79,16 +79,20 @@ export default {
79
79
 
80
80
  return {
81
81
  VariableDeclaration(node) {
82
- for (const decl of node.declarations) {
83
- if (!decl.init || !decl.id || decl.id.type !== 'Identifier') {
82
+ for (const declaration of node.declarations) {
83
+ if (
84
+ !declaration.init ||
85
+ !declaration.id ||
86
+ declaration.id.type !== 'Identifier'
87
+ ) {
84
88
  continue;
85
89
  }
86
- const coreType = getCoreInitType(decl.init);
90
+ const coreType = getCoreInitType(declaration.init);
87
91
  if (coreType) {
88
- coreVariables.set(decl.id.name, coreType);
92
+ coreVariables.set(declaration.id.name, coreType);
89
93
  }
90
- if (isWSProxyConstructor(decl.init)) {
91
- wsproxyVariables.add(decl.id.name);
94
+ if (isWSProxyConstructor(declaration.init)) {
95
+ wsproxyVariables.add(declaration.id.name);
92
96
  }
93
97
  }
94
98
  },
@@ -119,7 +123,7 @@ export default {
119
123
  const methodName = property.name;
120
124
 
121
125
  // ── MCN target: flag all SSJS API calls as unsupported ────────
122
- if (targetNext) {
126
+ if (isTargetNext) {
123
127
  // Only report on Platform.<NS>.<method>(), HTTP.<method>(),
124
128
  // Core Library instance calls, or WSProxy calls — not bare JS.
125
129
  const isPlatformCall =
@@ -189,10 +193,10 @@ export default {
189
193
  // Core library: var de = DataExtension.Init("key"); de.Foo();
190
194
  const coreType = coreVariables.get(objectName);
191
195
  if (coreType) {
192
- const objectDef = coreObjectLookup.get(coreType);
193
- if (objectDef) {
196
+ const objectDefinition = coreObjectLookup.get(coreType);
197
+ if (objectDefinition) {
194
198
  const knownMethods = new Set(
195
- objectDef.methods.map((m) => m.toLowerCase()),
199
+ objectDefinition.methods.map((m) => m.toLowerCase()),
196
200
  );
197
201
  if (!knownMethods.has(methodName.toLowerCase())) {
198
202
  context.report({
@@ -201,7 +205,7 @@ export default {
201
205
  data: {
202
206
  method: methodName,
203
207
  objectType: coreType,
204
- available: objectDef.methods.join(', '),
208
+ available: objectDefinition.methods.join(', '),
205
209
  },
206
210
  });
207
211
  }
@@ -21,8 +21,8 @@ const AUTO_FIX = {
21
21
 
22
22
  function nullishCoalescingFix(context, node) {
23
23
  return (fixer) => {
24
- const src = context.sourceCode.getText();
25
- const between = src.slice(node.left.range[1], node.right.range[0]);
24
+ const source = context.sourceCode.getText();
25
+ const between = source.slice(node.left.range[1], node.right.range[0]);
26
26
  const offset = between.indexOf('??');
27
27
  if (offset === -1) {
28
28
  return null;
@@ -34,9 +34,9 @@ function nullishCoalescingFix(context, node) {
34
34
 
35
35
  function directObjectReturnFix(context, node) {
36
36
  return (fixer) => {
37
- const objText = context.sourceCode.getText(node.argument);
37
+ const objectText = context.sourceCode.getText(node.argument);
38
38
  const indent = ' '.repeat(node.loc.start.column);
39
- return fixer.replaceText(node, `var _result = ${objText};\n${indent}return _result;`);
39
+ return fixer.replaceText(node, `var _result = ${objectText};\n${indent}return _result;`);
40
40
  };
41
41
  }
42
42
 
@@ -92,7 +92,7 @@ export default {
92
92
  },
93
93
  };
94
94
 
95
- if (entry.feature in AUTO_FIX) {
95
+ if (Object.hasOwn(AUTO_FIX, entry.feature)) {
96
96
  report.fix = AUTO_FIX[entry.feature](node);
97
97
  } else if (entry.feature === 'NullishCoalescing') {
98
98
  report.fix = nullishCoalescingFix(context, node);
@@ -34,19 +34,19 @@ export default {
34
34
  return;
35
35
  }
36
36
 
37
- const arg = node.arguments[0];
37
+ const argument = node.arguments[0];
38
38
 
39
- if (isAlreadySafe(arg)) {
39
+ if (isAlreadySafe(argument)) {
40
40
  return;
41
41
  }
42
42
 
43
43
  context.report({
44
- node: arg,
44
+ node: argument,
45
45
  messageId: 'unsafeArg',
46
- data: { argText: context.sourceCode.getText(arg) },
46
+ data: { argText: context.sourceCode.getText(argument) },
47
47
  fix(fixer) {
48
- const argText = context.sourceCode.getText(arg);
49
- return fixer.replaceText(arg, `${argText} + ''`);
48
+ const argumentText = context.sourceCode.getText(argument);
49
+ return fixer.replaceText(argument, `${argumentText} + ''`);
50
50
  },
51
51
  });
52
52
  },
@@ -63,7 +63,7 @@ function isParseJSONCall(node) {
63
63
  }
64
64
 
65
65
  // Platform.Function.ParseJSON(...)
66
- if (
66
+ return (
67
67
  callee.type === 'MemberExpression' &&
68
68
  callee.property.type === 'Identifier' &&
69
69
  callee.property.name === 'ParseJSON' &&
@@ -72,29 +72,21 @@ function isParseJSONCall(node) {
72
72
  callee.object.property.name === 'Function' &&
73
73
  callee.object.object.type === 'Identifier' &&
74
74
  callee.object.object.name === 'Platform'
75
- ) {
76
- return true;
77
- }
78
-
79
- return false;
75
+ );
80
76
  }
81
77
 
82
- function isAlreadySafe(arg) {
78
+ function isAlreadySafe(argument) {
83
79
  // arg + '' or '' + arg
84
80
  if (
85
- arg.type === 'BinaryExpression' &&
86
- arg.operator === '+' &&
87
- (isEmptyString(arg.left) || isEmptyString(arg.right))
81
+ argument.type === 'BinaryExpression' &&
82
+ argument.operator === '+' &&
83
+ (isEmptyString(argument.left) || isEmptyString(argument.right))
88
84
  ) {
89
85
  return true;
90
86
  }
91
87
 
92
88
  // String literal passed directly — already a string
93
- if (arg.type === 'Literal' && typeof arg.value === 'string') {
94
- return true;
95
- }
96
-
97
- return false;
89
+ return argument.type === 'Literal' && typeof argument.value === 'string';
98
90
  }
99
91
 
100
92
  function isEmptyString(node) {