eslint-plugin-greasemonkey 1.4.0 → 1.5.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.
package/README.md CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  # <img height=24 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon32.png"> eslint-plugin-greasemonkey
4
4
 
5
- [![](https://img.shields.io/npm/v/eslint-plugin-greasemonkey?logo=icinga&style=for-the-badge&label=Latest%20Build&color=32fcee)][epg-npm]
6
- [![](https://img.shields.io/badge/License-MIT-f1f20a.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge)](#license)
7
- [![](https://img.shields.io/badge/Requires-ESLint_%3E%3D9.0.0-4B32C3?style=for-the-badge&logo=eslint)](https://eslint.org/)
5
+ <a target="_blank" href="https://www.npmjs.com/package/eslint-plugin-greasemonkey">
6
+ <img alt="[Latest Build]" src="https://img.shields.io/npm/v/eslint-plugin-greasemonkey?logo=icinga&style=for-the-badge&label=Latest%20Build&color=32fcee"></a>
7
+ <a href="#license">
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
+ <a target="_blank" href="https://eslint.org/">
10
+ <img alt="[Requires ESLint >=9.0.0]"src="https://img.shields.io/badge/Requires-ESLint_%3E%3D9.0.0-4B32C3?style=for-the-badge&logo=eslint" ></a>
8
11
 
9
12
  ESLint rules for Greasemonkey userscripts.
10
13
 
@@ -35,7 +38,7 @@ export default [
35
38
  {
36
39
  files: ['**/*.user.js'], plugins: { greasemonkey }
37
40
  rules: {
38
- ...greasemonkey.configs.recommended.rules, // or .strict.rules to enable all
41
+ ...greasemonkey.configs.recommended.rules,
39
42
  'greasemonkey/no-unused-resources': 'off', // selectively disable rule
40
43
  'greasemonkey/meta-spacing': ['error', 2] // selectively tweak rule
41
44
  }
@@ -43,6 +46,8 @@ export default [
43
46
  ]
44
47
  ```
45
48
 
49
+ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`
50
+
46
51
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
47
52
 
48
53
  ---
@@ -51,6 +56,7 @@ export default [
51
56
 
52
57
  | ID | Description | Auto-fixable | Recommended |
53
58
  | --- | --- | :-: | :-: |
59
+ | [`meta-order`](#meta-order) | Enforces a preferred order for metadata keys | <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"> | <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"> |
54
60
  | [`meta-spacing`](#meta-spacing) | Aligns metadata values to the column after the longest key | <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"> | <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"> |
55
61
  | [`no-duplicate-keys`](#no-duplicate-keys) | Disallows duplicate metadata keys | <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"> | <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"> |
56
62
  | [`no-invalid-headers`](#no-invalid-headers) | Disallows invalid or unsupported metadata headers | <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"> | <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"> |
@@ -64,6 +70,7 @@ export default [
64
70
  | [`require-matching-localized-fields`](#require-matching-localized-fields) | Requires matching localized `@name:xx` / `@description:xx` pairs | <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"> | <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"> |
65
71
  | [`require-required-fields`](#require-required-fields) | Requires essential metadata fields | <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"> | <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"> |
66
72
  | [`require-space-before-meta-key`](#require-space-before-meta-key) | Requires exactly one space between `//` and metadata keys | <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"> | <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"> |
73
+ | [`require-sri-hashing`](#require-sri-hashing) | Requires SRI hashes for external `@require` URLs | <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"> | <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"> |
67
74
  | [`require-update-download-pair`](#require-update-download-pair) | Requires `@updateURL` and `@downloadURL` to be used together | <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"> | <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"> |
68
75
  | [`require-user-js-filename`](#require-user-js-filename) | Requires userscript filenames end in `.user.js` | <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"> | <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"> |
69
76
  | [`valid-field-values`](#valid-field-values) | Validates `@version` and URL fields | <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"> | <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"> |
@@ -71,6 +78,95 @@ export default [
71
78
 
72
79
  ---
73
80
 
81
+ <h3 id="meta-order">
82
+ <code>meta-order</code>
83
+ <a title="Only included in strict config" href="#usage">
84
+ <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
85
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
86
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
87
+ <a title="Doesn't catch bugs" href="#meta-order">
88
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
89
+ <a title="Accepts array w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
90
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
91
+ <a title="Copy rule URL" target="_blank"
92
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23meta-order">
93
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
94
+ </h3>
95
+
96
+ > Enforces a preferred order for metadata keys.
97
+
98
+ #### Options
99
+
100
+ - `string[]` (default `[]`) – metadata keys in custom order
101
+
102
+ For example:
103
+
104
+ ```js
105
+ /* eslint greasemonkey/meta-order: ['error', ['run-in', 'inject-into']] */
106
+ ```
107
+
108
+ #### <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
109
+
110
+ ```diff
111
+ // ==UserScript==
112
+ // @name My Script
113
+ ! // @version 1.0.0
114
+ // @description A script
115
+ ! // @namespace example.com
116
+ // @author John Doe
117
+ ! // @license MIT
118
+ // @icon https://example.com/icon.png
119
+ ! // @match https://example.com/*
120
+ // @grant GM_setValue
121
+ // @require https://example.com/lib.js
122
+ // @resource logo https://example.com/logo.png
123
+ ! // @connect example.com
124
+ // ==/UserScript==
125
+ ```
126
+
127
+ #### <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
128
+
129
+ ```diff
130
+ // ==UserScript==
131
+ // @name My Script
132
+ - // @version 1.0.0
133
+ // @description A script
134
+ - // @namespace example.com
135
+ // @author John Doe
136
+ - // @license MIT
137
+ + // @namespace example.com
138
+ + // @version 1.0.0
139
+ + // @license MIT
140
+ // @icon https://example.com/icon.png
141
+ - // @match https://example.com/*
142
+ + // @connect example.com
143
+ // @grant GM_setValue
144
+ - // @require https://example.com/lib.js
145
+ - // @resource logo https://example.com/logo.png
146
+ - // @connect example.com
147
+ + // @match https://example.com/*
148
+ + // @require https://example.com/lib.js
149
+ + // @resource logo https://example.com/logo.png
150
+ // ==/UserScript==
151
+ ```
152
+
153
+ #### When not to use it
154
+
155
+ If you don't care about the order of metadata fields.
156
+
157
+ #### See also
158
+
159
+ - [`meta-spacing`](#meta-spacing)
160
+ - [`no-invalid-headers`](#no-invalid-headers)
161
+
162
+ <div align="center">
163
+
164
+ [Back to index ↑][epg-rules]
165
+
166
+ </div>
167
+
168
+ ---
169
+
74
170
  <h3 id="meta-spacing">
75
171
  <code>meta-spacing</code>
76
172
  <a title="Included in recommended config" href="#usage">
@@ -204,8 +300,8 @@ If you intentionally repeat single-value keys.
204
300
 
205
301
  #### Options
206
302
 
207
- - `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptish'`) as a single string or array
208
- - `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes) as a single value or array
303
+ - `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`), as a single string or array
304
+ - `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
209
305
 
210
306
  #### <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
211
307
 
@@ -236,7 +332,7 @@ If you intentionally repeat single-value keys.
236
332
 
237
333
  #### When not to use it
238
334
 
239
- If your script relies entirely on custom headers that aren't in the built-in list and you'd rather not maintain an `allowedHeaders` list.
335
+ If your script relies heavily on custom headers and you'd rather not maintain `allowedHeaders`.
240
336
 
241
337
  #### See also
242
338
 
@@ -425,7 +521,7 @@ If you access the granted API through an alias or computed property rather than
425
521
 
426
522
  #### When not to use it
427
523
 
428
- If you pass the resource name through a variable or alias rather than using it literally.
524
+ If you pass resource names through variables or aliases rather than use them literally.
429
525
 
430
526
  #### See also
431
527
 
@@ -448,7 +544,7 @@ If you pass the resource name through a variable or alias rather than using it l
448
544
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
449
545
  </h3>
450
546
 
451
- > Prefers `@match` over `@include` (deprecated in Chrome MV3).
547
+ > Prefers `@match` over `@include` to align w/ <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/userScripts/">`chrome.userScripts`</a> syntax.
452
548
 
453
549
  #### <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
454
550
 
@@ -469,7 +565,11 @@ If you pass the resource name through a variable or alias rather than using it l
469
565
 
470
566
  #### When not to use it
471
567
 
472
- If you use `@include` in non-MV3 environments, or rely on regex syntax that `@match` cannot express.
568
+ If you rely on `@include` patterns that cannot be expressed using `@match`.
569
+
570
+ #### See also
571
+
572
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome match patterns</a>
473
573
 
474
574
  <div align="center">
475
575
 
@@ -590,7 +690,7 @@ If your target manager does not fall back to `@namespace` when a homepage key is
590
690
 
591
691
  #### Options
592
692
 
593
- - `ignoreLanguages` (default `[]`) – language codes to skip, e.g. `['fr', 'de']`
693
+ - `ignoreLanguages` (default `[]`) – language code(s) to skip (e.g. `'de'` or `['es', 'fr']`)
594
694
 
595
695
  #### <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
596
696
 
@@ -743,6 +843,67 @@ If you intentionally use a different prefix style.
743
843
 
744
844
  ---
745
845
 
846
+ <h3 id="require-sri-hashing">
847
+ <code>require-sri-hashing</code>
848
+ <a title="Only included in strict config" href="#usage">
849
+ <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
850
+ <a title="Doesn't catch bugs" href="#require-sri-hashing">
851
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
852
+ <a title="Accepts object w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
853
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
854
+ <a title="Copy rule URL" target="_blank"
855
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-sri-hashing">
856
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
857
+ </h3>
858
+
859
+ > Requires SRI hashes on `@require` and `@resource` URLs.
860
+
861
+ #### Options
862
+
863
+ - `algorithm` (default `'sha256'`) – SRI hash algorithm to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`)
864
+
865
+ #### <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
866
+
867
+ ```diff
868
+ /* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
869
+
870
+ // ==UserScript==
871
+ ! // @require https://example.com/library.js#sha256-BASE64HASH
872
+ ! // @resource logo https://example.com/logo.png#sha256-BASE64HASH
873
+ // ==/UserScript==
874
+ ```
875
+
876
+ #### <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
877
+
878
+ ```diff
879
+ /* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
880
+
881
+ // ==UserScript==
882
+ - // @require https://example.com/library.js#sha256-BASE64HASH
883
+ - // @resource logo https://example.com/logo.png#sha256-BASE64HASH
884
+ + // @require https://example.com/library.js#sha384-BASE64HASH
885
+ + // @resource logo https://example.com/logo.png#sha384-BASE64HASH
886
+ // ==/UserScript==
887
+ ```
888
+
889
+ #### When not to use it
890
+
891
+ If you prefer shorter URLs, or don't support users who enforce subresource integrity validation via manager settings.
892
+
893
+ #### See also
894
+
895
+ - [`no-invalid-headers`](#no-invalid-headers)
896
+ - [`require-required-fields`](#require-required-fields)
897
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey Subresource Integrity</a>
898
+
899
+ <div align="center">
900
+
901
+ [Back to index ↑][epg-rules]
902
+
903
+ </div>
904
+
905
+ ----
906
+
746
907
  <h3 id="require-update-download-pair">
747
908
  <code>require-update-download-pair</code>
748
909
  <a title="Only included in strict config" href="#usage">
@@ -777,7 +938,7 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
777
938
 
778
939
  #### See also
779
940
 
780
- - [`@updateURL`](https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL)
941
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL"><code>@updateURL</code></a>
781
942
  - [`require-homepage-pair`](#require-homepage-pair)
782
943
 
783
944
  <div align="center">
@@ -799,6 +960,19 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
799
960
 
800
961
  > Requires that files containing a userscript metadata block end in `.user.js`.
801
962
 
963
+ #### <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 filename
964
+
965
+ ```diff
966
+ ! /my-userscript.js
967
+ ```
968
+
969
+ #### <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 filename
970
+
971
+ ```diff
972
+ - /my-userscript.js
973
+ + /my-userscript.user.js
974
+ ```
975
+
802
976
  #### When not to use it
803
977
 
804
978
  If you intentionally name your userscripts something other than `*.user.js`.
@@ -884,8 +1058,8 @@ If you use a custom versioning scheme or URLs that aren't standard.
884
1058
 
885
1059
  #### Options
886
1060
 
887
- - `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`) as a single string or array
888
- - `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat)
1061
+ - `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
1062
+ - `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat), as a single string or array
889
1063
 
890
1064
  #### <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
891
1065
 
@@ -930,7 +1104,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
930
1104
 
931
1105
  ## Shield embed
932
1106
 
933
- <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1107
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
934
1108
  <img height=32 alt="[Linted by eslint-plugin-greasemonkey]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--greasemonkey-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
935
1109
 
936
1110
  #### HTML
@@ -950,11 +1124,12 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
950
1124
 
951
1125
  ## License
952
1126
 
953
- [<img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png">][epg-license]
1127
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
1128
+ <img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
954
1129
 
955
1130
  <details>
956
1131
 
957
- <summary>Copyright © 2026 <a href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
1132
+ <summary>Copyright © 2026 <a target="_blank" href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
958
1133
 
959
1134
  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:
960
1135
 
@@ -968,24 +1143,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
968
1143
 
969
1144
  ## Related
970
1145
 
971
- <!-- eslint-plugin-constructors -->
1146
+ <!-- eslint-plugin-constructors -->
972
1147
 
973
- ### [<img height=15 width=auto src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon16.png">][epc-readme] [eslint-plugin-constructors][epc-readme]
1148
+ ### <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img height=15 width=auto src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon16.png"> eslint-plugin-constructors</a>
974
1149
 
975
1150
  <details>
976
1151
 
977
1152
  <summary>ESLint rules for JavaScript constructor usage.</summary><br>
978
1153
 
979
- > [<img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png">][epc-readme]
1154
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png"></a>
980
1155
 
981
- > [**Install**][epc-install] / [Readme][epc-readme] / [Supported rules][epc-rules]
1156
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#installation"><b>Install</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme">Readme</a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#supported-rules">Supported rules</a>
982
1157
 
983
1158
  </details>
984
1159
 
985
- [epc-install]: https://codeberg.org/adamlui/eslint-plugin-constructors/#installation
986
- [epc-readme]: https://codeberg.org/adamlui/eslint-plugin-constructors/#readme
987
- [epc-rules]: https://codeberg.org/adamlui/eslint-plugin-constructors/#supported-rules
988
-
989
1160
  <!-- Footer -->
990
1161
 
991
1162
  <img height=10 width="100%" src="https://cdn.staticdelivr.com/gl/adamlui/ai-web-extensions/d11d2ee/assets/images/separators/gradient-aqua.png">
@@ -993,15 +1164,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
993
1164
  <div align="center">
994
1165
 
995
1166
  <picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/white/icon16.svg"><img height=14 src="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/dark-gray/icon16.svg"></picture>
996
- [**Latest releases**][epg-releases] / [Report a bug][epg-bugs] / [More packages by author][adamlui-npmx] / [Back to top ↑](#top)
1167
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases"><b>Latest releases</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/issues/new">Report a bug</a> / <a target="_blank" href="https://npmx.dev/org/adamlui">More packages by author</a> / [Back to top ↑](#top)
997
1168
 
998
1169
  </div>
999
1170
 
1000
1171
  <!-- Links -->
1001
1172
 
1002
- [adamlui-npmx]: https://npmx.dev/org/adamlui
1003
- [epg-bugs]: https://codeberg.org/adamlui/eslint-plugin-greasemonkey/issues/new
1004
- [epg-license]: https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md
1005
- [epg-npm]: https://www.npmjs.com/package/eslint-plugin-greasemonkey
1006
- [epg-releases]: https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases
1007
1173
  [epg-rules]: #supported-rules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-greasemonkey",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
5
5
  "type": "module",
6
6
  "engines": {
package/src/data/apis.js CHANGED
@@ -27,9 +27,10 @@ const tmAPIs = new Set([
27
27
 
28
28
  export const allAPIs = new Set([...gmLegacyAPIs, ...gmModernAPIs, ...scAPIs])
29
29
 
30
- export function isAPIValidFor(api, manager) {
31
- if (!allAPIs.has(api)) return false
32
- if (api.startsWith('CAT')) return manager == 'scriptcat'
33
- if (tmAPIs.has(api)) return manager == 'tampermonkey'
34
- return true
30
+ export function supportsAPI({ manager, api }) {
31
+ return !allAPIs.has(api) ? false
32
+ : api.startsWith('CAT') ? manager == 'scriptcat'
33
+ : tmAPIs.has(api) ? manager == 'tampermonkey'
34
+ : true
35
+
35
36
  }
@@ -1,3 +1,3 @@
1
1
  export default { // val = replacement
2
- rules: { 'require-meta-newline': 'require-blank-line-after-meta-block' }
2
+ rules: { 'require-meta-newline': 'require-blank-line-after-meta-block' } // 2026-9-13
3
3
  }
@@ -12,12 +12,11 @@ export const managerHeaders = new Map([
12
12
  ['run-in', ['tampermonkey']],
13
13
  ['sandbox', ['tampermonkey']],
14
14
  ['webRequest', ['tampermonkey']],
15
- ['nocompat', ['tampermonkey']],
16
- ['domain', ['scriptish']]
15
+ ['nocompat', ['tampermonkey']]
17
16
  ])
18
17
 
19
18
  export const localizedHeaders = ['name', 'description', 'antifeature']
20
- export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptish']
19
+ export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey']
21
20
  export const validHeaders = new Map()
22
21
 
23
22
  validHeaders.set('all', new Set([...universalHeaders, ...managerHeaders.keys()]))
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import aliases from './data/deprecations.js'
2
2
 
3
+ import metaOrder from './rules/meta-order.js'
3
4
  import metaSpacing from './rules/meta-spacing.js'
4
5
  import noDuplicateKeys from './rules/no-duplicate-keys.js'
5
6
  import noInvalidHeaders from './rules/no-invalid-headers.js'
@@ -13,6 +14,7 @@ import requireHomepagePair from './rules/require-homepage-pair.js'
13
14
  import requireMatchingLocalizedFields from './rules/require-matching-localized-fields.js'
14
15
  import requireRequiredFields from './rules/require-required-fields.js'
15
16
  import requireSpaceBeforeMetaKey from './rules/require-space-before-meta-key.js'
17
+ import requireSRIhashing from './rules/require-sri-hashing.js'
16
18
  import requireUpdateDownloadPair from './rules/require-update-download-pair.js'
17
19
  import requireUserJsFilename from './rules/require-user-js-filename.js'
18
20
  import validFieldValues from './rules/valid-field-values.js'
@@ -20,6 +22,7 @@ import validGrants from './rules/valid-grants.js'
20
22
 
21
23
  const plugin = {
22
24
  rules: {
25
+ 'meta-order': metaOrder,
23
26
  'meta-spacing': metaSpacing,
24
27
  'no-duplicate-keys': noDuplicateKeys,
25
28
  'no-invalid-headers': noInvalidHeaders,
@@ -33,6 +36,7 @@ const plugin = {
33
36
  'require-matching-localized-fields': requireMatchingLocalizedFields,
34
37
  'require-required-fields': requireRequiredFields,
35
38
  'require-space-before-meta-key': requireSpaceBeforeMetaKey,
39
+ 'require-sri-hashing': requireSRIhashing,
36
40
  'require-update-download-pair': requireUpdateDownloadPair,
37
41
  'require-user-js-filename': requireUserJsFilename,
38
42
  'valid-field-values': validFieldValues,
@@ -77,29 +81,22 @@ plugin.configs = {
77
81
  'greasemonkey/valid-field-values': 'error',
78
82
  'greasemonkey/valid-grants': 'error'
79
83
  }
80
- },
81
- strict: {
84
+ }
85
+ }
86
+
87
+ plugin.configs.strict = {
88
+ plugins: { greasemonkey: plugin },
89
+ rules: Object.fromEntries(Object.keys(plugin.rules).map(ruleId => [`greasemonkey/${ruleId}`, 'error']))
90
+ }
91
+
92
+ for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat'])
93
+ plugin.configs[manager] = { // eslint-disable-line security/detect-object-injection
82
94
  plugins: { greasemonkey: plugin },
83
95
  rules: {
84
- 'greasemonkey/meta-spacing': 'error',
85
- 'greasemonkey/no-duplicate-keys': 'error',
86
- 'greasemonkey/no-invalid-headers': 'error',
87
- 'greasemonkey/no-missing-grants': 'error',
88
- 'greasemonkey/no-unused-grants': 'error',
89
- 'greasemonkey/no-missing-resources': 'error',
90
- 'greasemonkey/no-unused-resources': 'error',
91
- 'greasemonkey/prefer-match': 'error',
92
- 'greasemonkey/require-blank-line-after-meta-block': 'error',
93
- 'greasemonkey/require-homepage-pair': 'error',
94
- 'greasemonkey/require-matching-localized-fields': 'error',
95
- 'greasemonkey/require-required-fields': 'error',
96
- 'greasemonkey/require-space-before-meta-key': 'error',
97
- 'greasemonkey/require-update-download-pair': 'error',
98
- 'greasemonkey/require-user-js-filename': 'error',
99
- 'greasemonkey/valid-field-values': 'error',
100
- 'greasemonkey/valid-grants': 'error'
96
+ ...plugin.configs.recommended.rules,
97
+ 'greasemonkey/no-invalid-headers': ['error', { target: manager }],
98
+ 'greasemonkey/valid-grants': ['error', { target: manager }]
101
99
  }
102
100
  }
103
- }
104
101
 
105
102
  export { plugin as default, plugin as greasemonkey }
@@ -0,0 +1,76 @@
1
+ import { extractMetadata } from '../utils.js'
2
+
3
+ const defaultOrder = [
4
+ 'name', 'description', 'author', 'namespace', 'version', 'license', 'icon', 'icon64',
5
+ 'compatible', 'match', 'exclude', 'include', 'connect', 'require', 'resource', 'grant',
6
+ 'noframes', 'downloadURL', 'updateURL', 'homepageURL', 'supportURL', 'contributionURL'
7
+ ]
8
+
9
+ function findViolation(entries, keys) {
10
+ const ranks = new Map(keys.map((key, index) => [key, index])),
11
+ known = entries.map(entry => {
12
+ const key = entry.key.split(':', 1)[0]
13
+ return { entry, key, rank: ranks.get(key) }
14
+ }).filter(item => item.rank !== undefined)
15
+ for (let i = 1; i < known.length; i++) // eslint-disable-next-line security/detect-object-injection
16
+ if (known[i - 1].rank > known[i].rank) return [known[i - 1], known[i]]
17
+ }
18
+
19
+ function sortEntries(entries, keys) {
20
+ const ranks = new Map(keys.map((key, index) => [key, index]))
21
+ return entries.toSorted((a, b) => {
22
+ const aRank = ranks.get(a.key.split(':', 1)[0]),
23
+ bRank = ranks.get(b.key.split(':', 1)[0])
24
+ return (aRank ?? Infinity) - (bRank ?? Infinity)
25
+ })
26
+ }
27
+
28
+ export default {
29
+ meta: {
30
+ type: 'layout',
31
+ docs: {
32
+ description: 'Enforce an order for metadata keys',
33
+ category: 'Stylistic Issues',
34
+ recommended: false
35
+ },
36
+ fixable: 'code',
37
+ schema: [{
38
+ type: 'array',
39
+ items: { type: 'string' },
40
+ description: 'Metadata keys in preferred order; unspecified keys are ignored'
41
+ }],
42
+ defaultOptions: [[]],
43
+ messages: { wrongOrder: '@{{first}} should appear before @{{second}}' }
44
+ },
45
+
46
+ create(context) {
47
+ const custom = context.options[0] ?? [],
48
+ master = custom.length ? defaultOrder.filter(key => !custom.includes(key)) : defaultOrder
49
+ return {
50
+ Program() {
51
+ const sourceCode = context.sourceCode,
52
+ entries = extractMetadata(sourceCode),
53
+ violation = findViolation(entries, master) ?? (custom.length && findViolation(entries, custom))
54
+ if (!violation) return
55
+ const [first, second] = violation
56
+ const sorted = sortEntries(sortEntries(entries, master), custom)
57
+ const replacements = // eslint-disable-next-line security/detect-object-injection
58
+ new Map(sorted.map((entry, idx) => [entries[idx], sourceCode.getText(entry.comment)]))
59
+ const start = entries[0].comment.range[0]
60
+ const end = entries.at(-1).comment.range[1]
61
+ let output = '', position = start
62
+ for (const entry of entries) {
63
+ output += sourceCode.text.slice(position, entry.comment.range[0])
64
+ output += replacements.get(entry)
65
+ position = entry.comment.range[1]
66
+ }
67
+ output += sourceCode.text.slice(position, end)
68
+ context.report({
69
+ node: second.entry.comment, messageId: 'wrongOrder',
70
+ data: { first: second.key, second: first.key },
71
+ fix(fixer) { return fixer.replaceTextRange([start, end], output) }
72
+ })
73
+ }
74
+ }
75
+ }
76
+ }
@@ -21,13 +21,13 @@ export default {
21
21
  return {
22
22
  Program() {
23
23
  const metaEntries = extractMetadata(context.sourceCode),
24
- seen = new Set()
24
+ seenEntries = new Set()
25
25
  for (const entry of metaEntries) {
26
26
  const baseKey = entry.key.split(':')[0]
27
27
  if (!entry.key.includes(':') && multiValKeys.has(baseKey)) continue
28
- if (seen.has(entry.key))
28
+ if (seenEntries.has(entry.key))
29
29
  context.report({ node: entry.comment, messageId: 'dupeKey', data: { key: entry.key }})
30
- else seen.add(entry.key)
30
+ else seenEntries.add(entry.key)
31
31
  }
32
32
  }
33
33
  }
@@ -46,8 +46,7 @@ export default {
46
46
  const options = context.options[0] || {},
47
47
  rawTarget = options.target ?? 'all',
48
48
  targets = Array.isArray(rawTarget) ? rawTarget : [rawTarget],
49
- rawAllowed = options.allowedHeaders ?? [],
50
- allowedList = Array.isArray(rawAllowed) ? rawAllowed : [rawAllowed],
49
+ allowedList = [options.allowedHeaders ?? []].flat(),
51
50
  allowedExact = new Set(),
52
51
  allowedRegexes = [],
53
52
  validSet = targets.includes('all') ? validHeaders.get('all')
@@ -22,8 +22,7 @@ export default {
22
22
  return {
23
23
  Identifier(node) { record(node.name, node) },
24
24
  MemberExpression(node) {
25
- const path = []
26
- let current = node
25
+ const path = [] ; let current = node
27
26
  while (current.type == 'MemberExpression') {
28
27
  if (current.property.type != 'Identifier') return
29
28
  path.unshift(current.property.name)
@@ -52,8 +52,8 @@ export default {
52
52
  if (!declared.has(name))
53
53
  context.report({ node: ref.node, messageId: 'missingResource', data: { name }})
54
54
  } else if (![...declared].some(name => ref.pattern.test(name)))
55
- context.report({
56
- node: ref.node, messageId: 'noMatchingResource', data: { pattern: ref.pattern.source }})
55
+ context.report({ node: ref.node, messageId: 'noMatchingResource',
56
+ data: { pattern: ref.pattern.source }})
57
57
  }
58
58
  }
59
59
  }
@@ -23,8 +23,7 @@ export default {
23
23
  if (!tokenAfter) return
24
24
  if (tokenAfter.loc.start.line - closingComment.loc.end.line < 2)
25
25
  context.report({
26
- node: closingComment,
27
- messageId: 'missingBlankLine',
26
+ node: closingComment, messageId: 'missingBlankLine',
28
27
  fix(fixer) { return fixer.insertTextAfter(closingComment, '\n') }
29
28
  })
30
29
  }
@@ -18,8 +18,8 @@ export default {
18
18
  Program() {
19
19
  const metaEntries = extractMetadata(context.sourceCode),
20
20
  homepageEntry = metaEntries.find(entry => entry.key == 'homepage'),
21
- homepageURLEntry = metaEntries.find(entry => entry.key == 'homepageURL')
22
- if (homepageEntry && !homepageURLEntry)
21
+ homepageURLentry = metaEntries.find(entry => entry.key == 'homepageURL')
22
+ if (homepageEntry && !homepageURLentry)
23
23
  context.report({
24
24
  node: homepageEntry.comment, messageId: 'missingPair',
25
25
  data: { missing: 'homepageURL', present: 'homepage' },
@@ -28,13 +28,13 @@ export default {
28
28
  `\n// @homepageURL ${homepageEntry.value}`)
29
29
  }
30
30
  })
31
- else if (homepageURLEntry && !homepageEntry)
31
+ else if (homepageURLentry && !homepageEntry)
32
32
  context.report({
33
- node: homepageURLEntry.comment, messageId: 'missingPair',
33
+ node: homepageURLentry.comment, messageId: 'missingPair',
34
34
  data: { missing: 'homepage', present: 'homepageURL' },
35
35
  fix(fixer) {
36
- return fixer.insertTextBefore(homepageURLEntry.comment,
37
- `// @homepage ${homepageURLEntry.value}\n`)
36
+ return fixer.insertTextBefore(homepageURLentry.comment,
37
+ `// @homepage ${homepageURLentry.value}\n`)
38
38
  }
39
39
  })
40
40
  }
@@ -12,9 +12,11 @@ export default {
12
12
  type: 'object',
13
13
  properties: {
14
14
  ignoreLanguages: {
15
- type: 'array',
16
- items: { type: 'string' },
17
- description: 'Language codes to ignore (e.g. ["fr", "de"])'
15
+ anyOf: [
16
+ { type: 'string' },
17
+ { type: 'array', items: { type: 'string' }, minItems: 1, uniqueItems: true }
18
+ ],
19
+ description: 'Language code(s) to ignore'
18
20
  }
19
21
  },
20
22
  additionalProperties: false
@@ -25,7 +27,7 @@ export default {
25
27
 
26
28
  create(context) {
27
29
  const options = context.options[0] || {},
28
- ignoreLanguages = options.ignoreLanguages || []
30
+ ignoreLanguages = [options.ignoreLanguages ?? []].flat()
29
31
  return {
30
32
  Program() {
31
33
  const metaEntries = extractMetadata(context.sourceCode),
@@ -0,0 +1,57 @@
1
+ import { extractMetadata } from '../utils.js'
2
+
3
+ const re = { hashFragment: /#([^#]+)$/, sriHash: /^(sha1|sha256|sha384|sha512|md5)-([a-z0-9+/]+={0,2})$/i }
4
+
5
+ export default {
6
+ meta: {
7
+ type: 'problem',
8
+ docs: {
9
+ description: 'Require SRI hashes on @require and @resource URLs',
10
+ category: 'Possible Errors',
11
+ recommended: false
12
+ },
13
+ schema: [{
14
+ type: 'object',
15
+ description: 'SRI hashing options',
16
+ properties: {
17
+ algorithm: {
18
+ type: 'string',
19
+ description: 'SRI hash algorithm to require',
20
+ enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512']
21
+ }
22
+ },
23
+ additionalProperties: false
24
+ }],
25
+ defaultOptions: [{ algorithm: 'sha256' }],
26
+ messages: {
27
+ missingHash: 'Missing SRI hash for @{{key}}.',
28
+ invalidHash: 'Invalid SRI hash for @{{key}}.{{detected}} Expected {{expected}}.'
29
+ }
30
+ },
31
+
32
+ create(context) {
33
+ const algorithm = (context.options[0]?.algorithm || 'sha256').toLowerCase()
34
+ return {
35
+ 'Program:exit'() {
36
+ for (const entry of extractMetadata(context.sourceCode)) {
37
+ if (entry.key != 'require' && entry.key != 'resource') continue
38
+ const hash = entry.value.trim().match(re.hashFragment)?.[1]
39
+ if (!hash) {
40
+ context.report({ node: entry.comment, messageId: 'missingHash', data: { key: entry.key } })
41
+ continue
42
+ }
43
+ const match = hash.match(re.sriHash)
44
+ if (!match || match[1].toLowerCase() != algorithm)
45
+ context.report({
46
+ node: entry.comment, messageId: 'invalidHash',
47
+ data: {
48
+ key: entry.key,
49
+ detected: match ? ` Detected ${match[1].toLowerCase()};` : '',
50
+ expected: algorithm
51
+ }
52
+ })
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
@@ -1,5 +1,5 @@
1
1
  import { extractMetadata } from '../utils.js'
2
- import { allAPIs, isAPIValidFor } from '../data/apis.js'
2
+ import { allAPIs, supportsAPI } from '../data/apis.js'
3
3
 
4
4
  const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat'],
5
5
  specialGrants = new Set(['none', 'unsafeWindow', 'window.close', 'window.focus', 'window.onurlchange']),
@@ -25,8 +25,10 @@ export default {
25
25
  description: 'Manager(s) whose grants to validate against (default: all)'
26
26
  },
27
27
  allowedGrants: {
28
- type: 'array',
29
- items: { type: 'string' },
28
+ anyOf: [
29
+ { type: 'string' },
30
+ { type: 'array', items: { type: 'string' }, minItems: 1, uniqueItems: true }
31
+ ],
30
32
  description: 'Additional @grant values to allow beyond the built-in list'
31
33
  }
32
34
  },
@@ -44,7 +46,7 @@ export default {
44
46
  const options = context.options[0] || {},
45
47
  rawTarget = options.target ?? 'all',
46
48
  targets = Array.isArray(rawTarget) ? rawTarget : [rawTarget],
47
- allowedGrants = options.allowedGrants || []
49
+ allowedGrants = [options.allowedGrants ?? []].flat()
48
50
  return {
49
51
  Program() {
50
52
  const metaEntries = extractMetadata(context.sourceCode),
@@ -52,7 +54,7 @@ export default {
52
54
  for (const grant of grantEntries) {
53
55
  const grantName = grant.value.trim()
54
56
  if (specialGrants.has(grantName) || allowedGrants.includes(grantName)) continue
55
- else if (!allGrants.has(grantName)) {
57
+ if (!allGrants.has(grantName)) {
56
58
  const suggestion = sortedGrants.get(grantName.toLowerCase())
57
59
  if (suggestion && suggestion != grantName)
58
60
  context.report({ node: grant.comment, messageId: 'wrongCase',
@@ -61,8 +63,11 @@ export default {
61
63
  context.report({ node: grant.comment, messageId: 'invalidGrant',
62
64
  data: { grant: grantName }})
63
65
  continue
64
- } else if (!targets.includes('all') && !targets.some(m => isAPIValidFor(grantName, m))) {
65
- const supporting = managers.filter(manager => isAPIValidFor(grantName, manager ))
66
+ }
67
+ if (!targets.includes('all')
68
+ && !targets.some(manager => supportsAPI({ manager, api: grantName }))
69
+ ) {
70
+ const supporting = managers.filter(manager => supportsAPI({ manager, api: grantName }))
66
71
  context.report({ node: grant.comment, messageId: 'unsupportedGrant',
67
72
  data: { grant: grantName, target: targets.join(', '), supporting: supporting.join(', ') }})
68
73
  }