eslint-plugin-chrome-extensions 2.8.0 → 2.9.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.
Files changed (36) hide show
  1. package/README.md +86 -88
  2. package/package.json +11 -7
  3. package/src/rules/css-spacing.js +9 -14
  4. package/src/rules/manifest-key-order.js +6 -5
  5. package/src/rules/manifest-spacing.js +5 -8
  6. package/src/rules/no-content-style-leak.js +4 -3
  7. package/src/rules/no-deprecated-apis.js +6 -6
  8. package/src/rules/no-global-keyframes.js +5 -8
  9. package/src/rules/no-host-page-selectors.js +12 -16
  10. package/src/rules/no-inline-event-handlers.js +7 -5
  11. package/src/rules/no-inline-scripts.js +10 -6
  12. package/src/rules/no-insecure-fetch.js +10 -6
  13. package/src/rules/no-insecure-messaging.js +8 -10
  14. package/src/rules/no-invalid-locales.js +7 -6
  15. package/src/rules/no-invalid-permissions.js +5 -5
  16. package/src/rules/no-missing-default-locale.js +5 -4
  17. package/src/rules/no-missing-i18n-keys.js +7 -5
  18. package/src/rules/no-missing-permissions.js +1 -1
  19. package/src/rules/no-missing-web-accessible-resources.js +10 -10
  20. package/src/rules/no-remote-code.js +30 -33
  21. package/src/rules/no-remote-objects.js +7 -6
  22. package/src/rules/no-remote-urls.js +11 -12
  23. package/src/rules/no-scrollbar-styling.js +6 -6
  24. package/src/rules/no-sensitive-permissions.js +6 -4
  25. package/src/rules/no-unrestricted-host-permissions.js +6 -7
  26. package/src/rules/no-unused-i18n-keys.js +5 -4
  27. package/src/rules/require-short-name-when-useful.js +3 -8
  28. package/src/rules/valid-filenames.js +9 -11
  29. package/src/rules/valid-license-file.js +7 -6
  30. package/src/rules/valid-manifest-icons.js +14 -20
  31. package/src/rules/valid-service-worker.js +8 -10
  32. package/src/rules/valid-url-fields.js +8 -13
  33. package/src/rules/valid-version-field.js +5 -6
  34. package/src/utils/extension.js +7 -3
  35. package/src/utils/init.js +5 -4
  36. package/src/utils/node.js +23 -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>
@@ -33,7 +33,7 @@ npm i -D eslint-plugin-chrome-extensions
33
33
  Usage
34
34
  </h2>
35
35
 
36
- ### ESLint
36
+ ### <img height=15 width=auto src="https://www.google.com/s2/favicons?domain=eslint.org&sz=64"> ESLint
37
37
 
38
38
  Add bundled configs to your `eslint.config.*js` for each file type you need:
39
39
 
@@ -44,8 +44,7 @@ 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 },
47
+ files: ['**/extension/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}'], ...chromeExtensions.configs.recommended,
49
48
  rules: {
50
49
  ...chromeExtensions.configs.recommended.rules,
51
50
  'chrome-extensions/no-deprecated-apis': 'off', // selectively disable rule
@@ -62,32 +61,37 @@ export default [
62
61
  ]
63
62
  ```
64
63
 
65
- ### Oxlint
64
+ **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
65
+
66
+ ---
66
67
 
67
- Add a JS bundle to your `oxlint.config.ts` (non-JS parsing unsupported by Oxlint):
68
+ ### <img height=15 width=auto src="https://www.google.com/s2/favicons?domain=oxc.rs&sz=64"> Oxlint
69
+
70
+ Add a JS bundle to your `oxlint.config.ts` (non-JS parsing is unsupported by Oxlint):
68
71
 
69
72
  ```ts
70
73
  ...
71
74
  import chromeExtensions from 'eslint-plugin-chrome-extensions'
72
75
 
73
76
  export default defineConfig({
74
- // main config
75
77
  overrides: [
76
- // other overrides
78
+ // other configs
77
79
  {
78
- files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
80
+ files: ['**/extension/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}'],
79
81
  jsPlugins: ['eslint-plugin-chrome-extensions'],
80
82
  rules: {
81
83
  ...chromeExtensions.configs.recommended.rules,
82
- 'chrome-extensions/no-deprecated-apis': 'off', // selectively disable rule
83
- 'chrome-extensions/no-insecure-messaging': ['error', { disallowWildcardTarget: false }] // selectively tweak rule
84
+ 'chrome-extensions/no-deprecated-apis': 'allow', // selectively disable rule
85
+ 'chrome-extensions/no-insecure-messaging': ['deny', { disallowWildcardTarget: false }] // selectively tweak rule
84
86
  }
85
87
  }
86
88
  ]
87
89
  })
88
90
  ```
89
91
 
90
- **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
92
+ **Note:** Rule config is either a severity string (`'allow'`, `'warn'`, `'deny'`) or an array `[severity, options]` when the rule accepts options.
93
+
94
+ **Note:** Oxlint is typically **6-20× faster than ESLint** w/ this plugin (due to its Rust core and parallel file processing).
91
95
 
92
96
  ---
93
97
 
@@ -590,10 +594,78 @@ If your CSS is scoped inside a Shadow DOM root.
590
594
 
591
595
  </details>
592
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
+
593
667
  <h3 id="no-inline-event-handlers">
594
668
  <code>no-inline-event-handlers</code>
595
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
596
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
597
669
  <a title="Only works in 'html/html' config block" href="#usage">
598
670
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
599
671
  <a title="Accepts array w/ options" href="#usage">
@@ -668,8 +740,6 @@ If your HTML is a <a target="_blank" href="https://developer.chrome.com/docs/ext
668
740
 
669
741
  <h3 id="no-inline-scripts">
670
742
  <code>no-inline-scripts</code>
671
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
672
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
673
743
  <a title="Only works in 'html/html' config block" href="#usage">
674
744
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
675
745
  <a title="Accepts array w/ options" href="#usage">
@@ -737,76 +807,6 @@ If your HTML is a <a target="_blank" href="https://developer.chrome.com/docs/ext
737
807
 
738
808
  </details>
739
809
 
740
- <h3 id="no-host-page-selectors">
741
- <code>no-host-page-selectors</code>
742
- <a title="Only works in 'css/css' config block" href="#usage">
743
- <img height=18 alt="[CSS only]" src="https://img.shields.io/badge/CSS_only-dcd6f7?style=flat-square" align="middle"></a>
744
- <a title="Accepts array w/ options" href="#usage">
745
- <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
746
- <a title="Copy rule URL" target="_blank"
747
- href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-host-page-selectors">
748
- <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
749
- </h3>
750
-
751
- <details>
752
-
753
- <summary>Disallows host-page selectors (<code>html</code>, <code>body</code>, <code>:root</code>, <code>*</code>, bare elem types) in content-script CSS.</summary>
754
-
755
- <h4 id="no-host-page-selectors-options">Options</h4>
756
-
757
- - `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>
758
- - `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>
759
- - `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>
760
- - `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>
761
-
762
- #### <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
763
-
764
- ```diff
765
- ! * { box-sizing: border-box }
766
-
767
- ! body { margin: 0 }
768
- ! html { font-family: sans-serif }
769
- ! button:hover { opacity: 0.8 }
770
- ```
771
-
772
- #### <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
773
-
774
- ```diff
775
- - * { box-sizing: border-box }
776
- + :host { box-sizing: border-box }
777
-
778
- - body { margin: 0 }
779
- + :host { margin: 0 }
780
- - html { font-family: sans-serif }
781
- + :host { font-family: sans-serif }
782
- - button:hover { opacity: 0.8 }
783
- + .myext-button:hover { opacity: 0.8 }
784
- ```
785
-
786
- #### When not to use it
787
-
788
- If you intentionally style the host page. For individual selectors you still need, prefer `allowedSelectors` over disabling the rule.
789
-
790
- #### Introduced in
791
-
792
- <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.7.0">v2.7.0</a> (October 17, 2026)
793
-
794
- #### See also
795
-
796
- - [`no-content-style-leak`](#no-content-style-leak)
797
- <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>
798
- - <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>
799
-
800
- <div align="center">
801
-
802
- [Back to index ↑][epce-rules]
803
-
804
- </div>
805
-
806
- ---
807
-
808
- </details>
809
-
810
810
  <h3 id="no-insecure-fetch">
811
811
  <code>no-insecure-fetch</code>
812
812
  <a title="Included in recommended config" href="#usage">
@@ -1476,8 +1476,6 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
1476
1476
 
1477
1477
  <h3 id="no-remote-objects">
1478
1478
  <code>no-remote-objects</code>
1479
- <a title="Introduced in latest minor release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.8.0">
1480
- <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
1481
1479
  <a title="Only works in 'html/html' config block" href="#usage">
1482
1480
  <img height=18 alt="[HTML only]" src="https://img.shields.io/badge/HTML_only-a8d8ea?style=flat-square" align="middle"></a>
1483
1481
  <a title="Accepts array w/ options" href="#usage">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-chrome-extensions",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "ESLint rules for Chrome extensions",
5
5
  "keywords": [
6
6
  "eslint",
@@ -8,6 +8,8 @@
8
8
  "eslintplugin",
9
9
  "chrome-extensions",
10
10
  "chromium",
11
+ "oxlint",
12
+ "oxlint-plugin",
11
13
  "security"
12
14
  ],
13
15
  "homepage": "https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme",
@@ -70,25 +72,27 @@
70
72
  "@eslint/markdown": "^7.5.1",
71
73
  "@html-eslint/eslint-plugin": "^0.67.0",
72
74
  "@stylistic/eslint-plugin": "^5.10.0",
75
+ "@typescript-eslint/parser": "^8.72.1",
73
76
  "eslint": "^10.13.0",
74
- "eslint-plugin-constructors": "^1.5.0",
77
+ "eslint-plugin-constructors": "^1.5.1",
75
78
  "eslint-plugin-eslint-plugin": "^7.6.2",
76
79
  "eslint-plugin-import-x": "^4.17.1",
77
- "eslint-plugin-n": "^18.4.1",
80
+ "eslint-plugin-n": "^18.5.0",
78
81
  "eslint-plugin-no-secrets": "^2.3.3",
79
82
  "eslint-plugin-no-unsanitized": "^4.1.5",
80
- "eslint-plugin-package-json": "^1.10.1",
83
+ "eslint-plugin-package-json": "^1.11.0",
81
84
  "eslint-plugin-promise": "^7.3.0",
82
85
  "eslint-plugin-regexp": "^3.3.1",
83
86
  "eslint-plugin-security": "^4.2.0",
84
87
  "eslint-plugin-sonarjs": "^4.2.2",
85
- "eslint-plugin-woodpecker": "^1.1.0",
88
+ "eslint-plugin-woodpecker": "^1.1.1",
86
89
  "oxlint": "^1.89.0"
87
90
  },
88
91
  "peerDependencies": {
89
- "@eslint/css": ">=0.5.0",
92
+ "@eslint/css": ">=0.1.0",
90
93
  "@eslint/json": ">=0.1.0",
91
- "@html-eslint/eslint-plugin": ">=0.42.0",
94
+ "@html-eslint/eslint-plugin": ">=0.41.0",
95
+ "@typescript-eslint/parser": ">=8.0.0",
92
96
  "eslint": ">=9.6.0"
93
97
  },
94
98
  "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
  }