eslint-plugin-sfmc 4.2.0 → 4.3.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.
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# `sfmc/ssjs-http-property-value`
|
|
2
|
+
|
|
3
|
+
Flags literal assignments to writable `Script.Util.HttpRequest` / `Script.Util.HttpGet`
|
|
4
|
+
instance properties whose value violates the property's documented, runtime-confirmed
|
|
5
|
+
constraint (allowed enum, integer-ness, or minimum).
|
|
6
|
+
|
|
7
|
+
Several request properties only accept a fixed set of values. Assigning something outside
|
|
8
|
+
that set throws or silently misbehaves in the SFMC SSJS engine:
|
|
9
|
+
|
|
10
|
+
| Property | Allowed values |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `method` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
|
|
13
|
+
| `emptyContentHandling` | `0`, `1`, `2` |
|
|
14
|
+
| `retries` | non-negative integer |
|
|
15
|
+
| `timeout` | non-negative integer |
|
|
16
|
+
|
|
17
|
+
The allowed values live in [`ssjs-data`](../../../..) (`SCRIPT_UTIL_REQUEST_PROPERTIES` /
|
|
18
|
+
`SCRIPT_UTIL_HTTPGET_PROPERTIES` → `valueConstraint`), so this rule and the VS Code /
|
|
19
|
+
Cursor diagnostic (`ssjs/invalid-http-property-value`) share one source of truth.
|
|
20
|
+
|
|
21
|
+
## Detection
|
|
22
|
+
|
|
23
|
+
The rule tracks data flow, so it only fires on a genuine request object:
|
|
24
|
+
|
|
25
|
+
1. A **request** variable is one assigned from `new Script.Util.HttpRequest(...)` or
|
|
26
|
+
`Script.Util.HttpGet(...)`.
|
|
27
|
+
2. An assignment `<requestVar>.<prop> = <literal>` where `<prop>` has a `valueConstraint`
|
|
28
|
+
and the right-hand side is a **literal** (string, number, or a negative numeric like
|
|
29
|
+
`-2.45`) is validated against the constraint.
|
|
30
|
+
|
|
31
|
+
Only literal values are checked. Variable / expression assignments cannot be verified
|
|
32
|
+
statically and are left alone to avoid false positives.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### ❌ Incorrect
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
var req = new Script.Util.HttpRequest("https://api.example.com/data");
|
|
40
|
+
|
|
41
|
+
req.emptyContentHandling = 5; // ← allowed: 0 | 1 | 2
|
|
42
|
+
req.retries = -2.45; // ← must be a non-negative integer
|
|
43
|
+
req.method = 'POT'; // ← allowed: GET | POST | PUT | PATCH | DELETE
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### ✅ Correct
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
var req = new Script.Util.HttpRequest("https://api.example.com/data");
|
|
50
|
+
|
|
51
|
+
req.emptyContentHandling = 1;
|
|
52
|
+
req.retries = 3;
|
|
53
|
+
req.method = 'POST';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Fix (suggestions)
|
|
57
|
+
|
|
58
|
+
For enum violations the rule offers a suggestion per allowed value (e.g. replace `'POT'`
|
|
59
|
+
with `'GET'`, `'POST'`, …). Numeric violations are reported without an automatic
|
|
60
|
+
replacement, since there is no single unambiguous valid value.
|
|
61
|
+
|
|
62
|
+
The same diagnostic and quick-fix are available in the VS Code / Cursor extension
|
|
63
|
+
(`ssjs/invalid-http-property-value`).
|
|
64
|
+
|
|
65
|
+
## Rule details
|
|
66
|
+
|
|
67
|
+
- **Type:** `problem`
|
|
68
|
+
- **Fixable:** No (offers suggestions)
|
|
69
|
+
- **Recommended:** Yes (`error`)
|
|
70
|
+
- **Strict:** Yes (`error`)
|
|
71
|
+
- **MCN:** Off (SSJS is not supported on Marketing Cloud Next)
|
|
@@ -21,7 +21,8 @@ return wrong results:
|
|
|
21
21
|
- `lastIndexOf` always returns `-1`.
|
|
22
22
|
|
|
23
23
|
This rule detects both categories and offers a lightbulb suggestion to insert a polyfill
|
|
24
|
-
at the
|
|
24
|
+
at the top of the file (after a leading `/* global … */` directive when present), matching
|
|
25
|
+
the language-server quick-fix so both tools place polyfills consistently.
|
|
25
26
|
|
|
26
27
|
## Covered methods
|
|
27
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-sfmc",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "ESLint plugin for Salesforce Marketing Cloud Engagement+Next - AMPscript, Server-Side JavaScript (SSJS) and Handlebars",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"eslint-plugin-mso-email": "^2.0.0",
|
|
43
43
|
"handlebars-data": "^0.3.0",
|
|
44
44
|
"mso-conditional-parser": "^2.0.1",
|
|
45
|
-
"ssjs-data": "^0.
|
|
45
|
+
"ssjs-data": "^0.18.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"eslint": ">=9.0.0"
|
package/src/index.js
CHANGED
|
@@ -58,6 +58,7 @@ import ssjsArgumentTypes from './rules/ssjs/ssjs-argument-types.js';
|
|
|
58
58
|
import ssjsCoreMethodArity from './rules/ssjs/ssjs-core-method-arity.js';
|
|
59
59
|
import ssjsNoClrHeaderAccess from './rules/ssjs/no-clr-header-access.js';
|
|
60
60
|
import ssjsRequireStringClrContent from './rules/ssjs/require-string-clr-content.js';
|
|
61
|
+
import ssjsHttpPropertyValue from './rules/ssjs/http-property-value.js';
|
|
61
62
|
|
|
62
63
|
// ── Handlebars (MCN) rules ──────────────────────────────────────────────────────
|
|
63
64
|
|
|
@@ -139,6 +140,7 @@ const plugin = {
|
|
|
139
140
|
'ssjs-core-method-arity': ssjsCoreMethodArity,
|
|
140
141
|
'ssjs-no-clr-header-access': ssjsNoClrHeaderAccess,
|
|
141
142
|
'ssjs-require-string-clr-content': ssjsRequireStringClrContent,
|
|
143
|
+
'ssjs-http-property-value': ssjsHttpPropertyValue,
|
|
142
144
|
|
|
143
145
|
// Handlebars (MCN) rules (hbs- prefix)
|
|
144
146
|
'hbs-no-unknown-helper': hbsNoUnknownHelper,
|
|
@@ -184,6 +186,7 @@ const ssjsMcnRules = {
|
|
|
184
186
|
'sfmc/ssjs-core-method-arity': 'off',
|
|
185
187
|
'sfmc/ssjs-no-clr-header-access': 'off',
|
|
186
188
|
'sfmc/ssjs-require-string-clr-content': 'off',
|
|
189
|
+
'sfmc/ssjs-http-property-value': 'off',
|
|
187
190
|
'no-cond-assign': 'off',
|
|
188
191
|
};
|
|
189
192
|
|
|
@@ -254,6 +257,7 @@ const ssjsRecommendedRules = {
|
|
|
254
257
|
'sfmc/ssjs-core-method-arity': 'warn',
|
|
255
258
|
'sfmc/ssjs-no-clr-header-access': 'error',
|
|
256
259
|
'sfmc/ssjs-require-string-clr-content': 'error',
|
|
260
|
+
'sfmc/ssjs-http-property-value': 'error',
|
|
257
261
|
'no-cond-assign': 'error',
|
|
258
262
|
};
|
|
259
263
|
|
|
@@ -277,6 +281,7 @@ const ssjsStrictRules = {
|
|
|
277
281
|
'sfmc/ssjs-core-method-arity': 'error',
|
|
278
282
|
'sfmc/ssjs-no-clr-header-access': 'error',
|
|
279
283
|
'sfmc/ssjs-require-string-clr-content': 'error',
|
|
284
|
+
'sfmc/ssjs-http-property-value': 'error',
|
|
280
285
|
'no-cond-assign': 'error',
|
|
281
286
|
};
|
|
282
287
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: ssjs-http-property-value
|
|
3
|
+
*
|
|
4
|
+
* Flags literal assignments to writable `Script.Util.HttpRequest` /
|
|
5
|
+
* `Script.Util.HttpGet` instance properties whose value violates the property's
|
|
6
|
+
* documented, runtime-confirmed constraint (allowed enum, integer-ness, or
|
|
7
|
+
* minimum). Examples that throw or misbehave at runtime:
|
|
8
|
+
*
|
|
9
|
+
* req.emptyContentHandling = 5; // allowed: 0 | 1 | 2
|
|
10
|
+
* req.retries = -2.45; // must be a non-negative integer
|
|
11
|
+
* req.method = 'POT'; // allowed: GET | POST | PUT | PATCH | DELETE
|
|
12
|
+
*
|
|
13
|
+
* Constraints live in ssjs-data (`SCRIPT_UTIL_REQUEST_PROPERTIES` /
|
|
14
|
+
* `SCRIPT_UTIL_HTTPGET_PROPERTIES` -> `valueConstraint`) so the LSP diagnostic
|
|
15
|
+
* (`ssjs/invalid-http-property-value`) and this rule share one source of truth.
|
|
16
|
+
* Only **literal** right-hand sides are checked; variables / expressions cannot
|
|
17
|
+
* be statically verified and are left alone to avoid false positives.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { SCRIPT_UTIL_REQUEST_PROPERTIES, SCRIPT_UTIL_HTTPGET_PROPERTIES } from 'ssjs-data';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Map of property name => valueConstraint, built once from ssjs-data. When both
|
|
24
|
+
* request and get define the same property, the constraints are identical.
|
|
25
|
+
*
|
|
26
|
+
* @returns {Map<string, object>} property name -> valueConstraint
|
|
27
|
+
*/
|
|
28
|
+
function buildConstraintLookup() {
|
|
29
|
+
const lookup = new Map();
|
|
30
|
+
for (const property of [...SCRIPT_UTIL_REQUEST_PROPERTIES, ...SCRIPT_UTIL_HTTPGET_PROPERTIES]) {
|
|
31
|
+
if (property && property.valueConstraint && !lookup.has(property.name)) {
|
|
32
|
+
lookup.set(property.name, property.valueConstraint);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return lookup;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const constraintLookup = buildConstraintLookup();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns true when the node is `Script.Util.HttpRequest` or
|
|
42
|
+
* `Script.Util.HttpGet` (a MemberExpression, optionally the callee of `new`).
|
|
43
|
+
*
|
|
44
|
+
* @param {import('eslint').Rule.Node} node - MemberExpression to test
|
|
45
|
+
* @returns {boolean} Whether it references a Script.Util HTTP constructor
|
|
46
|
+
*/
|
|
47
|
+
function isHttpConstructorMember(node) {
|
|
48
|
+
return (
|
|
49
|
+
node.type === 'MemberExpression' &&
|
|
50
|
+
node.property.type === 'Identifier' &&
|
|
51
|
+
(node.property.name === 'HttpRequest' || node.property.name === 'HttpGet') &&
|
|
52
|
+
node.object.type === 'MemberExpression' &&
|
|
53
|
+
node.object.property.type === 'Identifier' &&
|
|
54
|
+
node.object.property.name === 'Util' &&
|
|
55
|
+
node.object.object.type === 'Identifier' &&
|
|
56
|
+
node.object.object.name === 'Script'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns true when the node constructs a Script.Util HTTP request, i.e.
|
|
62
|
+
* `new Script.Util.HttpRequest(...)` or `Script.Util.HttpGet(...)`.
|
|
63
|
+
*
|
|
64
|
+
* @param {import('eslint').Rule.Node} node - init expression of a declarator
|
|
65
|
+
* @returns {boolean} Whether the expression yields an HTTP request instance
|
|
66
|
+
*/
|
|
67
|
+
function isHttpRequestInit(node) {
|
|
68
|
+
if (!node) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (node.type === 'NewExpression' || node.type === 'CallExpression') {
|
|
72
|
+
return isHttpConstructorMember(node.callee);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extracts the literal value from a RHS node, or null when it is not a plain
|
|
79
|
+
* literal (string / number / boolean, incl. a negative numeric like `-2.45`).
|
|
80
|
+
*
|
|
81
|
+
* @param {import('eslint').Rule.Node} node - the assignment right-hand side
|
|
82
|
+
* @returns {{value: string|number|boolean}|null} parsed value wrapper or null
|
|
83
|
+
*/
|
|
84
|
+
function parseLiteral(node) {
|
|
85
|
+
if (node.type === 'Literal' && typeof node.value !== 'object' && node.value !== null) {
|
|
86
|
+
return { value: node.value };
|
|
87
|
+
}
|
|
88
|
+
// Unary minus / plus on a numeric literal, e.g. -2.45.
|
|
89
|
+
if (
|
|
90
|
+
node.type === 'UnaryExpression' &&
|
|
91
|
+
(node.operator === '-' || node.operator === '+') &&
|
|
92
|
+
node.argument.type === 'Literal' &&
|
|
93
|
+
typeof node.argument.value === 'number'
|
|
94
|
+
) {
|
|
95
|
+
const n = node.operator === '-' ? -node.argument.value : node.argument.value;
|
|
96
|
+
return { value: n };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validates a value against a constraint. Returns a human-readable violation
|
|
103
|
+
* fragment (e.g. `must be one of "GET", "POST"`) or null when the value is ok.
|
|
104
|
+
*
|
|
105
|
+
* @param {string|number|boolean} value - the parsed literal value
|
|
106
|
+
* @param {object} constraint - the valueConstraint from ssjs-data
|
|
107
|
+
* @returns {string|null} violation description or null when valid
|
|
108
|
+
*/
|
|
109
|
+
function checkConstraint(value, constraint) {
|
|
110
|
+
if (Array.isArray(constraint.enum)) {
|
|
111
|
+
if (!constraint.enum.includes(value)) {
|
|
112
|
+
const allowed = constraint.enum
|
|
113
|
+
.map((v) => (typeof v === 'string' ? `"${v}"` : String(v)))
|
|
114
|
+
.join(', ');
|
|
115
|
+
return `must be one of ${allowed}`;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (constraint.numeric) {
|
|
120
|
+
if (typeof value !== 'number') {
|
|
121
|
+
return 'must be a number';
|
|
122
|
+
}
|
|
123
|
+
if (constraint.numeric === 'integer' && !Number.isSafeInteger(value)) {
|
|
124
|
+
return 'must be an integer';
|
|
125
|
+
}
|
|
126
|
+
if (typeof constraint.min === 'number' && value < constraint.min) {
|
|
127
|
+
return `must be >= ${constraint.min}`;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Builds source-ready replacement suggestions for a violation. Enum -> each
|
|
136
|
+
* allowed value (quoted for strings) with its optional `enumLabels` meaning.
|
|
137
|
+
* Numeric -> nothing (no single fix).
|
|
138
|
+
*
|
|
139
|
+
* @param {object} constraint - the valueConstraint from ssjs-data
|
|
140
|
+
* @returns {{code: string, label?: string}[]} replacement snippets to offer as suggestions
|
|
141
|
+
*/
|
|
142
|
+
function buildSuggestions(constraint) {
|
|
143
|
+
if (Array.isArray(constraint.enum)) {
|
|
144
|
+
const labels = constraint.enumLabels;
|
|
145
|
+
return constraint.enum.map((v) => ({
|
|
146
|
+
code: typeof v === 'string' ? `'${v}'` : String(v),
|
|
147
|
+
label: labels ? labels[String(v)] : undefined,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default {
|
|
154
|
+
meta: {
|
|
155
|
+
type: 'problem',
|
|
156
|
+
hasSuggestions: true,
|
|
157
|
+
docs: {
|
|
158
|
+
description:
|
|
159
|
+
'Disallow invalid literal values assigned to Script.Util.HttpRequest/HttpGet properties (e.g. method, emptyContentHandling, retries) based on their documented allowed values',
|
|
160
|
+
},
|
|
161
|
+
messages: {
|
|
162
|
+
invalidValue: 'Invalid value for {{ prop }}: it {{ violation }}.',
|
|
163
|
+
replaceWith: 'Replace with {{ value }}',
|
|
164
|
+
replaceWithLabel: 'Replace with {{ value }} ({{ label }})',
|
|
165
|
+
},
|
|
166
|
+
schema: [],
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
create(context) {
|
|
170
|
+
const requestVariables = new Set();
|
|
171
|
+
const pending = [];
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
VariableDeclarator(node) {
|
|
175
|
+
if (node.id.type === 'Identifier' && isHttpRequestInit(node.init)) {
|
|
176
|
+
requestVariables.add(node.id.name);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
AssignmentExpression(node) {
|
|
181
|
+
if (
|
|
182
|
+
node.operator !== '=' ||
|
|
183
|
+
node.left.type !== 'MemberExpression' ||
|
|
184
|
+
node.left.computed ||
|
|
185
|
+
node.left.object.type !== 'Identifier' ||
|
|
186
|
+
!requestVariables.has(node.left.object.name) ||
|
|
187
|
+
node.left.property.type !== 'Identifier'
|
|
188
|
+
) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const propertyName = node.left.property.name;
|
|
192
|
+
const constraint = constraintLookup.get(propertyName);
|
|
193
|
+
if (!constraint) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const parsed = parseLiteral(node.right);
|
|
197
|
+
if (!parsed) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const violation = checkConstraint(parsed.value, constraint);
|
|
201
|
+
if (!violation) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
pending.push({ rhs: node.right, propName: propertyName, violation, constraint });
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
'Program:exit'() {
|
|
208
|
+
for (const { rhs, propName, violation, constraint } of pending) {
|
|
209
|
+
const suggest = buildSuggestions(constraint).map(({ code, label }) =>
|
|
210
|
+
label
|
|
211
|
+
? {
|
|
212
|
+
messageId: 'replaceWithLabel',
|
|
213
|
+
data: { value: code, label },
|
|
214
|
+
fix: (fixer) => fixer.replaceText(rhs, code),
|
|
215
|
+
}
|
|
216
|
+
: {
|
|
217
|
+
messageId: 'replaceWith',
|
|
218
|
+
data: { value: code },
|
|
219
|
+
fix: (fixer) => fixer.replaceText(rhs, code),
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
context.report({
|
|
223
|
+
node: rhs,
|
|
224
|
+
messageId: 'invalidValue',
|
|
225
|
+
data: { prop: propName, violation },
|
|
226
|
+
suggest,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Two catalogs from ssjs-data feed this rule:
|
|
8
8
|
*
|
|
9
9
|
* 1. POLYFILLABLE_METHODS — a shipped ES3-safe polyfill exists. The report
|
|
10
|
-
* carries a suggestion that inserts the polyfill at the
|
|
10
|
+
* carries a suggestion that inserts the polyfill at the top of the file.
|
|
11
11
|
* 2. KNOWN_UNSUPPORTED — no polyfill is feasible. The report has no fix; the
|
|
12
12
|
* message carries the ssjs-data `suggestion` (e.g. use Platform.Function.X).
|
|
13
13
|
*
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* - category 'broken': method exists natively but returns incorrect results.
|
|
16
16
|
*
|
|
17
17
|
* No auto-fix is applied because prototype assignments are not hoisted —
|
|
18
|
-
* the suggestion inserts the polyfill at the
|
|
19
|
-
*
|
|
18
|
+
* the suggestion inserts the polyfill at the top of the file (after a leading
|
|
19
|
+
* `/* global *\/` directive when present), and users should verify placement
|
|
20
|
+
* before the first call (or load via Content Block).
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import {
|
|
@@ -58,7 +59,7 @@ export default {
|
|
|
58
59
|
broken:
|
|
59
60
|
"'{{owner}}.{{method}}' exists in SFMC SSJS but produces incorrect results. " +
|
|
60
61
|
'Add a polyfill to get the correct behavior.',
|
|
61
|
-
addPolyfill: "Insert '{{owner}}.{{method}}' polyfill at
|
|
62
|
+
addPolyfill: "Insert '{{owner}}.{{method}}' polyfill at top of file",
|
|
62
63
|
unavailableNoPolyfill:
|
|
63
64
|
"'{{owner}}.{{method}}' is not available in SFMC SSJS (ECMAScript 3). {{suggestion}}",
|
|
64
65
|
brokenNoPolyfill:
|
|
@@ -99,8 +100,21 @@ export default {
|
|
|
99
100
|
function buildSuggestFix(entry) {
|
|
100
101
|
return function fix(fixer) {
|
|
101
102
|
const source = context.sourceCode;
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
// Insert the polyfill at the TOP of the file so it is defined
|
|
104
|
+
// before any call site (SSJS does not hoist prototype
|
|
105
|
+
// assignments). This mirrors the LSP quick-fix. When a leading
|
|
106
|
+
// `/* global ... */` ESLint directive is the first comment,
|
|
107
|
+
// insert right after it so the directive stays at the very top.
|
|
108
|
+
const leadingGlobal = source
|
|
109
|
+
.getAllComments()
|
|
110
|
+
.find(
|
|
111
|
+
(c) =>
|
|
112
|
+
c.type === 'Block' && c.range[0] === 0 && /^\s*global\b/.test(c.value),
|
|
113
|
+
);
|
|
114
|
+
if (leadingGlobal) {
|
|
115
|
+
return fixer.insertTextAfterRange(leadingGlobal.range, '\n\n' + entry.polyfill);
|
|
116
|
+
}
|
|
117
|
+
return fixer.insertTextBeforeRange([0, 0], entry.polyfill + '\n\n');
|
|
104
118
|
};
|
|
105
119
|
}
|
|
106
120
|
|