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.
- package/README.md +72 -63
- package/docs/rules/hbs/helper-arity.md +55 -0
- package/docs/rules/hbs/helper-too-new-for-target.md +62 -0
- package/docs/rules/hbs/no-unknown-binding.md +49 -0
- package/docs/rules/hbs/no-unknown-helper.md +60 -0
- package/docs/rules/hbs/no-unsupported-construct.md +65 -0
- package/package.json +14 -12
- package/src/ampscript-parser.js +1 -1
- package/src/ampscript-processor.js +22 -16
- package/src/handlebars-parser.js +195 -0
- package/src/index.js +90 -11
- package/src/processor.js +76 -16
- package/src/rules/amp/{arg-types.js → argument-types.js} +16 -12
- package/src/rules/amp/function-arity.js +9 -9
- package/src/rules/amp/no-inline-statement.js +5 -5
- package/src/rules/amp/no-loop-counter-assign.js +7 -7
- package/src/rules/amp/no-nested-ampscript-delimiter.js +1 -1
- package/src/rules/amp/no-smart-quotes.js +18 -18
- package/src/rules/amp/no-unknown-function.js +16 -3
- package/src/rules/amp/prefer-attribute-value.js +7 -10
- package/src/rules/amp/require-rowcount-check.js +12 -15
- package/src/rules/amp/require-variable-declaration.js +3 -3
- package/src/rules/hbs/_shared.js +116 -0
- package/src/rules/hbs/helper-arity.js +97 -0
- package/src/rules/hbs/helper-too-new-for-target.js +94 -0
- package/src/rules/hbs/no-unknown-binding.js +74 -0
- package/src/rules/hbs/no-unknown-helper.js +79 -0
- package/src/rules/hbs/no-unsupported-construct.js +70 -0
- package/src/rules/ssjs/cache-loop-length.js +14 -17
- package/src/rules/ssjs/no-deprecated-function.js +8 -8
- package/src/rules/ssjs/no-hardcoded-credentials.js +3 -6
- package/src/rules/ssjs/no-property-call.js +6 -9
- package/src/rules/ssjs/no-treatascontent-injection.js +6 -13
- package/src/rules/ssjs/no-unavailable-method.js +22 -22
- package/src/rules/ssjs/no-unknown-function.js +16 -12
- package/src/rules/ssjs/no-unsupported-syntax.js +5 -5
- package/src/rules/ssjs/{prefer-parsejson-safe-arg.js → prefer-parsejson-safe-argument.js} +13 -21
- package/src/rules/ssjs/require-hasownproperty.js +54 -42
- package/src/rules/ssjs/require-platform-load-order.js +1 -1
- package/src/rules/ssjs/require-platform-load.js +6 -6
- package/src/rules/ssjs/{ssjs-arg-types.js → ssjs-argument-types.js} +59 -46
- package/src/rules/ssjs/ssjs-core-method-arity.js +19 -15
- /package/src/rules/amp/{no-var-redeclaration.js → no-variable-redeclaration.js} +0 -0
|
@@ -27,17 +27,17 @@ export default {
|
|
|
27
27
|
ForInStatement(node) {
|
|
28
28
|
const body = node.body;
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const statements = body.type === 'BlockStatement' ? body.body : [body];
|
|
31
31
|
|
|
32
|
-
if (
|
|
32
|
+
if (statements.length === 0) {
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const hasGuard =
|
|
36
|
+
const hasGuard = statements.some((statement) => containsHasOwnProperty(statement));
|
|
37
37
|
|
|
38
38
|
if (!hasGuard) {
|
|
39
39
|
const keyName = getKeyName(node.left);
|
|
40
|
-
const
|
|
40
|
+
const objectText = context.sourceCode.getText(node.right);
|
|
41
41
|
|
|
42
42
|
if (!keyName) {
|
|
43
43
|
context.report({
|
|
@@ -52,43 +52,49 @@ export default {
|
|
|
52
52
|
messageId: 'missingGuard',
|
|
53
53
|
fix(fixer) {
|
|
54
54
|
const sourceCode = context.sourceCode;
|
|
55
|
-
const
|
|
55
|
+
const innerStatements =
|
|
56
|
+
body.type === 'BlockStatement' ? body.body : [body];
|
|
56
57
|
|
|
57
58
|
// Single-line loops keep compact output to avoid reformatting
|
|
58
59
|
// code the author intentionally wrote on one line.
|
|
59
60
|
const isSingleLine = node.loc.start.line === node.loc.end.line;
|
|
60
61
|
if (isSingleLine) {
|
|
61
|
-
const compactBody =
|
|
62
|
-
.map((
|
|
62
|
+
const compactBody = innerStatements
|
|
63
|
+
.map((statement) => sourceCode.getText(statement))
|
|
63
64
|
.join(' ');
|
|
64
65
|
return fixer.replaceText(
|
|
65
66
|
body,
|
|
66
|
-
`{ if (${
|
|
67
|
+
`{ if (${objectText}.hasOwnProperty(${keyName})) { ${compactBody} } }`,
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// Multi-line loops: emit a properly-indented guard block.
|
|
71
72
|
const loopLine = sourceCode.lines[node.loc.start.line - 1] ?? '';
|
|
72
73
|
const baseIndent = (loopLine.match(/^[\t ]*/) ?? [''])[0];
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
? (sourceCode.lines[
|
|
74
|
+
const firstStatement = innerStatements[0];
|
|
75
|
+
const firstStatementLine = firstStatement
|
|
76
|
+
? (sourceCode.lines[firstStatement.loc.start.line - 1] ?? '')
|
|
76
77
|
: '';
|
|
77
|
-
const
|
|
78
|
+
const firstStatementIndent = (firstStatementLine.match(/^[\t ]*/) ?? [
|
|
79
|
+
'',
|
|
80
|
+
])[0];
|
|
78
81
|
const unit =
|
|
79
|
-
|
|
80
|
-
?
|
|
81
|
-
: '
|
|
82
|
+
firstStatementIndent.length > baseIndent.length
|
|
83
|
+
? firstStatementIndent.slice(baseIndent.length)
|
|
84
|
+
: ' '.repeat(4);
|
|
82
85
|
const guardIndent = baseIndent + unit;
|
|
83
|
-
const
|
|
86
|
+
const statementIndent = guardIndent + unit;
|
|
84
87
|
|
|
85
|
-
const guardedBody =
|
|
86
|
-
.map(
|
|
88
|
+
const guardedBody = innerStatements
|
|
89
|
+
.map(
|
|
90
|
+
(statement) =>
|
|
91
|
+
`${statementIndent}${sourceCode.getText(statement)}`,
|
|
92
|
+
)
|
|
87
93
|
.join('\n');
|
|
88
94
|
|
|
89
95
|
return fixer.replaceText(
|
|
90
96
|
body,
|
|
91
|
-
`{\n${guardIndent}if (${
|
|
97
|
+
`{\n${guardIndent}if (${objectText}.hasOwnProperty(${keyName})) {\n${guardedBody}\n${guardIndent}}\n${baseIndent}}`,
|
|
92
98
|
);
|
|
93
99
|
},
|
|
94
100
|
});
|
|
@@ -99,53 +105,59 @@ export default {
|
|
|
99
105
|
};
|
|
100
106
|
|
|
101
107
|
function containsHasOwnProperty(node) {
|
|
102
|
-
|
|
108
|
+
let current = node;
|
|
109
|
+
|
|
110
|
+
// Unwrap pass-through single-child nodes iteratively to avoid tail recursion.
|
|
111
|
+
while (current && current.type === 'ExpressionStatement') {
|
|
112
|
+
current = current.expression;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!current) {
|
|
103
116
|
return false;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
if (
|
|
119
|
+
if (current.type === 'IfStatement' && current.test && hasOwnPropertyTest(current.test)) {
|
|
107
120
|
return true;
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
if (
|
|
123
|
+
if (current.type === 'IfStatement') {
|
|
111
124
|
return (
|
|
112
|
-
hasOwnPropertyTest(
|
|
113
|
-
containsHasOwnProperty(
|
|
114
|
-
containsHasOwnProperty(
|
|
125
|
+
hasOwnPropertyTest(current.test) ||
|
|
126
|
+
containsHasOwnProperty(current.consequent) ||
|
|
127
|
+
containsHasOwnProperty(current.alternate)
|
|
115
128
|
);
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
if (
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (node.type === 'ExpressionStatement') {
|
|
123
|
-
return containsHasOwnProperty(node.expression);
|
|
131
|
+
if (current.type === 'BlockStatement') {
|
|
132
|
+
return current.body.some((child) => containsHasOwnProperty(child));
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
return false;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
function hasOwnPropertyTest(node) {
|
|
130
|
-
|
|
139
|
+
let current = node;
|
|
140
|
+
|
|
141
|
+
// Unwrap negations / unary wrappers iteratively to avoid tail recursion.
|
|
142
|
+
while (current && current.type === 'UnaryExpression') {
|
|
143
|
+
current = current.argument;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!current) {
|
|
131
147
|
return false;
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
current.type === 'CallExpression' &&
|
|
152
|
+
current.callee.type === 'MemberExpression' &&
|
|
153
|
+
current.callee.property.type === 'Identifier' &&
|
|
154
|
+
current.callee.property.name === 'hasOwnProperty'
|
|
139
155
|
) {
|
|
140
156
|
return true;
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
if (
|
|
144
|
-
return hasOwnPropertyTest(
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (node.type === 'UnaryExpression') {
|
|
148
|
-
return hasOwnPropertyTest(node.argument);
|
|
159
|
+
if (current.type === 'LogicalExpression') {
|
|
160
|
+
return hasOwnPropertyTest(current.left) || hasOwnPropertyTest(current.right);
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
return false;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { coreObjectNames } from 'ssjs-data';
|
|
10
10
|
|
|
11
|
-
const TOP_LEVEL_CORE_NAMES = new Set([...coreObjectNames].map((n) => n.split('.')[0]));
|
|
11
|
+
const TOP_LEVEL_CORE_NAMES = new Set([...coreObjectNames].map((n) => n.split('.', 1)[0]));
|
|
12
12
|
|
|
13
13
|
export default {
|
|
14
14
|
meta: {
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
import { coreObjectNames, SSJS_GLOBALS } from 'ssjs-data';
|
|
14
14
|
|
|
15
|
-
const TOP_LEVEL_CORE_NAMES = new Set([...coreObjectNames].map((n) => n.split('.')[0]));
|
|
15
|
+
const TOP_LEVEL_CORE_NAMES = new Set([...coreObjectNames].map((n) => n.split('.', 1)[0]));
|
|
16
16
|
|
|
17
17
|
// Globals that resolve at runtime only after Platform.Load("core", ...) has been called.
|
|
18
18
|
// E.g. Now(), Write(), GUID(), Base64Encode(), Attribute.GetValue(), DateTime.Parse(), …
|
|
19
|
-
const
|
|
19
|
+
const CORE_LOAD_DEPENDENT_GLOBALS = new Set(
|
|
20
20
|
SSJS_GLOBALS.filter((g) => g.requiresCoreLoad).map((g) => g.name.toLowerCase()),
|
|
21
21
|
);
|
|
22
22
|
|
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
|
|
56
56
|
'Program:exit'() {
|
|
57
57
|
if (!hasPlatformLoad) {
|
|
58
|
-
for (const [
|
|
58
|
+
for (const [index, { node, name }] of pendingReports.entries()) {
|
|
59
59
|
context.report({
|
|
60
60
|
node,
|
|
61
61
|
messageId: 'missingLoad',
|
|
@@ -63,7 +63,7 @@ export default {
|
|
|
63
63
|
// Only attach the fix to the first violation to prevent
|
|
64
64
|
// ESLint from inserting the statement multiple times.
|
|
65
65
|
fix:
|
|
66
|
-
|
|
66
|
+
index === 0
|
|
67
67
|
? (fixer) =>
|
|
68
68
|
fixer.insertTextBeforeRange(
|
|
69
69
|
[0, 0],
|
|
@@ -105,7 +105,7 @@ function getCoreObjectUsage(node) {
|
|
|
105
105
|
|
|
106
106
|
// Bare call: Now(), Write(), GUID(), Base64Encode(), Redirect(), …
|
|
107
107
|
if (callee.type === 'Identifier') {
|
|
108
|
-
if (
|
|
108
|
+
if (CORE_LOAD_DEPENDENT_GLOBALS.has(callee.name.toLowerCase())) {
|
|
109
109
|
return callee.name;
|
|
110
110
|
}
|
|
111
111
|
return null;
|
|
@@ -135,7 +135,7 @@ function getCoreObjectUsage(node) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// requiresCoreLoad globals used as object: Attribute.GetValue(…), DateTime.Parse(…), …
|
|
138
|
-
if (
|
|
138
|
+
if (CORE_LOAD_DEPENDENT_GLOBALS.has(objectName.toLowerCase())) {
|
|
139
139
|
return `${objectName}.${callee.property.name}`;
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -61,34 +61,34 @@ export default {
|
|
|
61
61
|
},
|
|
62
62
|
|
|
63
63
|
create(context) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
64
|
+
const coreVariables = new Map(); // varName → className (Core Library instances)
|
|
65
|
+
const wsproxyVariables = new Set(); // varNames assigned new Script.Util.WSProxy()
|
|
66
66
|
|
|
67
|
-
function
|
|
67
|
+
function checkArguments(entry, arguments_, callName) {
|
|
68
68
|
if (!entry || !entry.params || entry.params.length === 0) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
for (const [
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
71
|
+
for (const [index, argument] of arguments_.entries()) {
|
|
72
|
+
const parameter = entry.params[index];
|
|
73
|
+
if (!parameter) {
|
|
74
74
|
break;
|
|
75
75
|
} // beyond declared params — arity rule handles this
|
|
76
|
-
if (!
|
|
76
|
+
if (!parameter.type || parameter.type === 'any') {
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
|
-
const actual =
|
|
79
|
+
const actual = inferArgumentType(argument);
|
|
80
80
|
if (actual === null) {
|
|
81
81
|
continue;
|
|
82
82
|
} // unknown type (variable/call) — skip
|
|
83
|
-
if (!isTypeCompatible(actual,
|
|
83
|
+
if (!isTypeCompatible(actual, parameter.type)) {
|
|
84
84
|
context.report({
|
|
85
|
-
node:
|
|
85
|
+
node: argument,
|
|
86
86
|
messageId: 'typeMismatch',
|
|
87
87
|
data: {
|
|
88
|
-
pos: String(
|
|
89
|
-
param:
|
|
88
|
+
pos: String(index + 1),
|
|
89
|
+
param: parameter.name,
|
|
90
90
|
call: callName,
|
|
91
|
-
expected:
|
|
91
|
+
expected: parameter.type,
|
|
92
92
|
actual,
|
|
93
93
|
},
|
|
94
94
|
});
|
|
@@ -98,17 +98,21 @@ export default {
|
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
100
|
VariableDeclaration(node) {
|
|
101
|
-
for (const
|
|
102
|
-
if (
|
|
101
|
+
for (const declaration of node.declarations) {
|
|
102
|
+
if (
|
|
103
|
+
!declaration.init ||
|
|
104
|
+
!declaration.id ||
|
|
105
|
+
declaration.id.type !== 'Identifier'
|
|
106
|
+
) {
|
|
103
107
|
continue;
|
|
104
108
|
}
|
|
105
|
-
if (isWSProxyConstructor(
|
|
106
|
-
|
|
109
|
+
if (isWSProxyConstructor(declaration.init)) {
|
|
110
|
+
wsproxyVariables.add(declaration.id.name);
|
|
107
111
|
continue;
|
|
108
112
|
}
|
|
109
|
-
const coreType = getCoreInitType(
|
|
113
|
+
const coreType = getCoreInitType(declaration.init);
|
|
110
114
|
if (coreType) {
|
|
111
|
-
|
|
115
|
+
coreVariables.set(declaration.id.name, coreType);
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
},
|
|
@@ -118,12 +122,12 @@ export default {
|
|
|
118
122
|
return;
|
|
119
123
|
}
|
|
120
124
|
if (isWSProxyConstructor(node.right)) {
|
|
121
|
-
|
|
125
|
+
wsproxyVariables.add(node.left.name);
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
124
128
|
const coreType = getCoreInitType(node.right);
|
|
125
129
|
if (coreType) {
|
|
126
|
-
|
|
130
|
+
coreVariables.set(node.left.name, coreType);
|
|
127
131
|
}
|
|
128
132
|
},
|
|
129
133
|
|
|
@@ -134,7 +138,7 @@ export default {
|
|
|
134
138
|
if (callee.type === 'Identifier') {
|
|
135
139
|
const entry = ssjsGlobalsLookup.get(callee.name.toLowerCase());
|
|
136
140
|
if (entry) {
|
|
137
|
-
|
|
141
|
+
checkArguments(entry, node.arguments, callee.name);
|
|
138
142
|
}
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
@@ -159,7 +163,7 @@ export default {
|
|
|
159
163
|
if (lookup) {
|
|
160
164
|
const entry = lookup.get(methodName.toLowerCase());
|
|
161
165
|
if (entry) {
|
|
162
|
-
|
|
166
|
+
checkArguments(
|
|
163
167
|
entry,
|
|
164
168
|
node.arguments,
|
|
165
169
|
`Platform.${callee.object.property.name}.${methodName}`,
|
|
@@ -170,48 +174,52 @@ export default {
|
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
if (callee.object.type === 'Identifier') {
|
|
173
|
-
const
|
|
174
|
-
const
|
|
177
|
+
const objectName = callee.object.name;
|
|
178
|
+
const objectLower = objectName.toLowerCase();
|
|
175
179
|
|
|
176
180
|
// Static top-level: HTTP.Post(...), HTTPHeader.SetValue(...), Attribute.GetValue(...)
|
|
177
|
-
const topLookup = TOPLEVEL_LOOKUPS.get(
|
|
181
|
+
const topLookup = TOPLEVEL_LOOKUPS.get(objectLower);
|
|
178
182
|
if (topLookup) {
|
|
179
183
|
const entry = topLookup.get(methodName.toLowerCase());
|
|
180
184
|
if (entry) {
|
|
181
|
-
|
|
185
|
+
checkArguments(entry, node.arguments, `${objectName}.${methodName}`);
|
|
182
186
|
}
|
|
183
187
|
return;
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
// WSProxy instance method: proxy.Retrieve(...)
|
|
187
|
-
if (
|
|
191
|
+
if (wsproxyVariables.has(objectName)) {
|
|
188
192
|
const entry = wsproxyMethodLookup.get(methodName.toLowerCase());
|
|
189
193
|
if (entry) {
|
|
190
|
-
|
|
194
|
+
checkArguments(entry, node.arguments, `WSProxy.${methodName}`);
|
|
191
195
|
}
|
|
192
196
|
return;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
// Core Library instance method: de.Add(...)
|
|
196
|
-
const coreType =
|
|
200
|
+
const coreType = coreVariables.get(objectName);
|
|
197
201
|
if (coreType) {
|
|
198
202
|
const classLookup = coreMethodArityLookup.get(coreType.toLowerCase());
|
|
199
203
|
if (classLookup) {
|
|
200
204
|
const entry = classLookup.get(methodName.toLowerCase());
|
|
201
205
|
if (entry) {
|
|
202
|
-
|
|
206
|
+
checkArguments(entry, node.arguments, `${coreType}.${methodName}`);
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
return;
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
// Static Core Library single-name: DataExtension.Init(...), BounceEvent.Retrieve(...)
|
|
209
|
-
if (coreObjectNames.has(
|
|
210
|
-
const classLookup = coreMethodArityLookup.get(
|
|
213
|
+
if (coreObjectNames.has(objectName)) {
|
|
214
|
+
const classLookup = coreMethodArityLookup.get(objectLower);
|
|
211
215
|
if (classLookup) {
|
|
212
216
|
const entry = classLookup.get(methodName.toLowerCase());
|
|
213
217
|
if (entry) {
|
|
214
|
-
|
|
218
|
+
checkArguments(
|
|
219
|
+
entry,
|
|
220
|
+
node.arguments,
|
|
221
|
+
`${objectName}.${methodName}`,
|
|
222
|
+
);
|
|
215
223
|
}
|
|
216
224
|
}
|
|
217
225
|
}
|
|
@@ -225,7 +233,7 @@ export default {
|
|
|
225
233
|
if (classLookup) {
|
|
226
234
|
const entry = classLookup.get(methodName.toLowerCase());
|
|
227
235
|
if (entry) {
|
|
228
|
-
|
|
236
|
+
checkArguments(entry, node.arguments, `${objectPath}.${methodName}`);
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
}
|
|
@@ -266,13 +274,13 @@ function getMemberPath(node) {
|
|
|
266
274
|
return node.name;
|
|
267
275
|
}
|
|
268
276
|
if (node.type === 'MemberExpression' && node.property.type === 'Identifier') {
|
|
269
|
-
const
|
|
270
|
-
return
|
|
277
|
+
const object = getMemberPath(node.object);
|
|
278
|
+
return object ? `${object}.${node.property.name}` : null;
|
|
271
279
|
}
|
|
272
280
|
return null;
|
|
273
281
|
}
|
|
274
282
|
|
|
275
|
-
function
|
|
283
|
+
function inferArgumentType(node) {
|
|
276
284
|
if (!node || node.type === 'SpreadElement') {
|
|
277
285
|
return null;
|
|
278
286
|
}
|
|
@@ -294,14 +302,22 @@ function inferArgType(node) {
|
|
|
294
302
|
return 'string';
|
|
295
303
|
}
|
|
296
304
|
if (node.type === 'ArrayExpression') {
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
305
|
+
const elements = node.elements.filter(Boolean);
|
|
306
|
+
if (elements.length === 0) {
|
|
299
307
|
return 'array';
|
|
300
308
|
}
|
|
301
|
-
if (
|
|
309
|
+
if (
|
|
310
|
+
elements.every(
|
|
311
|
+
(element) => element.type === 'Literal' && typeof element.value === 'string',
|
|
312
|
+
)
|
|
313
|
+
) {
|
|
302
314
|
return 'string[]';
|
|
303
315
|
}
|
|
304
|
-
if (
|
|
316
|
+
if (
|
|
317
|
+
elements.every(
|
|
318
|
+
(element) => element.type === 'Literal' && typeof element.value === 'number',
|
|
319
|
+
)
|
|
320
|
+
) {
|
|
305
321
|
return 'number[]';
|
|
306
322
|
}
|
|
307
323
|
return 'array';
|
|
@@ -321,10 +337,7 @@ function isTypeCompatible(actual, expected) {
|
|
|
321
337
|
return true;
|
|
322
338
|
}
|
|
323
339
|
// 'array' is compatible with typed array variants
|
|
324
|
-
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
return false;
|
|
340
|
+
return Boolean(allowed.includes('array') && (actual === 'string[]' || actual === 'number[]'));
|
|
328
341
|
}
|
|
329
342
|
|
|
330
343
|
function isWSProxyConstructor(node) {
|
|
@@ -28,13 +28,13 @@ export default {
|
|
|
28
28
|
},
|
|
29
29
|
|
|
30
30
|
create(context) {
|
|
31
|
-
const
|
|
31
|
+
const coreVariables = new Map(); // varName → className
|
|
32
32
|
|
|
33
|
-
function checkArity(entry,
|
|
33
|
+
function checkArity(entry, arguments_, callName, reportNode) {
|
|
34
34
|
if (!entry) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
const actual =
|
|
37
|
+
const actual = arguments_.length;
|
|
38
38
|
if (actual < entry.minArgs) {
|
|
39
39
|
context.report({
|
|
40
40
|
node: reportNode,
|
|
@@ -52,13 +52,17 @@ export default {
|
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
54
|
VariableDeclaration(node) {
|
|
55
|
-
for (const
|
|
56
|
-
if (
|
|
55
|
+
for (const declaration of node.declarations) {
|
|
56
|
+
if (
|
|
57
|
+
!declaration.init ||
|
|
58
|
+
!declaration.id ||
|
|
59
|
+
declaration.id.type !== 'Identifier'
|
|
60
|
+
) {
|
|
57
61
|
continue;
|
|
58
62
|
}
|
|
59
|
-
const coreType = getCoreInitType(
|
|
63
|
+
const coreType = getCoreInitType(declaration.init);
|
|
60
64
|
if (coreType) {
|
|
61
|
-
|
|
65
|
+
coreVariables.set(declaration.id.name, coreType);
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
},
|
|
@@ -69,7 +73,7 @@ export default {
|
|
|
69
73
|
}
|
|
70
74
|
const coreType = getCoreInitType(node.right);
|
|
71
75
|
if (coreType) {
|
|
72
|
-
|
|
76
|
+
coreVariables.set(node.left.name, coreType);
|
|
73
77
|
}
|
|
74
78
|
},
|
|
75
79
|
|
|
@@ -84,10 +88,10 @@ export default {
|
|
|
84
88
|
const methodName = callee.property.name;
|
|
85
89
|
|
|
86
90
|
if (callee.object.type === 'Identifier') {
|
|
87
|
-
const
|
|
91
|
+
const objectName = callee.object.name;
|
|
88
92
|
|
|
89
93
|
// Core Library instance method: de.Add(...)
|
|
90
|
-
const coreType =
|
|
94
|
+
const coreType = coreVariables.get(objectName);
|
|
91
95
|
if (coreType) {
|
|
92
96
|
const classLookup = coreMethodArityLookup.get(coreType.toLowerCase());
|
|
93
97
|
if (classLookup) {
|
|
@@ -103,14 +107,14 @@ export default {
|
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
// Static single-name: DataExtension.Init(...), BounceEvent.Retrieve(...)
|
|
106
|
-
if (coreObjectNames.has(
|
|
107
|
-
const classLookup = coreMethodArityLookup.get(
|
|
110
|
+
if (coreObjectNames.has(objectName)) {
|
|
111
|
+
const classLookup = coreMethodArityLookup.get(objectName.toLowerCase());
|
|
108
112
|
if (classLookup) {
|
|
109
113
|
const entry = classLookup.get(methodName.toLowerCase());
|
|
110
114
|
checkArity(
|
|
111
115
|
entry,
|
|
112
116
|
node.arguments,
|
|
113
|
-
`${
|
|
117
|
+
`${objectName}.${methodName}`,
|
|
114
118
|
callee.property,
|
|
115
119
|
);
|
|
116
120
|
}
|
|
@@ -169,8 +173,8 @@ function getMemberPath(node) {
|
|
|
169
173
|
return node.name;
|
|
170
174
|
}
|
|
171
175
|
if (node.type === 'MemberExpression' && node.property.type === 'Identifier') {
|
|
172
|
-
const
|
|
173
|
-
return
|
|
176
|
+
const object = getMemberPath(node.object);
|
|
177
|
+
return object ? `${object}.${node.property.name}` : null;
|
|
174
178
|
}
|
|
175
179
|
return null;
|
|
176
180
|
}
|
|
File without changes
|