eslint-plugin-greasemonkey 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
 
@@ -51,6 +54,7 @@ export default [
51
54
 
52
55
  | ID | Description | Auto-fixable | Recommended |
53
56
  | --- | --- | :-: | :-: |
57
+ | [`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
58
  | [`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
59
  | [`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
60
  | [`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 +68,7 @@ export default [
64
68
  | [`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
69
  | [`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
70
  | [`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"> |
71
+ | [`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
72
  | [`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
73
  | [`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
74
  | [`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 +76,95 @@ export default [
71
76
 
72
77
  ---
73
78
 
79
+ <h3 id="meta-order">
80
+ <code>meta-order</code>
81
+ <a title="Only included in strict config" href="#usage">
82
+ <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
83
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
84
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
85
+ <a title="Doesn't catch bugs" href="#meta-order">
86
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
87
+ <a title="Accepts array w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
88
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
89
+ <a title="Copy rule URL" target="_blank"
90
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23meta-order">
91
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
92
+ </h3>
93
+
94
+ > Enforces a preferred order for metadata keys.
95
+
96
+ #### Options
97
+
98
+ - `string[]` (default `[]`) – metadata keys in custom order
99
+
100
+ For example:
101
+
102
+ ```js
103
+ /* eslint greasemonkey/meta-order: ['error', ['run-in', 'inject-into']] */
104
+ ```
105
+
106
+ #### <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
107
+
108
+ ```diff
109
+ // ==UserScript==
110
+ // @name My Script
111
+ ! // @version 1.0.0
112
+ // @description A script
113
+ ! // @namespace example.com
114
+ // @author John Doe
115
+ ! // @license MIT
116
+ // @icon https://example.com/icon.png
117
+ ! // @match https://example.com/*
118
+ // @grant GM_setValue
119
+ // @require https://example.com/lib.js
120
+ // @resource logo https://example.com/logo.png
121
+ ! // @connect example.com
122
+ // ==/UserScript==
123
+ ```
124
+
125
+ #### <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
126
+
127
+ ```diff
128
+ // ==UserScript==
129
+ // @name My Script
130
+ - // @version 1.0.0
131
+ // @description A script
132
+ - // @namespace example.com
133
+ // @author John Doe
134
+ - // @license MIT
135
+ + // @namespace example.com
136
+ + // @version 1.0.0
137
+ + // @license MIT
138
+ // @icon https://example.com/icon.png
139
+ - // @match https://example.com/*
140
+ + // @connect example.com
141
+ // @grant GM_setValue
142
+ - // @require https://example.com/lib.js
143
+ - // @resource logo https://example.com/logo.png
144
+ - // @connect example.com
145
+ + // @match https://example.com/*
146
+ + // @require https://example.com/lib.js
147
+ + // @resource logo https://example.com/logo.png
148
+ // ==/UserScript==
149
+ ```
150
+
151
+ #### When not to use it
152
+
153
+ If you don't care about the order of metadata fields.
154
+
155
+ #### See also
156
+
157
+ - [`meta-spacing`](#meta-spacing)
158
+ - [`no-invalid-headers`](#no-invalid-headers)
159
+
160
+ <div align="center">
161
+
162
+ [Back to index ↑][epg-rules]
163
+
164
+ </div>
165
+
166
+ ---
167
+
74
168
  <h3 id="meta-spacing">
75
169
  <code>meta-spacing</code>
76
170
  <a title="Included in recommended config" href="#usage">
@@ -204,8 +298,8 @@ If you intentionally repeat single-value keys.
204
298
 
205
299
  #### Options
206
300
 
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
301
+ - `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`), as a single string or array
302
+ - `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
209
303
 
210
304
  #### <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
305
 
@@ -236,7 +330,7 @@ If you intentionally repeat single-value keys.
236
330
 
237
331
  #### When not to use it
238
332
 
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.
333
+ If your script relies heavily on custom headers and you'd rather not maintain `allowedHeaders`.
240
334
 
241
335
  #### See also
242
336
 
@@ -425,7 +519,7 @@ If you access the granted API through an alias or computed property rather than
425
519
 
426
520
  #### When not to use it
427
521
 
428
- If you pass the resource name through a variable or alias rather than using it literally.
522
+ If you pass resource names through variables or aliases rather than use them literally.
429
523
 
430
524
  #### See also
431
525
 
@@ -448,7 +542,7 @@ If you pass the resource name through a variable or alias rather than using it l
448
542
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
449
543
  </h3>
450
544
 
451
- > Prefers `@match` over `@include` (deprecated in Chrome MV3).
545
+ > 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
546
 
453
547
  #### <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
548
 
@@ -469,7 +563,11 @@ If you pass the resource name through a variable or alias rather than using it l
469
563
 
470
564
  #### When not to use it
471
565
 
472
- If you use `@include` in non-MV3 environments, or rely on regex syntax that `@match` cannot express.
566
+ If you rely on `@include` patterns that cannot be expressed using `@match`.
567
+
568
+ #### See also
569
+
570
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome match patterns</a>
473
571
 
474
572
  <div align="center">
475
573
 
@@ -590,7 +688,7 @@ If your target manager does not fall back to `@namespace` when a homepage key is
590
688
 
591
689
  #### Options
592
690
 
593
- - `ignoreLanguages` (default `[]`) – language codes to skip, e.g. `['fr', 'de']`
691
+ - `ignoreLanguages` (default `[]`) – language code(s) to skip (e.g. `'de'` or `['es', 'fr']`)
594
692
 
595
693
  #### <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
694
 
@@ -743,6 +841,67 @@ If you intentionally use a different prefix style.
743
841
 
744
842
  ---
745
843
 
844
+ <h3 id="require-sri-hashing">
845
+ <code>require-sri-hashing</code>
846
+ <a title="Only included in strict config" href="#usage">
847
+ <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
848
+ <a title="Doesn't catch bugs" href="#require-sri-hashing">
849
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
850
+ <a title="Accepts object w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
851
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
852
+ <a title="Copy rule URL" target="_blank"
853
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-sri-hashing">
854
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
855
+ </h3>
856
+
857
+ > Requires SRI hashes on `@require` and `@resource` URLs.
858
+
859
+ #### Options
860
+
861
+ - `algorithm` (default `'sha256'`) – SRI hash algorithm to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`)
862
+
863
+ #### <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
864
+
865
+ ```diff
866
+ /* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
867
+
868
+ // ==UserScript==
869
+ ! // @require https://example.com/library.js#sha256-BASE64HASH
870
+ ! // @resource logo https://example.com/logo.png#sha256-BASE64HASH
871
+ // ==/UserScript==
872
+ ```
873
+
874
+ #### <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
875
+
876
+ ```diff
877
+ /* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
878
+
879
+ // ==UserScript==
880
+ - // @require https://example.com/library.js#sha256-BASE64HASH
881
+ - // @resource logo https://example.com/logo.png#sha256-BASE64HASH
882
+ + // @require https://example.com/library.js#sha384-BASE64HASH
883
+ + // @resource logo https://example.com/logo.png#sha384-BASE64HASH
884
+ // ==/UserScript==
885
+ ```
886
+
887
+ #### When not to use it
888
+
889
+ If you prefer shorter URLs, or don't support users who enforce subresource integrity validation via manager settings.
890
+
891
+ #### See also
892
+
893
+ - [`no-invalid-headers`](#no-invalid-headers)
894
+ - [`require-required-fields`](#require-required-fields)
895
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey Subresource Integrity</a>
896
+
897
+ <div align="center">
898
+
899
+ [Back to index ↑][epg-rules]
900
+
901
+ </div>
902
+
903
+ ----
904
+
746
905
  <h3 id="require-update-download-pair">
747
906
  <code>require-update-download-pair</code>
748
907
  <a title="Only included in strict config" href="#usage">
@@ -777,7 +936,7 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
777
936
 
778
937
  #### See also
779
938
 
780
- - [`@updateURL`](https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL)
939
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL"><code>@updateURL</code></a>
781
940
  - [`require-homepage-pair`](#require-homepage-pair)
782
941
 
783
942
  <div align="center">
@@ -799,6 +958,19 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
799
958
 
800
959
  > Requires that files containing a userscript metadata block end in `.user.js`.
801
960
 
961
+ #### <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
962
+
963
+ ```diff
964
+ ! /my-userscript.js
965
+ ```
966
+
967
+ #### <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
968
+
969
+ ```diff
970
+ - /my-userscript.js
971
+ + /my-userscript.user.js
972
+ ```
973
+
802
974
  #### When not to use it
803
975
 
804
976
  If you intentionally name your userscripts something other than `*.user.js`.
@@ -884,8 +1056,8 @@ If you use a custom versioning scheme or URLs that aren't standard.
884
1056
 
885
1057
  #### Options
886
1058
 
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)
1059
+ - `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
1060
+ - `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat), as a single string or array
889
1061
 
890
1062
  #### <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
1063
 
@@ -930,7 +1102,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
930
1102
 
931
1103
  ## Shield embed
932
1104
 
933
- <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1105
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
934
1106
  <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
1107
 
936
1108
  #### HTML
@@ -950,11 +1122,12 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
950
1122
 
951
1123
  ## License
952
1124
 
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]
1125
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
1126
+ <img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
954
1127
 
955
1128
  <details>
956
1129
 
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>
1130
+ <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
1131
 
959
1132
  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
1133
 
@@ -968,24 +1141,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
968
1141
 
969
1142
  ## Related
970
1143
 
971
- <!-- eslint-plugin-constructors -->
1144
+ <!-- eslint-plugin-constructors -->
972
1145
 
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]
1146
+ ### <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
1147
 
975
1148
  <details>
976
1149
 
977
1150
  <summary>ESLint rules for JavaScript constructor usage.</summary><br>
978
1151
 
979
- > [<img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png">][epc-readme]
1152
+ > <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
1153
 
981
- > [**Install**][epc-install] / [Readme][epc-readme] / [Supported rules][epc-rules]
1154
+ > <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
1155
 
983
1156
  </details>
984
1157
 
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
1158
  <!-- Footer -->
990
1159
 
991
1160
  <img height=10 width="100%" src="https://cdn.staticdelivr.com/gl/adamlui/ai-web-extensions/d11d2ee/assets/images/separators/gradient-aqua.png">
@@ -993,15 +1162,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
993
1162
  <div align="center">
994
1163
 
995
1164
  <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)
1165
+ <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
1166
 
998
1167
  </div>
999
1168
 
1000
1169
  <!-- Links -->
1001
1170
 
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
1171
  [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.0",
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
  }