eslint-plugin-chrome-extensions 2.8.1 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +76 -83
  2. package/docs/LICENSE.md +1 -1
  3. package/package.json +14 -10
  4. package/src/rules/css-spacing.js +9 -14
  5. package/src/rules/manifest-key-order.js +6 -5
  6. package/src/rules/manifest-spacing.js +5 -8
  7. package/src/rules/no-content-style-leak.js +4 -3
  8. package/src/rules/no-deprecated-apis.js +6 -6
  9. package/src/rules/no-global-keyframes.js +5 -8
  10. package/src/rules/no-host-page-selectors.js +12 -16
  11. package/src/rules/no-inline-event-handlers.js +7 -5
  12. package/src/rules/no-inline-scripts.js +10 -6
  13. package/src/rules/no-insecure-fetch.js +10 -6
  14. package/src/rules/no-insecure-messaging.js +8 -10
  15. package/src/rules/no-invalid-locales.js +7 -6
  16. package/src/rules/no-invalid-permissions.js +5 -5
  17. package/src/rules/no-missing-default-locale.js +5 -4
  18. package/src/rules/no-missing-i18n-keys.js +7 -5
  19. package/src/rules/no-missing-permissions.js +1 -1
  20. package/src/rules/no-missing-web-accessible-resources.js +10 -10
  21. package/src/rules/no-remote-code.js +30 -33
  22. package/src/rules/no-remote-objects.js +7 -6
  23. package/src/rules/no-remote-urls.js +11 -12
  24. package/src/rules/no-scrollbar-styling.js +6 -6
  25. package/src/rules/no-sensitive-permissions.js +6 -4
  26. package/src/rules/no-unrestricted-host-permissions.js +6 -7
  27. package/src/rules/no-unused-i18n-keys.js +5 -4
  28. package/src/rules/require-short-name-when-useful.js +3 -8
  29. package/src/rules/valid-filenames.js +9 -11
  30. package/src/rules/valid-license-file.js +7 -6
  31. package/src/rules/valid-manifest-icons.js +14 -20
  32. package/src/rules/valid-service-worker.js +8 -10
  33. package/src/rules/valid-url-fields.js +8 -13
  34. package/src/rules/valid-version-field.js +5 -6
  35. package/src/utils/extension.js +7 -3
  36. package/src/utils/init.js +5 -4
  37. package/src/utils/node.js +21 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <a href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/src/branch/main/docs/LICENSE.md">
8
8
  <img alt="[License: MIT]" src="https://img.shields.io/badge/License-MIT-f1f20a.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
9
9
  <a target="_blank" href="https://getnpmstats.com/?package=eslint-plugin-chrome-extensions&range=last-year&interval=monthly">
10
- <img src="https://img.shields.io/npm/dm/eslint-plugin-chrome-extensions?style=for-the-badge&logo=npm&color=black"></a>
10
+ <img alt="[Monthly npm downloads]" src="https://img.shields.io/npm/dm/eslint-plugin-chrome-extensions?style=for-the-badge&logo=npm&color=black"></a>
11
11
  <a href="#usage">
12
12
  <img alt="[Supports ESLint >=9.6.0 / Oxlint >=1.65.0]" src="https://img.shields.io/badge/Supports-ESLint_%3E%3D9.6.0_%2F_Oxlint_%3E%3D1.65.0-4B32C3?style=for-the-badge&logo=eslint"></a>
13
13
  <br><br>
@@ -44,9 +44,8 @@ import chromeExtensions from 'eslint-plugin-chrome-extensions'
44
44
  export default [
45
45
  // other configs
46
46
  {
47
- files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
48
- plugins: { 'chrome-extensions': chromeExtensions },
49
- rules: {
47
+ files: ['**/extension/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}'], ...chromeExtensions.configs.recommended,
48
+ rules: { // include this block if customizing rules
50
49
  ...chromeExtensions.configs.recommended.rules,
51
50
  'chrome-extensions/no-deprecated-apis': 'off', // selectively disable rule
52
51
  'chrome-extensions/valid-version-field': ['error', { versionType: 'date' }] // selectively tweak rule
@@ -68,7 +67,7 @@ export default [
68
67
 
69
68
  ### <img height=15 width=auto src="https://www.google.com/s2/favicons?domain=oxc.rs&sz=64"> Oxlint
70
69
 
71
- Add a JS bundle to your `oxlint.config.ts` (non-JS parsing is unsupported by Oxlint):
70
+ Add a JS/TS bundle to your `oxlint.config.*ts` (non-JS/TS parsing is unsupported by Oxlint):
72
71
 
73
72
  ```ts
74
73
  ...
@@ -78,7 +77,7 @@ export default defineConfig({
78
77
  overrides: [
79
78
  // other configs
80
79
  {
81
- files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
80
+ files: ['**/extension/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}'],
82
81
  jsPlugins: ['eslint-plugin-chrome-extensions'],
83
82
  rules: {
84
83
  ...chromeExtensions.configs.recommended.rules,
@@ -96,7 +95,7 @@ export default defineConfig({
96
95
 
97
96
  ---
98
97
 
99
- *Available JS bundles:* `recommended`, `strict` (everything), `problem` (deterministic bugs only), `security`
98
+ *Available JS/TS bundles:* `recommended`, `strict` (everything), `problem` (deterministic bugs only), `security`
100
99
 
101
100
  *Available filetype bundles (ESLint only):* `manifest`, `license`, `css`, `html`, `json`, `json5`, `jsonc`
102
101
 
@@ -595,10 +594,78 @@ If your CSS is scoped inside a Shadow DOM root.
595
594
 
596
595
  </details>
597
596
 
597
+ <h3 id="no-host-page-selectors">
598
+ <code>no-host-page-selectors</code>
599
+ <a title="Only works in 'css/css' config block" href="#usage">
600
+ <img height=18 alt="[CSS only]" src="https://img.shields.io/badge/CSS_only-dcd6f7?style=flat-square" align="middle"></a>
601
+ <a title="Accepts array w/ options" href="#usage">
602
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
603
+ <a title="Copy rule URL" target="_blank"
604
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-host-page-selectors">
605
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
606
+ </h3>
607
+
608
+ <details>
609
+
610
+ <summary>Disallows host-page selectors (<code>html</code>, <code>body</code>, <code>:root</code>, <code>*</code>, bare elem types) in content-script CSS.</summary>
611
+
612
+ <h4 id="no-host-page-selectors-options">Options</h4>
613
+
614
+ - `allowedSelectors` (default `[]`) – selectors to explicitly allow (e.g. `['body.dark']`), as a single string or array <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
615
+ - `disallowUniversal` (default `true`) – flag `*` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
616
+ - `disallowStructural` (default `true`) – flag `html`, `body`, `:root` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
617
+ - `disallowBareElements` (default `true`) – flag elem types (e.g. `div`, `a`) that lack `class`/`id`/`attribute` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
618
+
619
+ #### <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
620
+
621
+ ```diff
622
+ ! * { box-sizing: border-box }
623
+
624
+ ! body { margin: 0 }
625
+ ! html { font-family: sans-serif }
626
+ ! button:hover { opacity: 0.8 }
627
+ ```
628
+
629
+ #### <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
630
+
631
+ ```diff
632
+ - * { box-sizing: border-box }
633
+ + :host { box-sizing: border-box }
634
+
635
+ - body { margin: 0 }
636
+ + :host { margin: 0 }
637
+ - html { font-family: sans-serif }
638
+ + :host { font-family: sans-serif }
639
+ - button:hover { opacity: 0.8 }
640
+ + .myext-button:hover { opacity: 0.8 }
641
+ ```
642
+
643
+ #### When not to use it
644
+
645
+ If you intentionally style the host page. For individual selectors you still need, prefer `allowedSelectors` over disabling the rule.
646
+
647
+ #### Introduced in
648
+
649
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0">v2.7.0</a> (October 17, 2026)
650
+
651
+ #### See also
652
+
653
+ - [`no-content-style-leak`](#no-content-style-leak)
654
+ <a title="Only included in strict config" href="#usage"><img height=14 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
655
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:host">MDN — `:host`</a>
656
+
657
+ <div align="center">
658
+
659
+ [Back to index ↑][epce-rules]
660
+
661
+ </div>
662
+
663
+ ---
664
+
665
+ </details>
666
+
598
667
  <h3 id="no-inline-event-handlers">
599
668
  <code>no-inline-event-handlers</code>
600
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
601
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
602
669
  <a title="Only works in 'html/html' config block" href="#usage">
603
670
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
604
671
  <a title="Accepts array w/ options" href="#usage">
@@ -673,8 +740,6 @@ If your HTML is a <a target="_blank" href="https://developer.chrome.com/docs/ext
673
740
 
674
741
  <h3 id="no-inline-scripts">
675
742
  <code>no-inline-scripts</code>
676
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
677
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
678
743
  <a title="Only works in 'html/html' config block" href="#usage">
679
744
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
680
745
  <a title="Accepts array w/ options" href="#usage">
@@ -742,76 +807,6 @@ If your HTML is a <a target="_blank" href="https://developer.chrome.com/docs/ext
742
807
 
743
808
  </details>
744
809
 
745
- <h3 id="no-host-page-selectors">
746
- <code>no-host-page-selectors</code>
747
- <a title="Only works in 'css/css' config block" href="#usage">
748
- <img height=18 alt="[CSS only]" src="https://img.shields.io/badge/CSS_only-dcd6f7?style=flat-square" align="middle"></a>
749
- <a title="Accepts array w/ options" href="#usage">
750
- <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
751
- <a title="Copy rule URL" target="_blank"
752
- href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-host-page-selectors">
753
- <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
754
- </h3>
755
-
756
- <details>
757
-
758
- <summary>Disallows host-page selectors (<code>html</code>, <code>body</code>, <code>:root</code>, <code>*</code>, bare elem types) in content-script CSS.</summary>
759
-
760
- <h4 id="no-host-page-selectors-options">Options</h4>
761
-
762
- - `allowedSelectors` (default `[]`) – selectors to explicitly allow (e.g. `['body.dark']`), as a single string or array <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
763
- - `disallowUniversal` (default `true`) – flag `*` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
764
- - `disallowStructural` (default `true`) – flag `html`, `body`, `:root` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
765
- - `disallowBareElements` (default `true`) – flag elem types (e.g. `div`, `a`) that lack `class`/`id`/`attribute` <a title="Introduced in v2.7.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0"><img height=14 alt="[Since v2.7.0]" src="https://img.shields.io/badge/since-v2.7.0-8a9199?style=flat-square" align="middle"></a>
766
-
767
- #### <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
768
-
769
- ```diff
770
- ! * { box-sizing: border-box }
771
-
772
- ! body { margin: 0 }
773
- ! html { font-family: sans-serif }
774
- ! button:hover { opacity: 0.8 }
775
- ```
776
-
777
- #### <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
778
-
779
- ```diff
780
- - * { box-sizing: border-box }
781
- + :host { box-sizing: border-box }
782
-
783
- - body { margin: 0 }
784
- + :host { margin: 0 }
785
- - html { font-family: sans-serif }
786
- + :host { font-family: sans-serif }
787
- - button:hover { opacity: 0.8 }
788
- + .myext-button:hover { opacity: 0.8 }
789
- ```
790
-
791
- #### When not to use it
792
-
793
- If you intentionally style the host page. For individual selectors you still need, prefer `allowedSelectors` over disabling the rule.
794
-
795
- #### Introduced in
796
-
797
- <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0">v2.7.0</a> (October 17, 2026)
798
-
799
- #### See also
800
-
801
- - [`no-content-style-leak`](#no-content-style-leak)
802
- <a title="Only included in strict config" href="#usage"><img height=14 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
803
- - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:host">MDN — `:host`</a>
804
-
805
- <div align="center">
806
-
807
- [Back to index ↑][epce-rules]
808
-
809
- </div>
810
-
811
- ---
812
-
813
- </details>
814
-
815
810
  <h3 id="no-insecure-fetch">
816
811
  <code>no-insecure-fetch</code>
817
812
  <a title="Included in recommended config" href="#usage">
@@ -1481,8 +1476,6 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
1481
1476
 
1482
1477
  <h3 id="no-remote-objects">
1483
1478
  <code>no-remote-objects</code>
1484
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
1485
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
1486
1479
  <a title="Only works in 'html/html' config block" href="#usage">
1487
1480
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
1488
1481
  <a title="Accepts array w/ options" href="#usage">
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.8.1",
3
+ "version": "2.9.1",
4
4
  "description": "ESLint rules for Chrome extensions",
5
5
  "keywords": [
6
6
  "eslint",
@@ -72,25 +72,29 @@
72
72
  "@eslint/markdown": "^7.5.1",
73
73
  "@html-eslint/eslint-plugin": "^0.67.0",
74
74
  "@stylistic/eslint-plugin": "^5.10.0",
75
- "eslint": "^10.13.0",
76
- "eslint-plugin-constructors": "^1.5.1",
75
+ "@typescript-eslint/parser": "^8.72.1",
76
+ "eslint": "^10.14.0",
77
+ "eslint-plugin-constructors": "^1.7.0",
77
78
  "eslint-plugin-eslint-plugin": "^7.6.2",
78
79
  "eslint-plugin-import-x": "^4.17.1",
79
80
  "eslint-plugin-n": "^18.5.0",
80
81
  "eslint-plugin-no-secrets": "^2.3.3",
81
82
  "eslint-plugin-no-unsanitized": "^4.1.5",
82
- "eslint-plugin-package-json": "^1.11.0",
83
+ "eslint-plugin-package-json": "^1.12.0",
83
84
  "eslint-plugin-promise": "^7.3.0",
84
- "eslint-plugin-regexp": "^3.3.1",
85
- "eslint-plugin-security": "^4.2.0",
85
+ "eslint-plugin-regexp": "^3.3.2",
86
+ "eslint-plugin-security": "^4.2.1",
86
87
  "eslint-plugin-sonarjs": "^4.2.2",
87
- "eslint-plugin-woodpecker": "^1.1.1",
88
- "oxlint": "^1.89.0"
88
+ "eslint-plugin-woodpecker": "^1.1.2",
89
+ "oxlint": "^1.90.0",
90
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
91
+ "typescript-latest": "npm:typescript@^7.0.2"
89
92
  },
90
93
  "peerDependencies": {
91
- "@eslint/css": ">=0.5.0",
94
+ "@eslint/css": ">=0.1.0",
92
95
  "@eslint/json": ">=0.1.0",
93
- "@html-eslint/eslint-plugin": ">=0.42.0",
96
+ "@html-eslint/eslint-plugin": ">=0.41.0",
97
+ "@typescript-eslint/parser": ">=8.0.0",
94
98
  "eslint": ">=9.6.0"
95
99
  },
96
100
  "engines": {
@@ -3,6 +3,11 @@
3
3
  import extension from '../utils/extension.js'
4
4
  import regex from '../data/regex.js'
5
5
 
6
+ const defaultOptions = {
7
+ spaces: 2, checkBraces: true, checkBrackets: true, checkColons: true, checkCombinators: true, checkCommas: true,
8
+ checkParentheses: true, checkSemicolons: true
9
+ }
10
+
6
11
  const re = { leadingWhitespace: /^[ \t]+/ },
7
12
  combinators = new Set(['>', '+', '~'])
8
13
 
@@ -37,10 +42,7 @@ export default {
37
42
  },
38
43
  additionalProperties: false
39
44
  }],
40
- defaultOptions: [{
41
- spaces: 2, checkBraces: true, checkBrackets: true, checkColons: true,
42
- checkCombinators: true, checkCommas: true, checkParentheses: true, checkSemicolons: true
43
- }],
45
+ defaultOptions: [defaultOptions],
44
46
  messages: {
45
47
  wrongIndent: 'Expected {{expected}} spaces of indentation (found {{found}}).',
46
48
  tabFound: 'Expected spaces for indentation, found tabs.',
@@ -63,16 +65,9 @@ export default {
63
65
 
64
66
  create(context) {
65
67
  if (!regex.filename.css.test(extension.file.getName(context))) return {}
66
- const opts = context.options[0] || {},
67
- spaces = opts.spaces ?? 2,
68
- checkBraces = opts.checkBraces ?? true,
69
- checkBrackets = opts.checkBrackets ?? true,
70
- checkColons = opts.checkColons ?? true,
71
- checkCombinators = opts.checkCombinators ?? true,
72
- checkCommas = opts.checkCommas ?? true,
73
- checkParentheses = opts.checkParentheses ?? true,
74
- checkSemicolons = opts.checkSemicolons ?? true,
75
- sourceCode = context.sourceCode,
68
+ const { spaces, checkBraces, checkBrackets, checkColons, checkCombinators, checkCommas, checkParentheses,
69
+ checkSemicolons } = { ...defaultOptions, ...context.options[0] }
70
+ const sourceCode = context.sourceCode,
76
71
  text = sourceCode.text,
77
72
  lines = text.split('\n'),
78
73
  lineOffsets = [0]
@@ -3,6 +3,8 @@ import extension from '../utils/extension.js'
3
3
  import manifest from '../data/manifest.js'
4
4
  import regex from '../data/regex.js'
5
5
 
6
+ const defaultOptions = { order: [] }
7
+
6
8
  function keyOf(member) {
7
9
  const keyNode = member.name || member.key
8
10
  return keyNode?.value ?? keyNode?.name
@@ -37,20 +39,19 @@ export default {
37
39
  },
38
40
  additionalProperties: false
39
41
  }],
40
- defaultOptions: [{ order: [] }],
42
+ defaultOptions: [defaultOptions],
41
43
  messages: { wrongOrder: '`{{first}}` should appear before `{{second}}`.' }
42
44
  },
43
45
 
44
46
  create(context) {
45
47
  if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
46
- const custom = context.options[0]?.order ?? [],
47
- master = custom.length ? manifest.keys.order.filter(key => !custom.includes(key))
48
- : manifest.keys.order
48
+ const { order } = { ...defaultOptions, ...context.options[0] },
49
+ master = order.length ? manifest.keys.order.filter(key => !order.includes(key)) : manifest.keys.order
49
50
  return {
50
51
  'Document:exit'(document) {
51
52
  const root = document.body ; if (root?.type != 'Object') return
52
53
  const members = root.members || root.properties || [],
53
- violation = findViolation(members, master) ?? (custom.length && findViolation(members, custom))
54
+ violation = findViolation(members, master) ?? (order.length && findViolation(members, order))
54
55
  if (!violation) return
55
56
  const [first, second] = violation
56
57
  context.report({
@@ -1,7 +1,8 @@
1
1
  import extension from '../utils/extension.js'
2
2
  import regex from '../data/regex.js'
3
3
 
4
- const re = { leadingWhitespace: /^[ \t]+/ },
4
+ const defaultOptions = { spaces: 2, checkColons: true, checkCommas: true, checkBraces: true, checkBrackets: true },
5
+ re = { leadingWhitespace: /^[ \t]+/ },
5
6
  brackets = { open: new Set(['LBrace', 'LBracket']), close: new Set(['RBrace', 'RBracket']) }
6
7
 
7
8
  export default {
@@ -39,7 +40,7 @@ export default {
39
40
  },
40
41
  additionalProperties: false
41
42
  }],
42
- defaultOptions: [{ spaces: 2, checkColons: true, checkCommas: true, checkBraces: true, checkBrackets: true }],
43
+ defaultOptions: [defaultOptions],
43
44
  messages: {
44
45
  wrongIndent: 'Expected {{expected}} spaces of indentation (found {{found}}).',
45
46
  tabFound: 'Expected spaces for indentation, found tabs.',
@@ -57,12 +58,8 @@ export default {
57
58
 
58
59
  create(context) {
59
60
  if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
60
- const opts = context.options[0] || {},
61
- spaces = opts.spaces ?? 2,
62
- checkColons = opts.checkColons ?? true,
63
- checkCommas = opts.checkCommas ?? true,
64
- checkBraces = opts.checkBraces ?? true,
65
- checkBrackets = opts.checkBrackets ?? true
61
+ const { spaces, checkColons, checkCommas, checkBraces, checkBrackets }
62
+ = { ...defaultOptions, ...context.options[0] }
66
63
  return {
67
64
  'Document'(document) {
68
65
  const sourceCode = context.sourceCode,
@@ -1,11 +1,12 @@
1
1
  import extension from '../utils/extension.js'
2
+ import nodeUtils from '../utils/node.js'
2
3
 
3
4
  const re = { appendMethod: /^append(?:Child)?$/ }
4
5
 
5
6
  function isCreateStyle(node) {
6
7
  return node?.type == 'CallExpression'
7
8
  && node.callee?.property?.name == 'createElement'
8
- && node.arguments[0]?.value == 'style'
9
+ && nodeUtils.getStringLiteral(node.arguments[0]) == 'style'
9
10
  }
10
11
  function isObjectAssign(node) {
11
12
  return node?.type == 'CallExpression'
@@ -52,8 +53,8 @@ export default {
52
53
  return {
53
54
  VariableDeclarator(node) {
54
55
  if (node.id.type != 'Identifier') return
55
- if (isCreateStyle(node.init)) styleElems.add(node.id.name)
56
- if (node.init?.type == 'Identifier' && isDocumentRef(node.init))
56
+ if (isCreateStyle(nodeUtils.unwrapTS(node.init))) styleElems.add(node.id.name)
57
+ if (nodeUtils.unwrapTS(node.init)?.type == 'Identifier' && isDocumentRef(node.init))
57
58
  docAliases.add(node.id.name)
58
59
  },
59
60
  CallExpression(node) {
@@ -1,6 +1,8 @@
1
1
  import apiUtils from '../utils/api.js'
2
2
  import nodeUtils from '../utils/node.js'
3
3
 
4
+ const defaultOptions = { target: 'mv3', allowedAPIs: [] }
5
+
4
6
  export default {
5
7
  meta: {
6
8
  type: 'problem',
@@ -9,7 +11,7 @@ export default {
9
11
  category: 'Possible Errors',
10
12
  recommended: true
11
13
  },
12
- defaultOptions: [{ target: 'mv3', allowedAPIs: [] }],
14
+ defaultOptions: [defaultOptions],
13
15
  schema: [{
14
16
  type: 'object',
15
17
  properties: {
@@ -37,13 +39,11 @@ export default {
37
39
  },
38
40
 
39
41
  create(context) {
40
- const opts = context.options[0] || {},
41
- target = opts.target || 'mv3',
42
- allowedAPIs = Array.isArray(opts.allowedAPIs) ? opts.allowedAPIs
43
- : opts.allowedAPIs ? [opts.allowedAPIs] : []
42
+ const { target, allowedAPIs } = { ...defaultOptions, ...context.options[0] },
43
+ allowedAPIsArray = Array.isArray(allowedAPIs) ? allowedAPIs : [allowedAPIs]
44
44
  if (target != 'mv3') return {}
45
45
  function isAllowed(api) {
46
- for (const allowed of allowedAPIs)
46
+ for (const allowed of allowedAPIsArray)
47
47
  if (api == allowed || api.startsWith(`${allowed}.`)) return true
48
48
  return false
49
49
  }
@@ -1,5 +1,8 @@
1
1
  import regex from '../data/regex.js'
2
2
 
3
+ const defaultOptions = { flagCommonNames: true },
4
+ re = { keyframesHeader: /^@(?:-webkit-)?keyframes\s+([\w-]+)/i, quotes: /^["']|["']$/g }
5
+
3
6
  const commonNames = new Set([
4
7
  'blink', 'bounce', 'bounce-in', 'bounce-out', 'fade', 'fade-in', 'fade-in-down', 'fade-in-left', 'fade-in-right',
5
8
  'fade-in-up', 'fade-out', 'fade-out-down', 'fade-out-left', 'fade-out-right', 'fade-out-up', 'flip', 'flip-in',
@@ -8,10 +11,6 @@ const commonNames = new Set([
8
11
  'slide-out-down', 'slide-out-left', 'slide-out-right', 'slide-out-up', 'spin', 'spinner', 'swing', 'wobble', 'zoom',
9
12
  'zoom-in', 'zoom-out'
10
13
  ])
11
- const re = {
12
- keyframesHeader: /^@(?:-webkit-)?keyframes\s+([\w-]+)/i,
13
- quotes: /^["']|["']$/g
14
- }
15
14
 
16
15
  export default {
17
16
  meta: {
@@ -21,7 +20,7 @@ export default {
21
20
  category: 'Possible Errors',
22
21
  css: true
23
22
  },
24
- defaultOptions: [{}],
23
+ defaultOptions: [defaultOptions],
25
24
  schema: [{
26
25
  type: 'object',
27
26
  properties: {
@@ -46,9 +45,7 @@ export default {
46
45
  },
47
46
 
48
47
  create(context) {
49
- const opts = context.options[0] || {},
50
- requiredPrefix = opts.requiredPrefix,
51
- flagCommonNames = opts.flagCommonNames !== false
48
+ const { requiredPrefix, flagCommonNames } = { ...defaultOptions, ...context.options[0] }
52
49
  function getKeyframeName(node) {
53
50
  const pv = node.prelude?.value
54
51
  if (typeof pv == 'string' && pv.trim())
@@ -1,6 +1,9 @@
1
1
  import array from '../utils/array.js'
2
2
  import node from '../utils/node.js'
3
3
 
4
+ const defaultOptions = {
5
+ allowedSelectors: [], disallowUniversal: true, disallowStructural: true, disallowBareElements: true }
6
+
4
7
  const re = { keyframeSelector: /^(?:from|to|[\d.]+%)$/i }
5
8
 
6
9
  export default {
@@ -11,12 +14,7 @@ export default {
11
14
  category: 'Possible Errors',
12
15
  css: true
13
16
  },
14
- defaultOptions: [{
15
- allowedSelectors: [],
16
- disallowUniversal: true,
17
- disallowStructural: true,
18
- disallowBareElements: true
19
- }],
17
+ defaultOptions: [defaultOptions],
20
18
  schema: [{
21
19
  type: 'object',
22
20
  properties: {
@@ -50,15 +48,13 @@ export default {
50
48
  },
51
49
 
52
50
  create(context) {
53
- const opts = context.options[0] || {},
54
- allowedSelectors = new Set(array.to(opts.allowedSelectors)),
55
- checkUniversal = opts.disallowUniversal !== false,
56
- checkStructural = opts.disallowStructural !== false,
57
- checkBareElements = opts.disallowBareElements !== false
51
+ const { allowedSelectors, disallowUniversal, disallowStructural, disallowBareElements }
52
+ = { ...defaultOptions, ...context.options[0] }
53
+ const allowedSelectorSet = new Set(array.to(allowedSelectors))
58
54
  function checkSelector(selector) {
59
55
  if (!selector) return
60
56
  const text = getSelectorText(selector).trim()
61
- if (re.keyframeSelector.test(text) || allowedSelectors.has(text)) return
57
+ if (re.keyframeSelector.test(text) || allowedSelectorSet.has(text)) return
62
58
  const simples = []
63
59
  for (const child of selector.children ?? []) {
64
60
  if (node.plainType(child) == 'Combinator') break
@@ -78,15 +74,15 @@ export default {
78
74
  else if (name) elementTypes.push(name)
79
75
  }
80
76
  }
81
- if (isUniversal && checkUniversal)
77
+ if (isUniversal && disallowUniversal)
82
78
  return context.report({ node: selector, messageId: 'universalSelector' })
83
- if (isRoot && checkStructural)
79
+ if (isRoot && disallowStructural)
84
80
  return context.report({ node: selector, messageId: 'structuralSelector', data: { selector: text }})
85
81
  for (const type of elementTypes) {
86
82
  if (type == 'html' || type == 'body') {
87
- if (checkStructural)
83
+ if (disallowStructural)
88
84
  return context.report({ node: selector, messageId: 'structuralSelector', data: { selector: text }})
89
- } else if (checkBareElements && !hasQualifier) {
85
+ } else if (disallowBareElements && !hasQualifier) {
90
86
  return context.report({ node: selector, messageId: 'elementSelector', data: { selector: text }})
91
87
  }
92
88
  }
@@ -3,6 +3,8 @@
3
3
  import array from '../utils/array.js'
4
4
  import html from '../utils/html.js'
5
5
 
6
+ const defaultOptions = { allowedAttributes: [] }
7
+
6
8
  const re = {
7
9
  attrNameEnd: /[\s=>]/,
8
10
  block: {
@@ -55,7 +57,7 @@ export default {
55
57
  category: 'Possible Errors',
56
58
  html: true
57
59
  },
58
- defaultOptions: [{ allowedAttributes: [] }],
60
+ defaultOptions: [defaultOptions],
59
61
  schema: [{
60
62
  type: 'object',
61
63
  properties: {
@@ -76,9 +78,9 @@ export default {
76
78
  },
77
79
 
78
80
  create(context) {
79
- const opts = context.options[0] || {},
80
- allowedAttributes = new Set(array.to(opts.allowedAttributes ?? []).map(a => a.toLowerCase())),
81
- text = context.sourceCode.text
81
+ const { allowedAttributes } = { ...defaultOptions, ...context.options[0] },
82
+ allowedAttributesSet = new Set(array.to(allowedAttributes).map(attr => attr.toLowerCase())),
83
+ text = context.sourceCode.text
82
84
  let scanned = false
83
85
  function maskContent() {
84
86
  return text
@@ -105,7 +107,7 @@ export default {
105
107
  for (const attr of parseAttrs(attrPart)) {
106
108
  if (!re.inlineHandler.test(attr.name)) continue
107
109
  const attrLower = attr.name.toLowerCase()
108
- if (allowedAttributes.has(attrLower)) continue
110
+ if (allowedAttributesSet.has(attrLower)) continue
109
111
  context.report({ loc: html.offsetToLoc(text, tagEnd + attr.offset),
110
112
  messageId: 'inlineHandler', data: { attr: attrLower }})
111
113
  }
@@ -1,6 +1,8 @@
1
1
  import array from '../utils/array.js'
2
2
  import html from '../utils/html.js'
3
3
 
4
+ const defaultOptions = { allowedSrcPrefixes: ['chrome-extension://'] }
5
+
4
6
  const re = {
5
7
  nonExecutable: /^(?:application\/(?:ld\+)?json|importmap|speculationrules|text\/(?:x-)?template)$/i,
6
8
  script: /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi
@@ -14,7 +16,7 @@ export default {
14
16
  category: 'Possible Errors',
15
17
  html: true
16
18
  },
17
- defaultOptions: [{ allowedSrcPrefixes: ['chrome-extension://'] }],
19
+ defaultOptions: [defaultOptions],
18
20
  schema: [{
19
21
  type: 'object',
20
22
  properties: {
@@ -29,15 +31,17 @@ export default {
29
31
  additionalProperties: false
30
32
  }],
31
33
  messages: {
32
- inlineBody: 'Inline `<script>` bodies are blocked by MV3\'s `script-src \'self\'`. Move the code to an external file.',
33
- remoteSrc: 'Remote `<script src="{{src}}">` is blocked by MV3\'s `script-src \'self\'`. Bundle the script or use `chrome.runtime.getURL()`.'
34
+ inlineBody: 'Inline `<script>` bodies are blocked by MV3\'s `script-src \'self\'`. '
35
+ + 'Move the code to an external file.',
36
+ remoteSrc: 'Remote `<script src="{{src}}">` is blocked by MV3\'s `script-src \'self\'`. '
37
+ + 'Bundle the script or use `chrome.runtime.getURL()`.'
34
38
  }
35
39
  },
36
40
 
37
41
  create(context) {
38
- const opts = context.options[0] || {},
39
- allowedPrefixes = array.to(opts.allowedSrcPrefixes ?? ['chrome-extension://']),
40
- text = context.sourceCode.text
42
+ const { allowedSrcPrefixes } = { ...defaultOptions, ...context.options[0] },
43
+ allowedPrefixes = array.to(allowedSrcPrefixes),
44
+ text = context.sourceCode.text
41
45
  let scanned = false
42
46
  function scan() {
43
47
  if (scanned) return