eslint-plugin-chrome-extensions 2.1.0 → 2.2.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 +153 -33
- package/docs/LICENSE.md +1 -1
- package/package.json +3 -5
- package/src/data/apis.js +52 -0
- package/src/data/deprecations.js +5 -5
- package/src/data/manifest.js +22 -11
- package/src/data/permissions.js +1 -1
- package/src/index.js +6 -7
- package/src/rules/manifest-spacing.js +160 -0
- package/src/rules/valid-mv3-schema.js +48 -19
- package/src/rules/valid-service-worker.js +7 -2
- package/src/utils.js +11 -14
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ import json from '@eslint/json'
|
|
|
41
41
|
export default [
|
|
42
42
|
// other configs
|
|
43
43
|
{
|
|
44
|
-
files: ['extension/**/*.js'],
|
|
44
|
+
files: ['**/extension/**/*.js'],
|
|
45
45
|
plugins: { 'chrome-extensions': chromeExtensions },
|
|
46
46
|
rules: {
|
|
47
47
|
...chromeExtensions.configs.recommended.rules,
|
|
@@ -49,7 +49,7 @@ export default [
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
files: ['
|
|
52
|
+
files: ['**/manifest.json'], language: 'json/json',
|
|
53
53
|
plugins: { 'chrome-extensions': chromeExtensions, json },
|
|
54
54
|
rules: {
|
|
55
55
|
...chromeExtensions.configs.manifest.rules,
|
|
@@ -73,7 +73,7 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
|
|
|
73
73
|
<h3 id="manifest-key-order">
|
|
74
74
|
<code>manifest-key-order</code>
|
|
75
75
|
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
76
|
-
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-
|
|
76
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
77
77
|
<a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
|
|
78
78
|
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
79
79
|
<a title="Doesn't prevent bugs" href="#manifest-key-order">
|
|
@@ -89,7 +89,7 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
|
|
|
89
89
|
|
|
90
90
|
<summary>Enforces a preferred order for manifest keys.</summary>
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
<h4 id="manifest-key-order-options">Options</h4>
|
|
93
93
|
|
|
94
94
|
- `order` (default `[]`) – manifest keys in custom order (e.g. `['name', 'manifest_version']`) to selectively override built-in order with
|
|
95
95
|
|
|
@@ -129,7 +129,98 @@ If you don't care about the order of manifest keys.
|
|
|
129
129
|
#### See also
|
|
130
130
|
|
|
131
131
|
- [`require-mv3`](#require-mv3)
|
|
132
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
133
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
132
134
|
- [`valid-mv3-schema`](#valid-mv3-schema)
|
|
135
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
136
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
137
|
+
|
|
138
|
+
<div align="center">
|
|
139
|
+
|
|
140
|
+
[Back to index ↑][epce-rules]
|
|
141
|
+
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
<h3 id="manifest-spacing">
|
|
149
|
+
<code>manifest-spacing</code>
|
|
150
|
+
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
151
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
152
|
+
<a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
|
|
153
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
154
|
+
<a title="Doesn't prevent bugs" href="#manifest-spacing">
|
|
155
|
+
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
156
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
157
|
+
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
158
|
+
<a title="Copy rule URL" target="_blank"
|
|
159
|
+
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23manifest-spacing">
|
|
160
|
+
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
161
|
+
</h3>
|
|
162
|
+
|
|
163
|
+
<details>
|
|
164
|
+
|
|
165
|
+
<summary>Enforces consistent indentation and spacing in <code>manifest.json</code>.</summary>
|
|
166
|
+
|
|
167
|
+
<h4 id="manifest-spacing-options">Options</h4>
|
|
168
|
+
|
|
169
|
+
- `spaces` (default `2`) – number of spaces per indent level
|
|
170
|
+
- `checkColons` (default `true`) – enforce 0 spaces before and 1 space after `:` in key/value pairs
|
|
171
|
+
- `checkCommas` (default `true`) – enforce 0 spaces before and 1 space after `,` in inline objects
|
|
172
|
+
- `checkBraces` (default `true`) – enforce 1 space after `{` and before `}` in inline objects
|
|
173
|
+
- `checkBrackets` (default `true`) – enforce 0 spaces after `[` and before `]` in inline arrays
|
|
174
|
+
|
|
175
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
176
|
+
|
|
177
|
+
```diff
|
|
178
|
+
/* eslint chrome-extensions/manifest-spacing: ['error', { spaces: 2 }] */
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
! "manifest_version": 3,
|
|
182
|
+
! "version" : "1.0.0",
|
|
183
|
+
! "permissions": [ "tabs","cookies" ],
|
|
184
|
+
! "icons": {"16": "icon16.png"},
|
|
185
|
+
"name": "My Extension"
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
|
|
190
|
+
|
|
191
|
+
```diff
|
|
192
|
+
/* eslint chrome-extensions/manifest-spacing: ['error', { spaces: 2 }] */
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
- "manifest_version": 3,
|
|
196
|
+
+ "manifest_version": 3,
|
|
197
|
+
- "version" : "1.0.0",
|
|
198
|
+
+ "version": "1.0.0",
|
|
199
|
+
- "permissions": [ "tabs","cookies" ],
|
|
200
|
+
+ "permissions": ["tabs", "cookies"],
|
|
201
|
+
- "icons": {"16": "icon16.png"},
|
|
202
|
+
+ "icons": { "16": "icon16.png" },
|
|
203
|
+
"name": "My Extension"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### When not to use it
|
|
208
|
+
|
|
209
|
+
If you don't care about visual consistency in your manifest.
|
|
210
|
+
|
|
211
|
+
#### Introduced in
|
|
212
|
+
|
|
213
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.2.0">v2.2.0</a> (October 12, 2026)
|
|
214
|
+
|
|
215
|
+
#### See also
|
|
216
|
+
|
|
217
|
+
- [`manifest-key-order`](#manifest-key-order)
|
|
218
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
219
|
+
<a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix"><img height=14 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
220
|
+
<a title="Doesn't prevent bugs" href="#manifest-key-order"><img height=14 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
221
|
+
<a title="Accepts array w/ options" href="#manifest-key-order-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
222
|
+
- [`no-duplicate-manifest-keys`](#no-duplicate-manifest-keys)
|
|
223
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
133
224
|
|
|
134
225
|
<div align="center">
|
|
135
226
|
|
|
@@ -180,8 +271,8 @@ If you intentionally interact w/ host page styles.
|
|
|
180
271
|
|
|
181
272
|
#### See also
|
|
182
273
|
|
|
183
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts">Chrome for Developers — Content scripts</a>
|
|
184
|
-
- <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">MDN — Using shadow DOM</a>
|
|
274
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts">Chrome for Developers — Content scripts</a>
|
|
275
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64"> <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">MDN — Using shadow DOM</a>
|
|
185
276
|
|
|
186
277
|
<div align="center">
|
|
187
278
|
|
|
@@ -208,7 +299,7 @@ If you intentionally interact w/ host page styles.
|
|
|
208
299
|
|
|
209
300
|
<summary>Disallows deprecated <code>chrome</code> API calls.</summary>
|
|
210
301
|
|
|
211
|
-
|
|
302
|
+
<h4 id="no-deprecated-apis-options">Options</h4>
|
|
212
303
|
|
|
213
304
|
- `target` (default `'mv3'`) – Chrome extension manifest version whose deprecations to validate against (`'mv3'`, `'mv2'`)
|
|
214
305
|
- `allowedAPIs` (default `[]`) – additional deprecated API namespaces to allow, as a single string or array
|
|
@@ -246,7 +337,7 @@ If you don't want deprecation validation at all. For individual deprecated APIs
|
|
|
246
337
|
|
|
247
338
|
#### See also
|
|
248
339
|
|
|
249
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api">Chrome for Developers — API reference</a>
|
|
340
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api">Chrome for Developers — API reference</a>
|
|
250
341
|
|
|
251
342
|
<div align="center">
|
|
252
343
|
|
|
@@ -261,7 +352,7 @@ If you don't want deprecation validation at all. For individual deprecated APIs
|
|
|
261
352
|
<h3 id="no-duplicate-manifest-keys">
|
|
262
353
|
<code>no-duplicate-manifest-keys</code>
|
|
263
354
|
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
264
|
-
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-
|
|
355
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
265
356
|
<a title="Copy rule URL" target="_blank"
|
|
266
357
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-duplicate-manifest-keys">
|
|
267
358
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
@@ -308,7 +399,12 @@ If you rely on a Chromium fork resolving duplicate keys in a specific order.
|
|
|
308
399
|
#### See also
|
|
309
400
|
|
|
310
401
|
- [`manifest-key-order`](#manifest-key-order)
|
|
402
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
403
|
+
<a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix"><img height=14 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
404
|
+
<a title="Doesn't prevent bugs" href="#manifest-key-order"><img height=14 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
405
|
+
<a title="Accepts array w/ options" href="#manifest-key-order-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
311
406
|
- [`no-unused-permissions`](#no-unused-permissions)
|
|
407
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
312
408
|
|
|
313
409
|
<div align="center">
|
|
314
410
|
|
|
@@ -361,8 +457,7 @@ If you intentionally target HTTP endpoints.
|
|
|
361
457
|
|
|
362
458
|
#### See also
|
|
363
459
|
|
|
364
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/network-requests">Chrome for Developers — Cross-origin network requests</a>
|
|
365
|
-
- [`no-remote-code`](#no-remote-code)
|
|
460
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/network-requests">Chrome for Developers — Cross-origin network requests</a>
|
|
366
461
|
|
|
367
462
|
<div align="center">
|
|
368
463
|
|
|
@@ -391,7 +486,7 @@ If you intentionally target HTTP endpoints.
|
|
|
391
486
|
|
|
392
487
|
<summary>Requires sender/origin validation + filtered <code>tabs.query()</code> + explicit <code>window.postMessage()</code> target.</summary>
|
|
393
488
|
|
|
394
|
-
|
|
489
|
+
<h4 id="no-insecure-messaging-options">Options</h4>
|
|
395
490
|
|
|
396
491
|
- `disallowWildcardTarget` (default `true`) – flag <code>postMessage()</code> calls that use <code>'*'</code> as <code>targetOrigin</code>
|
|
397
492
|
|
|
@@ -445,9 +540,9 @@ If you trust every external caller and open tab, or you intentionally broadcast
|
|
|
445
540
|
|
|
446
541
|
#### See also
|
|
447
542
|
|
|
448
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onMessageExternal">Chrome for Developers — `runtime.onMessageExternal`</a>
|
|
449
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/tabs#method-query">Chrome for Developers — `tabs.query()`</a>
|
|
450
|
-
- <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns">MDN — `window.postMessage()` security concerns</a>
|
|
543
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onMessageExternal">Chrome for Developers — `runtime.onMessageExternal`</a>
|
|
544
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/tabs#method-query">Chrome for Developers — `tabs.query()`</a>
|
|
545
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64"> <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns">MDN — `window.postMessage()` security concerns</a>
|
|
451
546
|
|
|
452
547
|
<div align="center">
|
|
453
548
|
|
|
@@ -464,7 +559,7 @@ If you trust every external caller and open tab, or you intentionally broadcast
|
|
|
464
559
|
<a title="Included in recommended config" href="#usage">
|
|
465
560
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
466
561
|
<a title="Works w/ 'json/json' config block" href="#usage">
|
|
467
|
-
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-
|
|
562
|
+
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
468
563
|
<a title="Accepts array w/ options" href="#usage">
|
|
469
564
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
470
565
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -476,7 +571,7 @@ If you trust every external caller and open tab, or you intentionally broadcast
|
|
|
476
571
|
|
|
477
572
|
<summary>Disallows invalid or unsupported manifest permission names.</summary>
|
|
478
573
|
|
|
479
|
-
|
|
574
|
+
<h4 id="no-invalid-permissions-options">Options</h4>
|
|
480
575
|
|
|
481
576
|
- `target` (default `'mv3'`) – Chrome manifest version whose permissions to validate against (`'mv3'`, `'mv2'`)
|
|
482
577
|
- `allowedPermissions` (default `[]`) – additional permission names to allow beyond the built-in list, as a single string or array
|
|
@@ -516,7 +611,9 @@ If you don't want permission validation at all. For custom permissions from a Ch
|
|
|
516
611
|
#### See also
|
|
517
612
|
|
|
518
613
|
- [`no-missing-permissions`](#no-missing-permissions)
|
|
614
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
519
615
|
- [`no-unused-permissions`](#no-unused-permissions)
|
|
616
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
520
617
|
|
|
521
618
|
<div align="center">
|
|
522
619
|
|
|
@@ -579,7 +676,11 @@ If you use APIs through aliases or wrappers that obscure the original call site.
|
|
|
579
676
|
#### See also
|
|
580
677
|
|
|
581
678
|
- [`no-invalid-permissions`](#no-invalid-permissions)
|
|
679
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
680
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
681
|
+
<a title="Accepts array w/ options" href="#no-invalid-permissions-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
582
682
|
- [`no-unused-permissions`](#no-unused-permissions)
|
|
683
|
+
<a title="Only works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
583
684
|
|
|
584
685
|
<div align="center">
|
|
585
686
|
|
|
@@ -641,8 +742,8 @@ If your paths are built at runtime.
|
|
|
641
742
|
|
|
642
743
|
#### See also
|
|
643
744
|
|
|
644
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources">Chrome for Developers — `web_accessible_resources`</a>
|
|
645
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#method-getURL">Chrome for Developers — `runtime.getURL()`</a>
|
|
745
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources">Chrome for Developers — `web_accessible_resources`</a>
|
|
746
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#method-getURL">Chrome for Developers — `runtime.getURL()`</a>
|
|
646
747
|
|
|
647
748
|
<div align="center">
|
|
648
749
|
|
|
@@ -671,7 +772,7 @@ If your paths are built at runtime.
|
|
|
671
772
|
|
|
672
773
|
<summary>Disallows remote or dynamically-evaluated code (blocked by MV3's CSP at runtime).</summary>
|
|
673
774
|
|
|
674
|
-
|
|
775
|
+
<h4 id="no-remote-code-options">Options</h4>
|
|
675
776
|
|
|
676
777
|
- `patterns` (default `['eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']`) – detection paths to enable, as a single string or array
|
|
677
778
|
- `checkExecuteScriptClosures` (default `true`) – additionally flag <code>chrome.scripting.executeScript({ func })</code> bodies that capture outer-scope variables
|
|
@@ -709,7 +810,11 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
|
|
|
709
810
|
#### See also
|
|
710
811
|
|
|
711
812
|
- [`require-mv3`](#require-mv3)
|
|
813
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
814
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
712
815
|
- [`valid-mv3-schema`](#valid-mv3-schema)
|
|
816
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
817
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
713
818
|
|
|
714
819
|
<div align="center">
|
|
715
820
|
|
|
@@ -724,7 +829,7 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
|
|
|
724
829
|
<h3 id="no-unrestricted-host-permissions">
|
|
725
830
|
<code>no-unrestricted-host-permissions</code>
|
|
726
831
|
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
727
|
-
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-
|
|
832
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
728
833
|
<a title="Accepts array w/ options" href="#usage">
|
|
729
834
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
730
835
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -736,7 +841,7 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
|
|
|
736
841
|
|
|
737
842
|
<summary>Disallows broad host permissions that grant access to every site.</summary>
|
|
738
843
|
|
|
739
|
-
|
|
844
|
+
<h4 id="no-unrestricted-host-permissions-options">Options</h4>
|
|
740
845
|
|
|
741
846
|
- `allowedPatterns` (default `[]`) – host patterns to explicitly allow (e.g. `['<all_urls>']`)
|
|
742
847
|
|
|
@@ -772,8 +877,8 @@ If you legitimately need broad access (e.g. ad blockers or dev tools).
|
|
|
772
877
|
|
|
773
878
|
#### See also
|
|
774
879
|
|
|
775
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">Chrome for Developers — Declare permissions</a>
|
|
776
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome for Developers — Match patterns</a>
|
|
880
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">Chrome for Developers — Declare permissions</a>
|
|
881
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome for Developers — Match patterns</a>
|
|
777
882
|
|
|
778
883
|
<div align="center">
|
|
779
884
|
|
|
@@ -788,7 +893,7 @@ If you legitimately need broad access (e.g. ad blockers or dev tools).
|
|
|
788
893
|
<h3 id="no-unused-permissions">
|
|
789
894
|
<code>no-unused-permissions</code>
|
|
790
895
|
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
791
|
-
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-
|
|
896
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
792
897
|
<a title="Copy rule URL" target="_blank"
|
|
793
898
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unused-permissions">
|
|
794
899
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
@@ -834,7 +939,11 @@ If you access the granted API through an alias or computed property rather than
|
|
|
834
939
|
#### See also
|
|
835
940
|
|
|
836
941
|
- [`no-invalid-permissions`](#no-invalid-permissions)
|
|
942
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
943
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
944
|
+
<a title="Accepts array w/ options" href="#no-invalid-permissions-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
837
945
|
- [`no-missing-permissions`](#no-missing-permissions)
|
|
946
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
838
947
|
|
|
839
948
|
<div align="center">
|
|
840
949
|
|
|
@@ -851,7 +960,7 @@ If you access the granted API through an alias or computed property rather than
|
|
|
851
960
|
<a title="Included in recommended config" href="#usage">
|
|
852
961
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
853
962
|
<a title="Works w/ 'json/json' config block" href="#usage">
|
|
854
|
-
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-
|
|
963
|
+
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
855
964
|
<a title="Copy rule URL" target="_blank"
|
|
856
965
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23require-mv3">
|
|
857
966
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
@@ -893,6 +1002,8 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
|
|
|
893
1002
|
#### See also
|
|
894
1003
|
|
|
895
1004
|
- [`valid-mv3-schema`](#valid-mv3-schema)
|
|
1005
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1006
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
896
1007
|
|
|
897
1008
|
<div align="center">
|
|
898
1009
|
|
|
@@ -907,7 +1018,7 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
|
|
|
907
1018
|
<h3 id="valid-manifest-icons">
|
|
908
1019
|
<code>valid-manifest-icons</code>
|
|
909
1020
|
<a title="Only works w/ 'json/json' config block" href="#usage">
|
|
910
|
-
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-
|
|
1021
|
+
<img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
|
|
911
1022
|
<a title="Accepts array w/ options" href="#usage">
|
|
912
1023
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
913
1024
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -919,7 +1030,7 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
|
|
|
919
1030
|
|
|
920
1031
|
<summary>Ensures manifest <code>icons</code> exist and use the recommended sizes.</summary>
|
|
921
1032
|
|
|
922
|
-
|
|
1033
|
+
<h4 id="valid-manifest-icons-options">Options</h4>
|
|
923
1034
|
|
|
924
1035
|
- `requireRecommendedSizes` (default `true`) – require all of `16`, `32`, `48`, `128`
|
|
925
1036
|
- `checkIconExists` (default `true`) – flag icon paths that don't resolve to an existing file
|
|
@@ -963,7 +1074,7 @@ If your icons are injected at build time, or you target browsers that support SV
|
|
|
963
1074
|
|
|
964
1075
|
#### See also
|
|
965
1076
|
|
|
966
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/icons">Chrome for Developers — `icons`</a>
|
|
1077
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/icons">Chrome for Developers — `icons`</a>
|
|
967
1078
|
|
|
968
1079
|
<div align="center">
|
|
969
1080
|
|
|
@@ -980,7 +1091,7 @@ If your icons are injected at build time, or you target browsers that support SV
|
|
|
980
1091
|
<a title="Included in recommended config" href="#usage">
|
|
981
1092
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
982
1093
|
<a title="Works w/ 'json/json' config block" href="#usage">
|
|
983
|
-
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-
|
|
1094
|
+
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
984
1095
|
<a title="Copy rule URL" target="_blank"
|
|
985
1096
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-mv3-schema">
|
|
986
1097
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
@@ -1028,7 +1139,11 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
|
|
|
1028
1139
|
#### See also
|
|
1029
1140
|
|
|
1030
1141
|
- [`require-mv3`](#require-mv3)
|
|
1142
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1143
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
1031
1144
|
- [`no-deprecated-apis`](#no-deprecated-apis)
|
|
1145
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1146
|
+
<a title="Accepts array w/ options" href="#no-deprecated-apis-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1032
1147
|
|
|
1033
1148
|
<div align="center">
|
|
1034
1149
|
|
|
@@ -1086,7 +1201,12 @@ If your background context isn't a service worker.
|
|
|
1086
1201
|
#### See also
|
|
1087
1202
|
|
|
1088
1203
|
- [`require-mv3`](#require-mv3)
|
|
1204
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1205
|
+
<a title="Works w/ 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
1089
1206
|
- [`no-remote-code`](#no-remote-code)
|
|
1207
|
+
<a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1208
|
+
<a title="Included in security config" href="#usage"><img height=14 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
|
|
1209
|
+
<a title="Accepts array w/ options" href="#no-remote-code-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1090
1210
|
|
|
1091
1211
|
<div align="center">
|
|
1092
1212
|
|
|
@@ -1103,7 +1223,7 @@ If your background context isn't a service worker.
|
|
|
1103
1223
|
<a title="Included in recommended config" href="#usage">
|
|
1104
1224
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1105
1225
|
<a title="Works w/ 'json/json' config block" href="#usage">
|
|
1106
|
-
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-
|
|
1226
|
+
<img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
|
|
1107
1227
|
<a title="Accepts array w/ options" href="#usage">
|
|
1108
1228
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1109
1229
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -1115,7 +1235,7 @@ If your background context isn't a service worker.
|
|
|
1115
1235
|
|
|
1116
1236
|
<summary>Validates the manifest <code>version</code> field.</summary>
|
|
1117
1237
|
|
|
1118
|
-
|
|
1238
|
+
<h4 id="valid-version-field-options">Options</h4>
|
|
1119
1239
|
|
|
1120
1240
|
- `versionType` (default `'chrome'`) – version format to enforce (`'chrome'`, `'semver'`, `'date'`)
|
|
1121
1241
|
- `allowFutureDate` (default `false`) – allow `version` dates later than the current date
|
|
@@ -1156,7 +1276,7 @@ If you don't care about version format consistency, or your version is injected
|
|
|
1156
1276
|
|
|
1157
1277
|
#### See also
|
|
1158
1278
|
|
|
1159
|
-
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/version">Chrome for Developers — `version`</a>
|
|
1279
|
+
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/version">Chrome for Developers — `version`</a>
|
|
1160
1280
|
|
|
1161
1281
|
<div align="center">
|
|
1162
1282
|
|
package/docs/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🏛️ MIT License
|
|
2
2
|
|
|
3
|
-
**Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui)
|
|
3
|
+
**Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui)**
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-chrome-extensions",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "ESLint rules for Chrome extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
],
|
|
35
35
|
"types": "src/index.d.ts",
|
|
36
36
|
"scripts": {
|
|
37
|
-
"prepare": "husky",
|
|
38
37
|
"lint": "eslint . --cache && yamllint .",
|
|
39
38
|
"lint:all": "eslint . && yamllint .",
|
|
40
39
|
"lint:fix": "eslint . --fix --cache && yamllint .",
|
|
@@ -64,11 +63,11 @@
|
|
|
64
63
|
"devDependencies": {
|
|
65
64
|
"@e18e/eslint-plugin": "^0.8.1",
|
|
66
65
|
"@eslint/js": "^10.0.1",
|
|
67
|
-
"@eslint/json": "^2.1.
|
|
66
|
+
"@eslint/json": "^2.1.1",
|
|
68
67
|
"@eslint/markdown": "^8.0.3",
|
|
69
68
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
70
69
|
"eslint": "^10.12.0",
|
|
71
|
-
"eslint-plugin-constructors": "^1.4.
|
|
70
|
+
"eslint-plugin-constructors": "^1.4.2",
|
|
72
71
|
"eslint-plugin-eslint-plugin": "^7.6.2",
|
|
73
72
|
"eslint-plugin-import-x": "^4.17.1",
|
|
74
73
|
"eslint-plugin-n": "^18.4.1",
|
|
@@ -77,7 +76,6 @@
|
|
|
77
76
|
"eslint-plugin-promise": "^7.3.0",
|
|
78
77
|
"eslint-plugin-regexp": "^3.3.1",
|
|
79
78
|
"eslint-plugin-security": "^4.2.0",
|
|
80
|
-
"husky": "^9.1.7",
|
|
81
79
|
"yamllint-js": "^0.2.4"
|
|
82
80
|
},
|
|
83
81
|
"dependencies": {
|
package/src/data/apis.js
CHANGED
|
@@ -33,6 +33,7 @@ export const apiPermissionMap = new Map([
|
|
|
33
33
|
['chrome.history', 'history'],
|
|
34
34
|
['chrome.idle', 'idle'],
|
|
35
35
|
['chrome.management', 'management'],
|
|
36
|
+
['chrome.menus', 'menus'],
|
|
36
37
|
['chrome.notifications', 'notifications'],
|
|
37
38
|
['chrome.pageCapture', 'pageCapture'],
|
|
38
39
|
['chrome.privacy', 'privacy'],
|
|
@@ -41,10 +42,60 @@ export const apiPermissionMap = new Map([
|
|
|
41
42
|
['chrome.sessions', 'sessions'],
|
|
42
43
|
['chrome.storage', 'storage'],
|
|
43
44
|
['chrome.tabCapture', 'tabCapture'],
|
|
45
|
+
['chrome.tabs.query', 'tabs'],
|
|
44
46
|
['chrome.topSites', 'topSites'],
|
|
45
47
|
['chrome.webRequest', 'webRequest']
|
|
46
48
|
])
|
|
47
49
|
|
|
50
|
+
export const permissionlessAPIs = new Set([
|
|
51
|
+
// https://developer.chrome.com/docs/extensions/reference/api/management
|
|
52
|
+
'chrome.management.getPermissionWarningsByManifest',
|
|
53
|
+
'chrome.management.getSelf',
|
|
54
|
+
'chrome.management.uninstallSelf',
|
|
55
|
+
// https://developer.chrome.com/docs/extensions/reference/api/runtime
|
|
56
|
+
'chrome.runtime.connect',
|
|
57
|
+
'chrome.runtime.getManifest',
|
|
58
|
+
'chrome.runtime.getPlatformInfo',
|
|
59
|
+
'chrome.runtime.getURL',
|
|
60
|
+
'chrome.runtime.onBrowserUpdateAvailable', // eslint-disable-line no-secrets/no-secrets
|
|
61
|
+
'chrome.runtime.onConnect',
|
|
62
|
+
'chrome.runtime.onConnectExternal',
|
|
63
|
+
'chrome.runtime.onInstalled',
|
|
64
|
+
'chrome.runtime.onMessage',
|
|
65
|
+
'chrome.runtime.onMessageExternal',
|
|
66
|
+
'chrome.runtime.onStartup',
|
|
67
|
+
'chrome.runtime.onSuspend',
|
|
68
|
+
'chrome.runtime.onSuspendCanceled',
|
|
69
|
+
'chrome.runtime.onUpdateAvailable',
|
|
70
|
+
'chrome.runtime.openOptionsPage',
|
|
71
|
+
'chrome.runtime.reload',
|
|
72
|
+
'chrome.runtime.requestUpdateCheck',
|
|
73
|
+
'chrome.runtime.restart',
|
|
74
|
+
'chrome.runtime.restartAfterDelay',
|
|
75
|
+
'chrome.runtime.sendMessage',
|
|
76
|
+
'chrome.runtime.setUninstallURL',
|
|
77
|
+
// https://developer.chrome.com/docs/extensions/reference/api/tabs
|
|
78
|
+
'chrome.tabs.captureVisibleTab',
|
|
79
|
+
'chrome.tabs.connect',
|
|
80
|
+
'chrome.tabs.create',
|
|
81
|
+
'chrome.tabs.detectLanguage',
|
|
82
|
+
'chrome.tabs.discard',
|
|
83
|
+
'chrome.tabs.duplicate',
|
|
84
|
+
'chrome.tabs.executeScript',
|
|
85
|
+
'chrome.tabs.get',
|
|
86
|
+
'chrome.tabs.getCurrent',
|
|
87
|
+
'chrome.tabs.goBack',
|
|
88
|
+
'chrome.tabs.goForward',
|
|
89
|
+
'chrome.tabs.highlight',
|
|
90
|
+
'chrome.tabs.insertCSS',
|
|
91
|
+
'chrome.tabs.move',
|
|
92
|
+
'chrome.tabs.reload',
|
|
93
|
+
'chrome.tabs.remove',
|
|
94
|
+
'chrome.tabs.removeCSS',
|
|
95
|
+
'chrome.tabs.sendMessage',
|
|
96
|
+
'chrome.tabs.update'
|
|
97
|
+
])
|
|
98
|
+
|
|
48
99
|
export const externalMessagingListeners =
|
|
49
100
|
new Set(['chrome.runtime.onMessageExternal', 'chrome.runtime.onConnectExternal'])
|
|
50
101
|
|
|
@@ -56,6 +107,7 @@ export function findDeprecated(api) {
|
|
|
56
107
|
}
|
|
57
108
|
|
|
58
109
|
export function findPermission(api) {
|
|
110
|
+
if (permissionlessAPIs.has(api)) return null
|
|
59
111
|
for (const [prefix, permission] of apiPermissionMap)
|
|
60
112
|
if (api == prefix || api.startsWith(`${prefix}.`))
|
|
61
113
|
return permission
|
package/src/data/deprecations.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Delete a line to end support
|
|
2
2
|
|
|
3
|
-
export const bundles = {
|
|
4
|
-
'no-invalid-permissions': ['recommended'],
|
|
5
|
-
'require-mv3': ['recommended'],
|
|
6
|
-
'valid-mv3-schema': ['recommended'],
|
|
7
|
-
'valid-version-field': ['recommended']
|
|
3
|
+
export const bundles = {
|
|
4
|
+
'no-invalid-permissions': ['recommended'], // 2026-10-5 announced future removal
|
|
5
|
+
'require-mv3': ['recommended'], // 2026-10-5 announced future removal
|
|
6
|
+
'valid-mv3-schema': ['recommended'], // 2026-10-5 announced future removal
|
|
7
|
+
'valid-version-field': ['recommended'] // 2026-10-5 announced future removal
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export default { bundles }
|
package/src/data/manifest.js
CHANGED
|
@@ -11,19 +11,30 @@ export const manifestKeyOrder = [
|
|
|
11
11
|
'manifest_version', 'name', 'version',
|
|
12
12
|
// CWS-required
|
|
13
13
|
'description', 'icons',
|
|
14
|
-
// optional
|
|
15
|
-
'action', 'background', 'chrome_settings_overrides', 'chrome_url_overrides',
|
|
16
|
-
'commands', 'content_scripts', 'content_security_policy',
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'short_name', 'side_panel', 'storage', 'tts_engine', 'update_url',
|
|
24
|
-
'version_name', 'web_accessible_resources'
|
|
14
|
+
// optional
|
|
15
|
+
'action', 'author', 'background', 'browser_specific_settings', 'chrome_settings_overrides', 'chrome_url_overrides',
|
|
16
|
+
'commands', 'content_scripts', 'content_security_policy', 'cross_origin_embedder_policy',
|
|
17
|
+
'cross_origin_opener_policy', 'dark_theme', 'declarative_net_request', 'default_locale', 'developer',
|
|
18
|
+
'devtools_page', 'dictionaries', 'export', 'externally_connectable', 'homepage_url', 'host_permissions', 'import',
|
|
19
|
+
'incognito', 'key', 'mime_types_handler', 'minimum_chrome_version', 'oauth2', 'omnibox',
|
|
20
|
+
'optional_host_permissions', 'optional_permissions', 'options_page', 'options_ui', 'permissions',
|
|
21
|
+
'protocol_handlers', 'requirements', 'sandbox', 'short_name', 'sidebar_action', 'side_panel', 'storage', 'theme',
|
|
22
|
+
'theme_experiment', 'tts_engine', 'update_url', 'version_name', 'web_accessible_resources'
|
|
25
23
|
]
|
|
26
24
|
|
|
25
|
+
export const ffDataCollectionRequired = new Set([
|
|
26
|
+
'none', 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo',
|
|
27
|
+
'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'websiteActivity',
|
|
28
|
+
'websiteContent'
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
export const ffDataCollectionOptional = new Set([
|
|
32
|
+
'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo', 'locationInfo',
|
|
33
|
+
'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'technicalAndInteraction', 'websiteActivity',
|
|
34
|
+
'websiteContent'
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
export const ffMV3bgKeys = new Set(['scripts', 'page'])
|
|
27
38
|
export const mv2OnlyKeys = new Set(['browser_action', 'page_action'])
|
|
28
39
|
|
|
29
40
|
export const remoteCodePatterns = [
|
package/src/data/permissions.js
CHANGED
|
@@ -16,7 +16,7 @@ export const validPermissions = new Set([
|
|
|
16
16
|
'downloads', 'downloads.open', 'downloads.ui', 'enterprise.deviceAttributes', 'enterprise.hardwarePlatform',
|
|
17
17
|
'enterprise.networkingAttributes', 'enterprise.platformKeys', 'favicon', 'fileBrowserHandler',
|
|
18
18
|
'fileSystemProvider', 'fontSettings', 'gcm', 'geolocation', 'history', 'identity', 'identity.email', 'idle',
|
|
19
|
-
'loginState', 'management', 'nativeMessaging', 'notifications', 'offscreen', 'pageCapture', 'platformKeys',
|
|
19
|
+
'loginState', 'management', 'menus', 'nativeMessaging', 'notifications', 'offscreen', 'pageCapture', 'platformKeys',
|
|
20
20
|
'power', 'printerProvider', 'printing', 'printingMetrics', 'privacy', 'processes', 'proxy', 'publicSuffix',
|
|
21
21
|
'readingList', 'runtime', 'scripting', 'search', 'sessions', 'sidePanel', 'storage', 'system.cpu',
|
|
22
22
|
'system.display', 'system.memory', 'system.storage', 'tabCapture', 'tabGroups', 'tabs', 'topSites', 'tts',
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { bundles as oldBundles } from './data/deprecations.js'
|
|
2
2
|
|
|
3
3
|
import manifestKeyOrder from './rules/manifest-key-order.js'
|
|
4
|
+
import manifestSpacing from './rules/manifest-spacing.js'
|
|
4
5
|
import noContentStyleLeak from './rules/no-content-style-leak.js'
|
|
5
6
|
import noDeprecatedAPIs from './rules/no-deprecated-apis.js'
|
|
6
7
|
import noDuplicateManifestKeys from './rules/no-duplicate-manifest-keys.js'
|
|
@@ -18,11 +19,12 @@ import validMV3schema from './rules/valid-mv3-schema.js'
|
|
|
18
19
|
import validServiceWorker from './rules/valid-service-worker.js'
|
|
19
20
|
import validVersionField from './rules/valid-version-field.js'
|
|
20
21
|
|
|
21
|
-
import { bundleRules,
|
|
22
|
+
import { bundleRules, initBundleDeprecation } from './utils.js'
|
|
22
23
|
|
|
23
24
|
const plugin = {
|
|
24
25
|
rules: {
|
|
25
26
|
'manifest-key-order': manifestKeyOrder,
|
|
27
|
+
'manifest-spacing': manifestSpacing,
|
|
26
28
|
'no-content-style-leak': noContentStyleLeak,
|
|
27
29
|
'no-deprecated-apis': noDeprecatedAPIs,
|
|
28
30
|
'no-duplicate-manifest-keys': noDuplicateManifestKeys,
|
|
@@ -48,8 +50,7 @@ for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
|
48
50
|
plugin.configs = {
|
|
49
51
|
recommended: {
|
|
50
52
|
plugins: { 'chrome-extensions': plugin },
|
|
51
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended,
|
|
52
|
-
'chrome-extensions', deprecatedFrom(oldBundles, 'recommended'))
|
|
53
|
+
rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'chrome-extensions')
|
|
53
54
|
},
|
|
54
55
|
strict: {
|
|
55
56
|
plugins: { 'chrome-extensions': plugin },
|
|
@@ -61,13 +62,11 @@ plugin.configs = {
|
|
|
61
62
|
},
|
|
62
63
|
security: {
|
|
63
64
|
plugins: { 'chrome-extensions': plugin },
|
|
64
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.security,
|
|
65
|
-
'chrome-extensions', deprecatedFrom(oldBundles, 'security'))
|
|
65
|
+
rules: bundleRules(plugin.rules, rule => rule.meta.docs.security, 'chrome-extensions')
|
|
66
66
|
},
|
|
67
67
|
manifest: {
|
|
68
68
|
plugins: { 'chrome-extensions': plugin },
|
|
69
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.manifest,
|
|
70
|
-
'chrome-extensions', deprecatedFrom(oldBundles, 'manifest'))
|
|
69
|
+
rules: bundleRules(plugin.rules, rule => rule.meta.docs.manifest, 'chrome-extensions')
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { getFilename } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
const re = { manifestFilename: /manifest\.json$/, leadingWhitespace: /^[ \t]+/ },
|
|
4
|
+
brackets = { open: new Set(['LBrace', 'LBracket']), close: new Set(['RBrace', 'RBracket']) }
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'layout',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Enforces consistent indentation and spacing in `manifest.json`',
|
|
11
|
+
category: 'Stylistic Issues',
|
|
12
|
+
recommended: false,
|
|
13
|
+
manifest: true
|
|
14
|
+
},
|
|
15
|
+
fixable: 'whitespace',
|
|
16
|
+
schema: [{
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
spaces: {
|
|
20
|
+
type: 'number',
|
|
21
|
+
description: 'Number of spaces per indent level (default 2)'
|
|
22
|
+
},
|
|
23
|
+
checkColons: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
description: 'Enforce no spaces before and 1 space after `:` in key/value pairs (default true)'
|
|
26
|
+
},
|
|
27
|
+
checkCommas: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
description: 'Enforce no spaces before and 1 space after `,` in inline objects (default true)'
|
|
30
|
+
},
|
|
31
|
+
checkBraces: {
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
description: 'Enforce 1 space after `{` and before `}` in inline objects (default true)'
|
|
34
|
+
},
|
|
35
|
+
checkBrackets: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: 'Enforce no spaces after `[` or before `]` in inline arrays (default true)'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
additionalProperties: false
|
|
41
|
+
}],
|
|
42
|
+
defaultOptions: [{ spaces: 2, checkColons: true, checkCommas: true,
|
|
43
|
+
checkBraces: true, checkBrackets: true }],
|
|
44
|
+
messages: {
|
|
45
|
+
wrongIndent: 'Expected {{expected}} spaces of indentation (found {{found}}).',
|
|
46
|
+
tabFound: 'Expected spaces for indentation, found tabs.',
|
|
47
|
+
spaceBeforeColon: 'Expected no spaces before `:`.',
|
|
48
|
+
spaceAfterColon: 'Expected 1 space after `:`.',
|
|
49
|
+
spaceBeforeComma: 'Expected no spaces before `,`.',
|
|
50
|
+
spaceAfterComma: 'Expected 1 space after `,`.',
|
|
51
|
+
spaceAfterBrace: 'Expected 1 space after `{`.',
|
|
52
|
+
spaceBeforeBrace: 'Expected 1 space before `}`.',
|
|
53
|
+
spaceInEmptyBraces: 'Expected no spaces in empty `{}`.',
|
|
54
|
+
spaceAfterBracket: 'Expected no spaces after `[`.',
|
|
55
|
+
spaceBeforeBracket: 'Expected no spaces before `]`.'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
create(context) {
|
|
60
|
+
if (!re.manifestFilename.test(getFilename(context))) return {}
|
|
61
|
+
const opts = context.options[0] || {},
|
|
62
|
+
spaces = opts.spaces ?? 2,
|
|
63
|
+
checkColons = opts.checkColons ?? true,
|
|
64
|
+
checkCommas = opts.checkCommas ?? true,
|
|
65
|
+
checkBraces = opts.checkBraces ?? true,
|
|
66
|
+
checkBrackets = opts.checkBrackets ?? true
|
|
67
|
+
return {
|
|
68
|
+
'Document'(document) {
|
|
69
|
+
const sourceCode = context.sourceCode,
|
|
70
|
+
text = sourceCode.getText(),
|
|
71
|
+
lines = text.split('\n'),
|
|
72
|
+
tokens = document.tokens || [],
|
|
73
|
+
lineOffsets = [0]
|
|
74
|
+
for (const line of lines)
|
|
75
|
+
lineOffsets.push(lineOffsets.at(-1) + line.length +1)
|
|
76
|
+
let depth = 0, lastLine = 0
|
|
77
|
+
for (const token of tokens) { // check indentation
|
|
78
|
+
const startLine = token.loc.start.line
|
|
79
|
+
if (startLine != lastLine) {
|
|
80
|
+
const indent = lines[startLine -1].match(re.leadingWhitespace)?.[0] || '',
|
|
81
|
+
effectiveDepth = brackets.close.has(token.type) ? Math.max(0, depth -1) : depth,
|
|
82
|
+
expected = ' '.repeat(effectiveDepth * spaces)
|
|
83
|
+
if (indent != expected) {
|
|
84
|
+
const lineStart = lineOffsets[startLine -1],
|
|
85
|
+
isTab = indent.includes('\t')
|
|
86
|
+
context.report({
|
|
87
|
+
loc: { line: startLine, column: 1 }, messageId: isTab ? 'tabFound' : 'wrongIndent',
|
|
88
|
+
data: { expected: effectiveDepth * spaces, found: indent.length },
|
|
89
|
+
fix(fixer) {
|
|
90
|
+
return fixer.replaceTextRange([lineStart, lineStart + indent.length], expected) }
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
lastLine = startLine
|
|
94
|
+
}
|
|
95
|
+
if (brackets.open.has(token.type)) depth += 1
|
|
96
|
+
else if (brackets.close.has(token.type)) depth -= 1
|
|
97
|
+
}
|
|
98
|
+
for (let t = 0 ; t < tokens.length ; t++) { // check inline spacing
|
|
99
|
+
const token = tokens[t], // eslint-disable-line security/detect-object-injection
|
|
100
|
+
type = token.type,
|
|
101
|
+
prev = tokens[t -1], next = tokens[t +1]
|
|
102
|
+
if (next) {
|
|
103
|
+
const between = text.slice(token.range[1], next.range[0]),
|
|
104
|
+
inline = !between.includes('\n'),
|
|
105
|
+
isColon = type == 'Colon', isComma = type == 'Comma'
|
|
106
|
+
if (checkColons && isColon && inline && between != ' ')
|
|
107
|
+
context.report({
|
|
108
|
+
loc: token.loc.end, messageId: 'spaceAfterColon',
|
|
109
|
+
fix(fixer) { return fixer.replaceTextRange([token.range[1], next.range[0]], ' ') }
|
|
110
|
+
})
|
|
111
|
+
if (checkCommas && isComma && inline && between != ' ')
|
|
112
|
+
context.report({
|
|
113
|
+
loc: token.loc.end, messageId: 'spaceAfterComma',
|
|
114
|
+
fix(fixer) { return fixer.replaceTextRange([token.range[1], next.range[0]], ' ') }
|
|
115
|
+
})
|
|
116
|
+
if (checkBraces && type == 'LBrace' && inline) {
|
|
117
|
+
const empty = next.type == 'RBrace', want = empty ? '' : ' '
|
|
118
|
+
if (between != want)
|
|
119
|
+
context.report({
|
|
120
|
+
loc: token.loc.end,
|
|
121
|
+
messageId: empty ? 'spaceInEmptyBraces' : 'spaceAfterBrace',
|
|
122
|
+
fix(fixer) { return fixer.replaceTextRange([token.range[1], next.range[0]], want) }
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
if (checkBrackets && type == 'LBracket' && inline && between != '')
|
|
126
|
+
context.report({
|
|
127
|
+
loc: token.loc.end, messageId: 'spaceAfterBracket',
|
|
128
|
+
fix(fixer) { return fixer.replaceTextRange([token.range[1], next.range[0]], '') }
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
if (prev) {
|
|
132
|
+
const between = text.slice(prev.range[1], token.range[0]),
|
|
133
|
+
inline = !between.includes('\n'),
|
|
134
|
+
isColon = type == 'Colon', isComma = type == 'Comma'
|
|
135
|
+
if (checkColons && isColon && inline && between != '')
|
|
136
|
+
context.report({
|
|
137
|
+
loc: token.loc.start, messageId: 'spaceBeforeColon',
|
|
138
|
+
fix(fixer) { return fixer.replaceTextRange([prev.range[1], token.range[0]], '') }
|
|
139
|
+
})
|
|
140
|
+
if (checkCommas && isComma && inline && between != '')
|
|
141
|
+
context.report({
|
|
142
|
+
loc: token.loc.start, messageId: 'spaceBeforeComma',
|
|
143
|
+
fix(fixer) { return fixer.replaceTextRange([prev.range[1], token.range[0]], '') }
|
|
144
|
+
})
|
|
145
|
+
if (checkBraces && type == 'RBrace' && prev.type != 'LBrace' && inline && between != ' ')
|
|
146
|
+
context.report({
|
|
147
|
+
loc: token.loc.start, messageId: 'spaceBeforeBrace',
|
|
148
|
+
fix(fixer) { return fixer.replaceTextRange([prev.range[1], token.range[0]], ' ') }
|
|
149
|
+
})
|
|
150
|
+
if (checkBrackets && type == 'RBracket' && prev.type != 'LBracket' && inline && between != '')
|
|
151
|
+
context.report({
|
|
152
|
+
loc: token.loc.start, messageId: 'spaceBeforeBracket',
|
|
153
|
+
fix(fixer) { return fixer.replaceTextRange([prev.range[1], token.range[0]], '') }
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mv2OnlyKeys, forbiddenCSPdirectives } from '../data/manifest.js'
|
|
2
|
-
|
|
3
1
|
import { getFilename, readManifest } from '../utils.js'
|
|
2
|
+
import { mv2OnlyKeys, forbiddenCSPdirectives, ffMV3bgKeys, ffDataCollectionRequired, ffDataCollectionOptional }
|
|
3
|
+
from '../data/manifest.js'
|
|
4
4
|
|
|
5
5
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
6
6
|
|
|
@@ -15,6 +15,7 @@ export default {
|
|
|
15
15
|
},
|
|
16
16
|
schema: [],
|
|
17
17
|
messages: {
|
|
18
|
+
invalidDataCollectionValue: '`{{value}}` is not a valid data collection permission value for {{kind}}.',
|
|
18
19
|
mv2Key: '`{{key}}` is not allowed in MV3. {{reason}}.',
|
|
19
20
|
unsafeCSP: 'MV3 CSP must not include `{{directive}}`.',
|
|
20
21
|
wrongCSPType: '`content_security_policy` must be an object in MV3, not a string.'
|
|
@@ -23,11 +24,11 @@ export default {
|
|
|
23
24
|
|
|
24
25
|
create(context) {
|
|
25
26
|
if (re.manifestFilename.test(getFilename(context))) { // lint it directly
|
|
26
|
-
const ancestors = [], pendingReports = []
|
|
27
|
-
let isMV3 = false
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
return
|
|
27
|
+
const ancestors = [], pendingReports = [], deferredMV2 = []
|
|
28
|
+
let isMV3 = false, isFF = false
|
|
29
|
+
function keyOf(node) {
|
|
30
|
+
const keyNode = node?.name || node?.key
|
|
31
|
+
return keyNode?.value ?? keyNode?.name
|
|
31
32
|
}
|
|
32
33
|
return {
|
|
33
34
|
'*'(node) {
|
|
@@ -36,18 +37,23 @@ export default {
|
|
|
36
37
|
const isTopLevel = ancestors.length == 2
|
|
37
38
|
&& ancestors[0].type == 'Document'
|
|
38
39
|
&& ancestors[1].type == 'Object'
|
|
39
|
-
const inBackground = ancestors.some(
|
|
40
|
-
const inCSP = ancestors.some(
|
|
40
|
+
const inBackground = ancestors.some(ancestor => keyOf(ancestor) == 'background')
|
|
41
|
+
const inCSP = ancestors.some(ancestor => keyOf(ancestor) == 'content_security_policy')
|
|
42
|
+
const inDataCollection = ancestors.some(ancestor =>
|
|
43
|
+
keyOf(ancestor) == 'data_collection_permissions')
|
|
41
44
|
if (keyName == 'manifest_version' && node.value?.value == 3) isMV3 = true
|
|
45
|
+
if (isTopLevel && keyName == 'browser_specific_settings') isFF = true
|
|
42
46
|
if (isTopLevel && mv2OnlyKeys.has(keyName))
|
|
43
47
|
pendingReports.push({ node, messageId: 'mv2Key',
|
|
44
48
|
data: { key: keyName, reason: 'MV2-only key' }})
|
|
45
|
-
if (inBackground && keyName
|
|
46
|
-
|
|
47
|
-
data: { key:
|
|
49
|
+
if (inBackground && ffMV3bgKeys.has(keyName))
|
|
50
|
+
deferredMV2.push({ node, messageId: 'mv2Key',
|
|
51
|
+
data: { key: `background.${keyName}`,
|
|
52
|
+
reason: 'Use background.service_worker instead' }})
|
|
48
53
|
if (inBackground && keyName == 'persistent')
|
|
49
54
|
pendingReports.push({ node, messageId: 'mv2Key',
|
|
50
|
-
data: { key: 'background.persistent',
|
|
55
|
+
data: { key: 'background.persistent',
|
|
56
|
+
reason: 'MV3 service workers are never persistent' }})
|
|
51
57
|
if (isTopLevel && keyName == 'content_security_policy' && node.value?.type == 'String')
|
|
52
58
|
pendingReports.push({ node: node.value, messageId: 'wrongCSPType' })
|
|
53
59
|
if (inCSP && keyName == 'extension_pages' && typeof node.value?.value == 'string')
|
|
@@ -55,6 +61,17 @@ export default {
|
|
|
55
61
|
if (node.value.value.includes(directive))
|
|
56
62
|
pendingReports.push({ node: node.value, messageId: 'unsafeCSP',
|
|
57
63
|
data: { directive }})
|
|
64
|
+
if (inDataCollection && (keyName == 'required' || keyName == 'optional')
|
|
65
|
+
&& Array.isArray(node.value?.elements)
|
|
66
|
+
) for (const el of node.value.elements) {
|
|
67
|
+
const value = typeof el.value == 'string' ? el.value : el.value?.value
|
|
68
|
+
if (typeof value == 'string') {
|
|
69
|
+
const set = keyName == 'required' ? ffDataCollectionRequired : ffDataCollectionOptional
|
|
70
|
+
if (!set.has(value))
|
|
71
|
+
pendingReports.push({ node: el, messageId: 'invalidDataCollectionValue',
|
|
72
|
+
data: { value, kind: keyName }})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
58
75
|
}
|
|
59
76
|
ancestors.push(node)
|
|
60
77
|
},
|
|
@@ -62,20 +79,23 @@ export default {
|
|
|
62
79
|
ancestors.pop()
|
|
63
80
|
if (node.type == 'Document') {
|
|
64
81
|
if (!isMV3) return
|
|
65
|
-
|
|
82
|
+
if (!isFF) pendingReports.push(...deferredMV2)
|
|
83
|
+
for (const report of pendingReports) context.report(report)
|
|
66
84
|
}
|
|
67
85
|
}
|
|
68
86
|
}
|
|
69
87
|
}
|
|
70
88
|
const manifest = readManifest(context) ; if (!manifest || manifest.manifest_version != 3) return {}
|
|
89
|
+
const isFF = manifest.browser_specific_settings != null
|
|
71
90
|
return {
|
|
72
|
-
Program(node) {
|
|
91
|
+
Program(node) { /* eslint-disable security/detect-object-injection */
|
|
73
92
|
for (const key of Object.keys(manifest))
|
|
74
93
|
if (mv2OnlyKeys.has(key))
|
|
75
94
|
context.report({ node, messageId: 'mv2Key', data: { key, reason: 'MV2-only key' }})
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
for (const key of ffMV3bgKeys)
|
|
96
|
+
if (manifest.background?.[key] && !isFF)
|
|
97
|
+
context.report({ node, messageId: 'mv2Key',
|
|
98
|
+
data: { key: `background.${key}`, reason: 'Use background.service_worker instead' }})
|
|
79
99
|
if (manifest.background?.persistent != null)
|
|
80
100
|
context.report({ node, messageId: 'mv2Key',
|
|
81
101
|
data: { key: 'background.persistent', reason: 'MV3 service workers are never persistent' }})
|
|
@@ -87,7 +107,16 @@ export default {
|
|
|
87
107
|
for (const directive of forbiddenCSPdirectives)
|
|
88
108
|
if (cspPages.includes(directive))
|
|
89
109
|
context.report({ node, messageId: 'unsafeCSP', data: { directive }})
|
|
90
|
-
|
|
110
|
+
if (isFF) { // validate data collection permissions
|
|
111
|
+
const dcp = manifest.browser_specific_settings?.gecko?.data_collection_permissions
|
|
112
|
+
for (const [kind, set] of [['required', ffDataCollectionRequired], ['optional', ffDataCollectionOptional]])
|
|
113
|
+
if (Array.isArray(dcp?.[kind]))
|
|
114
|
+
for (const value of dcp[kind])
|
|
115
|
+
if (!set.has(value))
|
|
116
|
+
context.report({ node, messageId: 'invalidDataCollectionValue',
|
|
117
|
+
data: { value, kind }})
|
|
118
|
+
}
|
|
119
|
+
} /* eslint-enable security/detect-object-injection */
|
|
91
120
|
}
|
|
92
121
|
}
|
|
93
122
|
}
|
|
@@ -30,13 +30,18 @@ export default {
|
|
|
30
30
|
&& node.arguments[1]?.type == 'Literal'
|
|
31
31
|
&& typeof node.arguments[1].value == 'number'
|
|
32
32
|
&& node.arguments[1].value < serviceWorkerKeepaliveMinMS
|
|
33
|
+
&& !isInsideNestedFunction(node)
|
|
33
34
|
) context.report({ node, messageId: 'keepaliveTimer', data: { fn: node.callee.name }})
|
|
34
35
|
},
|
|
35
36
|
Identifier(node) {
|
|
36
37
|
if (node.parent?.type == 'MemberExpression' && node.parent.property === node) return
|
|
37
38
|
if (node.parent?.type == 'Property' && node.parent.key === node && !node.parent.computed) return
|
|
38
|
-
if (domGlobals.has(node.name))
|
|
39
|
-
|
|
39
|
+
if (!domGlobals.has(node.name)) return
|
|
40
|
+
for (let scope = context.sourceCode.getScope(node) ;
|
|
41
|
+
scope && scope.type != 'global' ;
|
|
42
|
+
scope = scope.upper
|
|
43
|
+
) if (scope.set.has(node.name)) return // shadowed by a local binding
|
|
44
|
+
context.report({ node, messageId: 'domInSW', data: { global: node.name }})
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
}
|
package/src/utils.js
CHANGED
|
@@ -4,24 +4,21 @@ import path from 'node:path'
|
|
|
4
4
|
const re = { chromeAPI: /\bchrome(?:\.\w+)+/g }, // eslint-disable-line security/detect-unsafe-regex
|
|
5
5
|
manifestCache = new Map()
|
|
6
6
|
|
|
7
|
-
export function bundleRules(rules, predicate, pluginName
|
|
7
|
+
export function bundleRules(rules, predicate, pluginName) {
|
|
8
8
|
return Object.fromEntries(Object.entries(rules)
|
|
9
|
-
.filter(([
|
|
9
|
+
.filter(([, rule]) => predicate(rule))
|
|
10
10
|
.map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (plugin.rules[ruleId]) // eslint-disable-line security/detect-object-injection
|
|
23
|
-
console.warn(`[${pluginName}] '${ruleId}' will be removed from '${
|
|
24
|
-
bundleName}' in a future major release${note ? ` (${note})` : ''}`)
|
|
13
|
+
export function initBundleDeprecation(plugin, [ruleId, bundleNames], pluginName) {
|
|
14
|
+
for (const bundleName of bundleNames) {
|
|
15
|
+
/* eslint-disable security/detect-object-injection */
|
|
16
|
+
if (plugin.configs[bundleName])
|
|
17
|
+
plugin.configs[bundleName].rules[`${pluginName}/${ruleId}`] = 'error'
|
|
18
|
+
/* eslint-enable security/detect-object-injection */
|
|
19
|
+
if (process.env.DEBUG_MODE)
|
|
20
|
+
console.warn(`[${pluginName}] '${ruleId}' will be removed from '${bundleName}' in a future major release`)
|
|
21
|
+
}
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
export function findExtensionRoot(context) {
|