eslint-plugin-chrome-extensions 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ESLint rules for Chrome extensions.
14
14
 
15
- <img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/0b5e3e49/assets/images/screenshots/vs-code/squiggles.png">
15
+ <img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/04d2f53b22/assets/images/screenshots/vs-code/squiggles.png">
16
16
 
17
17
  ---
18
18
 
@@ -45,8 +45,7 @@ export default [
45
45
  plugins: { 'chrome-extensions': chromeExtensions },
46
46
  rules: {
47
47
  ...chromeExtensions.configs.recommended.rules,
48
- 'chrome-extensions/no-insecure-messaging': 'off', // selectively disable rule
49
- 'chrome-extensions/no-deprecated-apis': ['error', { target: 'mv3' }] // selectively tweak rule
48
+ 'chrome-extensions/no-deprecated-apis': 'off' // selectively disable rule
50
49
  }
51
50
  },
52
51
  {
@@ -54,14 +53,13 @@ export default [
54
53
  plugins: { 'chrome-extensions': chromeExtensions, json },
55
54
  rules: {
56
55
  ...chromeExtensions.configs.manifest.rules,
57
- 'chrome-extensions/require-mv3': 'off', // selectively disable rule
58
56
  'chrome-extensions/valid-version-field': ['error', { versionType: 'date' }] // selectively tweak rule
59
57
  }
60
58
  }
61
59
  ]
62
60
  ```
63
61
 
64
- Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only), `manifest`
62
+ Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only), `security`, `manifest`
65
63
 
66
64
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
67
65
 
@@ -74,6 +72,8 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
74
72
 
75
73
  <h3 id="manifest-key-order">
76
74
  <code>manifest-key-order</code>
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-32fcee?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">
@@ -99,8 +99,8 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
99
99
  /* eslint chrome-extensions/manifest-key-order: ['error', { order: ['name', 'manifest_version'] }] */
100
100
 
101
101
  {
102
- "manifest_version": 3,
103
- ! "name": "My Extension",
102
+ ! "manifest_version": 3,
103
+ "name": "My Extension",
104
104
  "version": "1.0.0"
105
105
  }
106
106
  ```
@@ -133,7 +133,7 @@ If you don't care about the order of manifest keys.
133
133
 
134
134
  <div align="center">
135
135
 
136
- [Back to index ↑][epc-rules]
136
+ [Back to index ↑][epce-rules]
137
137
 
138
138
  </div>
139
139
 
@@ -185,7 +185,7 @@ If you intentionally interact w/ host page styles.
185
185
 
186
186
  <div align="center">
187
187
 
188
- [Back to index ↑][epc-rules]
188
+ [Back to index ↑][epce-rules]
189
189
 
190
190
  </div>
191
191
 
@@ -250,7 +250,123 @@ If you don't want deprecation validation at all. For individual deprecated APIs
250
250
 
251
251
  <div align="center">
252
252
 
253
- [Back to index ↑][epc-rules]
253
+ [Back to index ↑][epce-rules]
254
+
255
+ </div>
256
+
257
+ ---
258
+
259
+ </details>
260
+
261
+ <h3 id="no-duplicate-manifest-keys">
262
+ <code>no-duplicate-manifest-keys</code>
263
+ <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-32fcee?style=flat-square" align="middle"></a>
265
+ <a title="Copy rule URL" target="_blank"
266
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-duplicate-manifest-keys">
267
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
268
+ </h3>
269
+
270
+ <details>
271
+
272
+ <summary>Disallows duplicate keys in <code>manifest.json</code>.</summary>
273
+
274
+ #### <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
275
+
276
+ ```diff
277
+ {
278
+ "manifest_version": 3,
279
+ ! "name": "Foo",
280
+ ! "name": "Bar",
281
+ ! "version": "1.0.0",
282
+ ! "version": "2.0.0"
283
+ }
284
+ ```
285
+
286
+ #### <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
287
+
288
+ ```diff
289
+ {
290
+ "manifest_version": 3,
291
+ - "name": "Foo",
292
+ - "name": "Bar",
293
+ "name": "My Extension",
294
+ - "version": "1.0.0",
295
+ - "version": "2.0.0"
296
+ "version": "1.0.0"
297
+ }
298
+ ```
299
+
300
+ #### When not to use it
301
+
302
+ If you rely on a Chromium fork resolving duplicate keys in a specific order.
303
+
304
+ #### Introduced in
305
+
306
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
307
+
308
+ #### See also
309
+
310
+ - [`manifest-key-order`](#manifest-key-order)
311
+ - [`no-unused-permissions`](#no-unused-permissions)
312
+
313
+ <div align="center">
314
+
315
+ [Back to index ↑][epce-rules]
316
+
317
+ </div>
318
+
319
+ ---
320
+
321
+ </details>
322
+
323
+ <h3 id="no-insecure-fetch">
324
+ <code>no-insecure-fetch</code>
325
+ <a title="Included in recommended config" href="#usage">
326
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
327
+ <a title="Included in security config" href="#usage">
328
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
329
+ <a title="Copy rule URL" target="_blank"
330
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-insecure-fetch">
331
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
332
+ </h3>
333
+
334
+ <details>
335
+
336
+ <summary>Flags <code>fetch()</code> and <code>XMLHttpRequest.open()</code> calls that use <code>http://</code>.</summary>
337
+
338
+ #### <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
339
+
340
+ ```diff
341
+ ! fetch('http://api.example.com/data')
342
+ ! xhr.open('GET', 'http://cdn.example.com/lib.js')
343
+ ```
344
+
345
+ #### <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
346
+
347
+ ```diff
348
+ - fetch('http://api.example.com/data')
349
+ + fetch('https://api.example.com/data')
350
+ - xhr.open('GET', 'http://cdn.example.com/lib.js')
351
+ + xhr.open('GET', 'https://cdn.example.com/lib.js')
352
+ ```
353
+
354
+ #### When not to use it
355
+
356
+ If you intentionally target HTTP endpoints.
357
+
358
+ #### Introduced in
359
+
360
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
361
+
362
+ #### See also
363
+
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)
366
+
367
+ <div align="center">
368
+
369
+ [Back to index ↑][epce-rules]
254
370
 
255
371
  </div>
256
372
 
@@ -262,6 +378,10 @@ If you don't want deprecation validation at all. For individual deprecated APIs
262
378
  <code>no-insecure-messaging</code>
263
379
  <a title="Included in recommended config" href="#usage">
264
380
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
381
+ <a title="Included in security config" href="#usage">
382
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
383
+ <a title="Accepts array w/ options" href="#usage">
384
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
265
385
  <a title="Copy rule URL" target="_blank"
266
386
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-insecure-messaging">
267
387
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -269,7 +389,11 @@ If you don't want deprecation validation at all. For individual deprecated APIs
269
389
 
270
390
  <details>
271
391
 
272
- <summary>Requires external message handlers validate <code>sender</code> + flags unfiltered <code>tabs.query()</code>.</summary>
392
+ <summary>Requires sender/origin validation + filtered <code>tabs.query()</code> + explicit <code>window.postMessage()</code> target.</summary>
393
+
394
+ #### Options
395
+
396
+ - `disallowWildcardTarget` (default `true`) – flag <code>postMessage()</code> calls that use <code>'*'</code> as <code>targetOrigin</code>
273
397
 
274
398
  #### <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
275
399
 
@@ -278,9 +402,15 @@ If you don't want deprecation validation at all. For individual deprecated APIs
278
402
  ! handle(msg)
279
403
  })
280
404
 
405
+ window.addEventListener('message', event => {
406
+ ! handle(event.data)
407
+ })
408
+
281
409
  ! chrome.tabs.query({})
282
410
  .then(tabs => tabs.forEach(tab =>
283
411
  chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
412
+
413
+ ! window.postMessage(payload, '*')
284
414
  ```
285
415
 
286
416
  #### <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
@@ -291,15 +421,23 @@ If you don't want deprecation validation at all. For individual deprecated APIs
291
421
  handle(msg)
292
422
  })
293
423
 
424
+ window.addEventListener('message', event => {
425
+ + if (event.origin != 'https://allowed-origin.com') return
426
+ handle(event.data)
427
+ })
428
+
294
429
  - chrome.tabs.query({})
295
430
  + chrome.tabs.query({ url: 'https://allowed-origin.com/*' })
296
431
  .then(tabs => tabs.forEach(tab =>
297
432
  chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
433
+
434
+ - window.postMessage(payload, '*')
435
+ + window.postMessage(payload, 'https://allowed-origin.com')
298
436
  ```
299
437
 
300
438
  #### When not to use it
301
439
 
302
- If you trust every external caller and open tab.
440
+ If you trust every external caller and open tab, or you intentionally broadcast to any origin.
303
441
 
304
442
  #### Introduced in
305
443
 
@@ -309,10 +447,11 @@ If you trust every external caller and open tab.
309
447
 
310
448
  - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onMessageExternal">Chrome for Developers — `runtime.onMessageExternal`</a>
311
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>
312
451
 
313
452
  <div align="center">
314
453
 
315
- [Back to index ↑][epc-rules]
454
+ [Back to index ↑][epce-rules]
316
455
 
317
456
  </div>
318
457
 
@@ -324,6 +463,8 @@ If you trust every external caller and open tab.
324
463
  <code>no-invalid-permissions</code>
325
464
  <a title="Included in recommended config" href="#usage">
326
465
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
466
+ <a title="Works w/ 'json/json' config block" href="#usage">
467
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
327
468
  <a title="Accepts array w/ options" href="#usage">
328
469
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
329
470
  <a title="Copy rule URL" target="_blank"
@@ -379,7 +520,7 @@ If you don't want permission validation at all. For custom permissions from a Ch
379
520
 
380
521
  <div align="center">
381
522
 
382
- [Back to index ↑][epc-rules]
523
+ [Back to index ↑][epce-rules]
383
524
 
384
525
  </div>
385
526
 
@@ -442,7 +583,70 @@ If you use APIs through aliases or wrappers that obscure the original call site.
442
583
 
443
584
  <div align="center">
444
585
 
445
- [Back to index ↑][epc-rules]
586
+ [Back to index ↑][epce-rules]
587
+
588
+ </div>
589
+
590
+ ---
591
+
592
+ </details>
593
+
594
+ <h3 id="no-missing-web-accessible-resources">
595
+ <code>no-missing-web-accessible-resources</code>
596
+ <a title="Included in recommended config" href="#usage">
597
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
598
+ <a title="Copy rule URL" target="_blank"
599
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-missing-web-accessible-resources">
600
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
601
+ </h3>
602
+
603
+ <details>
604
+
605
+ <summary>Flags <code>chrome.runtime.getURL()</code> paths missing from <code>web_accessible_resources</code>.</summary>
606
+
607
+ #### <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
608
+
609
+ ```diff
610
+ // manifest.json
611
+ {
612
+ "manifest_version": 3,
613
+ ! "web_accessible_resources": []
614
+ }
615
+
616
+ // content.js
617
+ ! img.src = chrome.runtime.getURL('logo.png')
618
+ ```
619
+
620
+ #### <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
621
+
622
+ ```diff
623
+ // manifest.json
624
+ {
625
+ "manifest_version": 3,
626
+ - "web_accessible_resources": []
627
+ + "web_accessible_resources": [{ "resources": ["logo.png"], "matches": ["<all_urls>"] }]
628
+ }
629
+
630
+ // content.js
631
+ img.src = chrome.runtime.getURL('logo.png')
632
+ ```
633
+
634
+ #### When not to use it
635
+
636
+ If your paths are built at runtime.
637
+
638
+ #### Introduced in
639
+
640
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
641
+
642
+ #### See also
643
+
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>
646
+
647
+ <div align="center">
648
+
649
+ [Back to index ↑][epce-rules]
446
650
 
447
651
  </div>
448
652
 
@@ -454,6 +658,8 @@ If you use APIs through aliases or wrappers that obscure the original call site.
454
658
  <code>no-remote-code</code>
455
659
  <a title="Included in recommended config" href="#usage">
456
660
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
661
+ <a title="Included in security config" href="#usage">
662
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
457
663
  <a title="Accepts array w/ options" href="#usage">
458
664
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
459
665
  <a title="Copy rule URL" target="_blank"
@@ -492,6 +698,10 @@ If you use APIs through aliases or wrappers that obscure the original call site.
492
698
  + chrome.scripting.executeScript({ target: { tabId }, func: doThing, args: [value] })
493
699
  ```
494
700
 
701
+ #### When not to use it
702
+
703
+ If you intentionally target forks w/ relaxed CSPs. For individual patterns you still need, prefer narrowing via `patterns` over disabling the rule.
704
+
495
705
  #### Introduced in
496
706
 
497
707
  <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
@@ -503,7 +713,71 @@ If you use APIs through aliases or wrappers that obscure the original call site.
503
713
 
504
714
  <div align="center">
505
715
 
506
- [Back to index ↑][epc-rules]
716
+ [Back to index ↑][epce-rules]
717
+
718
+ </div>
719
+
720
+ ---
721
+
722
+ </details>
723
+
724
+ <h3 id="no-unrestricted-host-permissions">
725
+ <code>no-unrestricted-host-permissions</code>
726
+ <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-32fcee?style=flat-square" align="middle"></a>
728
+ <a title="Accepts array w/ options" href="#usage">
729
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
730
+ <a title="Copy rule URL" target="_blank"
731
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unrestricted-host-permissions">
732
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
733
+ </h3>
734
+
735
+ <details>
736
+
737
+ <summary>Disallows broad host permissions that grant access to every site.</summary>
738
+
739
+ #### Options
740
+
741
+ - `allowedPatterns` (default `[]`) – host patterns to explicitly allow (e.g. `['<all_urls>']`)
742
+
743
+ #### <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
744
+
745
+ ```diff
746
+ {
747
+ "manifest_version": 3,
748
+ ! "host_permissions": ["<all_urls>"],
749
+ ! "optional_host_permissions": ["*://*/*"]
750
+ }
751
+ ```
752
+
753
+ #### <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
754
+
755
+ ```diff
756
+ {
757
+ "manifest_version": 3,
758
+ - "host_permissions": ["<all_urls>"],
759
+ + "host_permissions": ["https://example.com/*", "https://*.trusted.com/*"],
760
+ - "optional_host_permissions": ["*://*/*"]
761
+ + "optional_host_permissions": ["https://user-content.example.com/*"]
762
+ }
763
+ ```
764
+
765
+ #### When not to use it
766
+
767
+ If you legitimately need broad access (e.g. ad blockers or dev tools).
768
+
769
+ #### Introduced in
770
+
771
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
772
+
773
+ #### See also
774
+
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>
777
+
778
+ <div align="center">
779
+
780
+ [Back to index ↑][epce-rules]
507
781
 
508
782
  </div>
509
783
 
@@ -513,8 +787,8 @@ If you use APIs through aliases or wrappers that obscure the original call site.
513
787
 
514
788
  <h3 id="no-unused-permissions">
515
789
  <code>no-unused-permissions</code>
516
- <a title="Included in recommended + strict configs" href="#usage">
517
- <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
790
+ <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-32fcee?style=flat-square" align="middle"></a>
518
792
  <a title="Copy rule URL" target="_blank"
519
793
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unused-permissions">
520
794
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -564,7 +838,7 @@ If you access the granted API through an alias or computed property rather than
564
838
 
565
839
  <div align="center">
566
840
 
567
- [Back to index ↑][epc-rules]
841
+ [Back to index ↑][epce-rules]
568
842
 
569
843
  </div>
570
844
 
@@ -576,6 +850,8 @@ If you access the granted API through an alias or computed property rather than
576
850
  <code>require-mv3</code>
577
851
  <a title="Included in recommended config" href="#usage">
578
852
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
853
+ <a title="Works w/ 'json/json' config block" href="#usage">
854
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
579
855
  <a title="Copy rule URL" target="_blank"
580
856
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23require-mv3">
581
857
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -608,7 +884,7 @@ If you access the granted API through an alias or computed property rather than
608
884
 
609
885
  #### When not to use it
610
886
 
611
- If you intentionally target MV2 for a Chromium fork that still supports it.
887
+ If you intentionally target MV2 for a browser that still supports it (e.g. Firefox).
612
888
 
613
889
  #### Introduced in
614
890
 
@@ -620,7 +896,78 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
620
896
 
621
897
  <div align="center">
622
898
 
623
- [Back to index ↑][epc-rules]
899
+ [Back to index ↑][epce-rules]
900
+
901
+ </div>
902
+
903
+ ---
904
+
905
+ </details>
906
+
907
+ <h3 id="valid-manifest-icons">
908
+ <code>valid-manifest-icons</code>
909
+ <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-32fcee?style=flat-square" align="middle"></a>
911
+ <a title="Accepts array w/ options" href="#usage">
912
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
913
+ <a title="Copy rule URL" target="_blank"
914
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-manifest-icons">
915
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
916
+ </h3>
917
+
918
+ <details>
919
+
920
+ <summary>Ensures manifest <code>icons</code> exist and use the recommended sizes.</summary>
921
+
922
+ #### Options
923
+
924
+ - `requireRecommendedSizes` (default `true`) – require all of `16`, `32`, `48`, `128`
925
+ - `checkIconExists` (default `true`) – flag icon paths that don't resolve to an existing file
926
+ - `noSVG` (default `true`) – flag SVG icon paths (unsupported by Chrome)
927
+
928
+ #### <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
929
+
930
+ ```diff
931
+ {
932
+ "manifest_version": 3,
933
+ "icons": {
934
+ ! "48": "icon48.svg",
935
+ "256": "icon256.png"
936
+ }
937
+ }
938
+ ```
939
+
940
+ #### <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
941
+
942
+ ```diff
943
+ {
944
+ "manifest_version": 3,
945
+ "icons": {
946
+ + "16": "icon16.png",
947
+ + "32": "icon32.png",
948
+ - "48": "icon48.svg",
949
+ + "48": "icon48.png",
950
+ + "128": "icon128.png",
951
+ "256": "icon256.png"
952
+ }
953
+ }
954
+ ```
955
+
956
+ #### When not to use it
957
+
958
+ If your icons are injected at build time, or you target browsers that support SVGs (e.g. Firefox).
959
+
960
+ #### Introduced in
961
+
962
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
963
+
964
+ #### See also
965
+
966
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/icons">Chrome for Developers — `icons`</a>
967
+
968
+ <div align="center">
969
+
970
+ [Back to index ↑][epce-rules]
624
971
 
625
972
  </div>
626
973
 
@@ -632,6 +979,8 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
632
979
  <code>valid-mv3-schema</code>
633
980
  <a title="Included in recommended config" href="#usage">
634
981
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
982
+ <a title="Works w/ 'json/json' config block" href="#usage">
983
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
635
984
  <a title="Copy rule URL" target="_blank"
636
985
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-mv3-schema">
637
986
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -670,7 +1019,7 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
670
1019
 
671
1020
  #### When not to use it
672
1021
 
673
- If you intentionally target MV2 for a Chromium fork that still supports it.
1022
+ If you intentionally target MV2 for a browser that still supports it (e.g. Firefox).
674
1023
 
675
1024
  #### Introduced in
676
1025
 
@@ -683,7 +1032,7 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
683
1032
 
684
1033
  <div align="center">
685
1034
 
686
- [Back to index ↑][epc-rules]
1035
+ [Back to index ↑][epce-rules]
687
1036
 
688
1037
  </div>
689
1038
 
@@ -741,7 +1090,7 @@ If your background context isn't a service worker.
741
1090
 
742
1091
  <div align="center">
743
1092
 
744
- [Back to index ↑][epc-rules]
1093
+ [Back to index ↑][epce-rules]
745
1094
 
746
1095
  </div>
747
1096
 
@@ -753,6 +1102,8 @@ If your background context isn't a service worker.
753
1102
  <code>valid-version-field</code>
754
1103
  <a title="Included in recommended config" href="#usage">
755
1104
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
1105
+ <a title="Works w/ 'json/json' config block" href="#usage">
1106
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
756
1107
  <a title="Accepts array w/ options" href="#usage">
757
1108
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
758
1109
  <a title="Copy rule URL" target="_blank"
@@ -775,7 +1126,6 @@ If your background context isn't a service worker.
775
1126
  ```diff
776
1127
  /* eslint chrome-extensions/valid-version-field: ['error', { versionType: 'date' }] */
777
1128
 
778
- // manifest.json
779
1129
  {
780
1130
  "manifest_version": 3,
781
1131
  ! "version": "2026.1.111",
@@ -788,7 +1138,6 @@ If your background context isn't a service worker.
788
1138
  ```diff
789
1139
  /* eslint chrome-extensions/valid-version-field: ['error', { versionType: 'date' }] */
790
1140
 
791
- // manifest.json
792
1141
  {
793
1142
  "manifest_version": 3,
794
1143
  - "version": "2026.1.111",
@@ -811,7 +1160,7 @@ If you don't care about version format consistency, or your version is injected
811
1160
 
812
1161
  <div align="center">
813
1162
 
814
- [Back to index ↑][epc-rules]
1163
+ [Back to index ↑][epce-rules]
815
1164
 
816
1165
  </div>
817
1166
 
@@ -902,4 +1251,4 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
902
1251
 
903
1252
  <!-- Links -->
904
1253
 
905
- [epc-rules]: #supported-rules
1254
+ [epce-rules]: #supported-rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-chrome-extensions",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "ESLint rules for Chrome extensions",
5
5
  "type": "module",
6
6
  "engines": {
@@ -68,7 +68,7 @@
68
68
  "@eslint/markdown": "^8.0.3",
69
69
  "@stylistic/eslint-plugin": "^5.10.0",
70
70
  "eslint": "^10.12.0",
71
- "eslint-plugin-constructors": "^1.4.0",
71
+ "eslint-plugin-constructors": "^1.4.1",
72
72
  "eslint-plugin-eslint-plugin": "^7.6.2",
73
73
  "eslint-plugin-import-x": "^4.17.1",
74
74
  "eslint-plugin-n": "^18.4.1",
@@ -0,0 +1,10 @@
1
+ // Delete a line to end support
2
+
3
+ export const bundles = { // ruleId: [bundleName, note]
4
+ 'no-invalid-permissions': ['recommended'],
5
+ 'require-mv3': ['recommended'],
6
+ 'valid-mv3-schema': ['recommended'],
7
+ 'valid-version-field': ['recommended']
8
+ }
9
+
10
+ export default { bundles }