eslint-plugin-greasemonkey 1.6.0 → 1.7.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
@@ -52,7 +52,7 @@ export default [
52
52
  ]
53
53
  ```
54
54
 
55
- Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`
55
+ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`, `adguard`
56
56
 
57
57
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
58
58
 
@@ -70,7 +70,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
70
70
  <a title="Only included in strict config" href="#usage">
71
71
  <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
72
72
  <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
73
- <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
73
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
74
74
  <a title="Doesn't prevent bugs" href="#meta-order">
75
75
  <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
76
76
  <a title="Accepts array w/ options" href="#usage">
@@ -86,7 +86,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
86
86
 
87
87
  #### Options
88
88
 
89
- - `order` (default `[]`) – metadata keys in custom order
89
+ - `order` (default `[]`) – metadata keys in custom order (e.g. `['license', 'namespace']`) to selectively override built-in order with
90
90
 
91
91
  #### <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
92
92
 
@@ -97,7 +97,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
97
97
  // @description A script
98
98
  ! // @namespace example.com
99
99
  // @author John Doe
100
- ! // @license MIT
100
+ // @license MIT
101
101
  // ==/UserScript==
102
102
  ```
103
103
 
@@ -110,10 +110,9 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
110
110
  // @description A script
111
111
  - // @namespace example.com
112
112
  // @author John Doe
113
- - // @license MIT
114
113
  + // @namespace example.com
115
114
  + // @version 1.0.0
116
- + // @license MIT
115
+ // @license MIT
117
116
  // ==/UserScript==
118
117
  ```
119
118
 
@@ -145,7 +144,7 @@ If you don't care about the order of metadata fields.
145
144
  <a title="Included in recommended config" href="#usage">
146
145
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
147
146
  <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
148
- <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
147
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
149
148
  <a title="Doesn't prevent bugs" href="#meta-spacing">
150
149
  <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
151
150
  <a title="Accepts array w/ options" href="#usage">
@@ -280,6 +279,16 @@ If you intentionally repeat single-value keys.
280
279
  <code>no-invalid-grants</code>
281
280
  <a title="Included in recommended config" href="#usage">
282
281
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
282
+ <a title="Optimized in tampermonkey config" href="#usage">
283
+ <img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
284
+ <a title="Optimized in violentmonkey config" href="#usage">
285
+ <img height=18 alt="[Violentmonkey]" src="https://img.shields.io/badge/Violentmonkey-f01e07?style=flat-square" align="middle"></a>
286
+ <a title="Optimized in greasemonkey config" href="#usage">
287
+ <img height=18 alt="[Greasemonkey]" src="https://img.shields.io/badge/Greasemonkey-6c4d8c?style=flat-square" align="middle"></a>
288
+ <a title="Optimized in scriptcat config" href="#usage">
289
+ <img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
290
+ <a title="Optimized in adguard config" href="#usage">
291
+ <img height=18 alt="[AdGuard]" src="https://img.shields.io/badge/AdGuard-68bd72?style=flat-square" align="middle"></a>
283
292
  <a title="Accepts array w/ options" href="#usage">
284
293
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
285
294
  <a title="Copy rule URL" target="_blank"
@@ -293,8 +302,8 @@ If you intentionally repeat single-value keys.
293
302
 
294
303
  #### Options
295
304
 
296
- - `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
297
- - `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat), as a single string or array
305
+ - `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`, `'adguard'`), as a single string or array
306
+ - `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey, Greasemonkey, ScriptCat and AdGuard), as a single string or array
298
307
 
299
308
  #### <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
300
309
 
@@ -363,6 +372,16 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
363
372
  <code>no-invalid-headers</code>
364
373
  <a title="Included in recommended config" href="#usage">
365
374
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
375
+ <a title="Optimized in tampermonkey config" href="#usage">
376
+ <img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
377
+ <a title="Optimized in violentmonkey config" href="#usage">
378
+ <img height=18 alt="[Violentmonkey]" src="https://img.shields.io/badge/Violentmonkey-f01e07?style=flat-square" align="middle"></a>
379
+ <a title="Optimized in greasemonkey config" href="#usage">
380
+ <img height=18 alt="[Greasemonkey]" src="https://img.shields.io/badge/Greasemonkey-6c4d8c?style=flat-square" align="middle"></a>
381
+ <a title="Optimized in scriptcat config" href="#usage">
382
+ <img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
383
+ <a title="Optimized in adguard config" href="#usage">
384
+ <img height=18 alt="[AdGuard]" src="https://img.shields.io/badge/AdGuard-68bd72?style=flat-square" align="middle"></a>
366
385
  <a title="Accepts array w/ options" href="#usage">
367
386
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
368
387
  <a title="Copy rule URL" target="_blank"
@@ -376,7 +395,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
376
395
 
377
396
  #### Options
378
397
 
379
- - `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
398
+ - `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`, `'adguard'`), as a single string or array
380
399
  - `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
381
400
 
382
401
  #### <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
@@ -387,8 +406,8 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
387
406
  // ==UserScript==
388
407
  // @name My Script
389
408
  ! // @runin normal-tabs
409
+ ! // @customURI https://example.com
390
410
  ! // @inject-into page
391
- // @customURL https://example.com
392
411
  // ==/UserScript==
393
412
  ```
394
413
 
@@ -401,8 +420,9 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
401
420
  // @name My Script
402
421
  - // @runin normal-tabs
403
422
  + // @run-in normal-tabs
423
+ - // @customURI https://example.com
424
+ + // @customURL https://example.com
404
425
  - // @inject-into page
405
- // @customURL https://example.com
406
426
  // ==/UserScript==
407
427
  ```
408
428
 
@@ -440,7 +460,7 @@ If your script relies heavily on custom headers and you'd rather not maintain `a
440
460
 
441
461
  <details>
442
462
 
443
- <summary>Flags API calls (e.g. <code>GM_setValue</code>, <code>GM.setValue</code>, <code>CAT.agent.dom</code>) used w/o a matching <code>@grant</code> declaration.</summary>
463
+ <summary>Flags API calls (e.g. <code>GM_setValue</code>, <code>CAT.agent.dom</code>) used w/o a matching <code>@grant</code> declaration.</summary>
444
464
 
445
465
  #### <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
446
466
 
@@ -497,7 +517,7 @@ If you use APIs through aliases or wrappers that obscure the original call site.
497
517
 
498
518
  <details>
499
519
 
500
- <summary>Flags <code>@resource</code> references via <code>GM_getResourceText()</code> or <code>GM_getResourceURL()</code> where no matching <code>@resource</code> is declared.</summary>
520
+ <summary>Flags <code>@resource</code> references via <code>GM_getResource<Text|URL>()</code> w/o a matching <code>@resource</code> declaration.</summary>
501
521
 
502
522
  #### <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
503
523
 
@@ -660,7 +680,7 @@ If you pass resource names through variables or aliases rather than use them lit
660
680
 
661
681
  <details>
662
682
 
663
- <summary>Prefers <code>@match</code> over <code>@include</code> to align w/ <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/userScripts/"><code>chrome.userScripts</code></a> syntax.</summary>
683
+ <summary>Prefers <code>@match</code> over <code>@include</code> for safer URL matching.</summary>
664
684
 
665
685
  #### <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
666
686
 
@@ -689,7 +709,8 @@ If you rely on `@include` patterns that cannot be expressed using `@match`.
689
709
 
690
710
  #### See also
691
711
 
692
- - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome match patterns</a>
712
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?locale=en&q=include#meta:match">Tampermonkey — `@match`</a>
713
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?locale=en&q=include#meta:include">Tampermonkey — `@include`</a>
693
714
 
694
715
  <div align="center">
695
716
 
@@ -706,7 +727,7 @@ If you rely on `@include` patterns that cannot be expressed using `@match`.
706
727
  <a title="Included in recommended config" href="#usage">
707
728
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
708
729
  <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
709
- <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
730
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
710
731
  <a title="Doesn't prevent bugs" href="#require-blank-line-after-meta-block">
711
732
  <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
712
733
  <a title="Copy rule URL" target="_blank"
@@ -769,7 +790,7 @@ If you don't care about visual separation between metadata and code.
769
790
  <a title="Only included in strict config" href="#usage">
770
791
  <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
771
792
  <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
772
- <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
793
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
773
794
  <a title="Copy rule URL" target="_blank"
774
795
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-homepage-pair">
775
796
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -798,7 +819,7 @@ If you don't care about visual separation between metadata and code.
798
819
 
799
820
  #### When not to use it
800
821
 
801
- If your target manager does not fall back to `@namespace` when a homepage key is missing (which usually produces a broken URL).
822
+ If you don't care about ensuring homepage URL compatibility across different managers.
802
823
 
803
824
  #### Introduced in
804
825
 
@@ -807,6 +828,8 @@ If your target manager does not fall back to `@namespace` when a homepage key is
807
828
  #### See also
808
829
 
809
830
  - [`require-update-download-pair`](#require-update-download-pair)
831
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=homepage#meta:homepage">Tampermonkey `@homepage`, `@homepageURL`, `@website`, `@source`</a>
832
+ - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#homepage-homepageurl-website">ScriptCat `@homepage`, `@homepageURL`, `@website`</a>
810
833
 
811
834
  <div align="center">
812
835
 
@@ -900,7 +923,7 @@ If you intentionally provide localized names without descriptions (or vice versa
900
923
 
901
924
  <details>
902
925
 
903
- <summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code> and at least one <code>@match</code> or <code>@include</code>by default).</summary>
926
+ <summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code> and at least one <code>@match</code> or <code>@include</code>).</summary>
904
927
 
905
928
  #### Options
906
929
 
@@ -932,11 +955,11 @@ If you intentionally provide localized names without descriptions (or vice versa
932
955
 
933
956
  #### When not to use it
934
957
 
935
- If you use a subset of metadata fields that doesn't match the convention.
958
+ If your script is a work-in-progress, or metadata is injected at build time.
936
959
 
937
960
  #### Introduced in
938
961
 
939
- <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026) as `require-required-fields`
962
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
940
963
 
941
964
  #### See also
942
965
 
@@ -958,7 +981,7 @@ If you use a subset of metadata fields that doesn't match the convention.
958
981
  <a title="Included in recommended config" href="#usage">
959
982
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
960
983
  <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
961
- <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
984
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
962
985
  <a title="Doesn't prevent bugs" href="#require-space-before-meta-key">
963
986
  <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
964
987
  <a title="Copy rule URL" target="_blank"
@@ -1029,7 +1052,7 @@ If you intentionally use a different prefix style.
1029
1052
 
1030
1053
  #### Options
1031
1054
 
1032
- - `algorithm` (default `'sha256'`) – SRI hash algorithm to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`)
1055
+ - `algorithm` (default `'sha256'`) – SRI hash algorithm(s) to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`), as a single string or array
1033
1056
 
1034
1057
  #### <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
1035
1058
 
@@ -1069,7 +1092,8 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1069
1092
 
1070
1093
  - [`no-invalid-headers`](#no-invalid-headers)
1071
1094
  - [`require-required-fields`](#require-required-fields)
1072
- - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey Subresource Integrity</a>
1095
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey — Subresource Integrity</a>
1096
+ - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#resource-integrity-verification">ScriptCat — Resource Integrity Verification</a>
1073
1097
 
1074
1098
  <div align="center">
1075
1099
 
@@ -1083,8 +1107,12 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1083
1107
 
1084
1108
  <h3 id="require-update-download-pair">
1085
1109
  <code>require-update-download-pair</code>
1086
- <a title="Only included in strict config" href="#usage">
1087
- <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
1110
+ <a title="Included in strict config" href="#usage">
1111
+ <img height=18 alt="[Strict]" src="https://img.shields.io/badge/Strict-ff8c42?style=flat-square" align="middle"></a>
1112
+ <a title="Included in tampermonkey config" href="#usage">
1113
+ <img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
1114
+ <a title="Included in scriptcat config" href="#usage">
1115
+ <img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
1088
1116
  <a title="Copy rule URL" target="_blank"
1089
1117
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-update-download-pair">
1090
1118
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -1113,7 +1141,7 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1113
1141
 
1114
1142
  #### When not to use it
1115
1143
 
1116
- If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the full script on every update check).
1144
+ If you don't target Tampermonkey or ScriptCat (where `@updateURL` avoids re-downloading the full script on every update check).
1117
1145
 
1118
1146
  #### Introduced in
1119
1147
 
@@ -1121,8 +1149,9 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
1121
1149
 
1122
1150
  #### See also
1123
1151
 
1124
- - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL"><code>@updateURL</code></a>
1125
1152
  - [`require-homepage-pair`](#require-homepage-pair)
1153
+ - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL">Tampermonkey — `@updateURL`</a>
1154
+ - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#updateurl">ScriptCat — `@updateURL`</a>
1126
1155
 
1127
1156
  <div align="center">
1128
1157
 
@@ -1261,14 +1290,14 @@ If you use a custom versioning scheme or URLs that aren't standard.
1261
1290
  #### Markdown
1262
1291
 
1263
1292
  ```md
1264
- [![Linted by eslint-plugin-greasemonkey](https://img.shields.io/badge/Linted_by-eslint--plugin--greasemonkey-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge)](https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme)
1293
+ [![[Linted by eslint-plugin-greasemonkey]](https://img.shields.io/badge/Linted_by-eslint--plugin--greasemonkey-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge)](https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme)
1265
1294
  ```
1266
1295
 
1267
1296
  ---
1268
1297
 
1269
1298
  ## License
1270
1299
 
1271
- <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
1300
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md">
1272
1301
  <img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
1273
1302
  <br><br>
1274
1303
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-greasemonkey",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
5
5
  "type": "module",
6
6
  "engines": {
@@ -68,15 +68,15 @@
68
68
  "@eslint/json": "^2.1.0",
69
69
  "@eslint/markdown": "^8.0.3",
70
70
  "@stylistic/eslint-plugin": "^5.10.0",
71
- "eslint": "^10.10.0",
72
- "eslint-plugin-constructors": "^1.2.2",
71
+ "eslint": "^10.11.0",
72
+ "eslint-plugin-constructors": "^1.3.0",
73
73
  "eslint-plugin-eslint-plugin": "^7.6.2",
74
74
  "eslint-plugin-import-x": "^4.17.1",
75
75
  "eslint-plugin-n": "^18.3.0",
76
76
  "eslint-plugin-no-secrets": "^2.3.3",
77
77
  "eslint-plugin-no-unsanitized": "^4.1.5",
78
78
  "eslint-plugin-promise": "^7.3.0",
79
- "eslint-plugin-regexp": "^3.3.0",
79
+ "eslint-plugin-regexp": "^3.3.1",
80
80
  "eslint-plugin-security": "^4.0.1",
81
81
  "globals": "^17.12.0",
82
82
  "husky": "^9.1.7",
package/src/data/apis.js CHANGED
@@ -25,11 +25,30 @@ const tmAPIs = new Set([
25
25
  'GM.getTab', 'GM.getTabs', 'GM.saveTab', 'GM.webRequest'
26
26
  ])
27
27
 
28
+ const agAPIs = new Set([
29
+ 'GM_info', 'GM.info',
30
+ 'GM_setValue', 'GM.setValue',
31
+ 'GM_getValue', 'GM.getValue',
32
+ 'GM_listValues', 'GM.listValues',
33
+ 'GM_deleteValue', 'GM.deleteValue',
34
+ 'GM_getResourceURL', 'GM.getResourceUrl',
35
+ 'GM_setClipboard', 'GM.setClipboard',
36
+ 'GM_xmlhttpRequest', 'GM.xmlHttpRequest',
37
+ 'GM_openInTab', 'GM.openInTab',
38
+ 'GM_notification',
39
+ 'unsafeWindow',
40
+ 'GM_getResourceText',
41
+ 'GM_addStyle',
42
+ 'GM_log',
43
+ 'GM_addElement', 'GM.addElement'
44
+ ])
45
+
28
46
  export const allAPIs = new Set([...gmLegacyAPIs, ...gmModernAPIs, ...scAPIs])
29
47
 
30
48
  export function supportsAPI({ manager, api }) {
31
49
  return !allAPIs.has(api) ? false
32
50
  : api.startsWith('CAT') ? manager == 'scriptcat'
33
51
  : tmAPIs.has(api) ? manager == 'tampermonkey'
52
+ : manager == 'adguard' ? agAPIs.has(api)
34
53
  : true
35
54
  }
@@ -1,6 +1,8 @@
1
1
  // Delete a line to end support
2
2
 
3
3
  export const rules = { // old: new
4
+ 'meta-spacing': { 'meta-spacing': { spaces: 3 }},
5
+ 'meta-order': { 'meta-order': { order: [] }},
4
6
  'valid-grants': 'no-invalid-grants', // 2026-9-16 announced removal @ v2
5
7
  'require-meta-newline': 'require-blank-line-after-meta-block' // 2026-9-13 announced removal @ v2
6
8
  }
@@ -2,10 +2,11 @@ export const universalHeaders = new Set([
2
2
  'antifeature', 'author', 'compatible', 'connect', 'contributionAmount', 'contributionURL', 'copyright',
3
3
  'defaulticon', 'description', 'downloadURL', 'exclude', 'exclude-match', 'grant', 'homepage', 'homepageURL',
4
4
  'icon', 'icon64', 'include', 'license', 'match', 'name', 'namespace', 'noframes', 'require', 'resource',
5
- 'run-at', 'source', 'supportURL', 'unwrap', 'updateURL', 'version', 'website'
5
+ 'run-at', 'source', 'supportURL', 'updateURL', 'version', 'website'
6
6
  ])
7
7
 
8
8
  export const managerHeaders = new Map([
9
+ ['unwrap', ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat']],
9
10
  ['run-in', ['tampermonkey', 'scriptcat']],
10
11
  ['nocompat', ['tampermonkey']],
11
12
  ['sandbox', ['tampermonkey']],
@@ -22,7 +23,7 @@ export const managerHeaders = new Map([
22
23
  ])
23
24
 
24
25
  export const localizedHeaders = ['name', 'description', 'antifeature']
25
- export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat']
26
+ export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard']
26
27
  export const validHeaders = new Map()
27
28
 
28
29
  validHeaders.set('all', new Set([...universalHeaders, ...managerHeaders.keys()]))
package/src/index.js CHANGED
@@ -81,14 +81,15 @@ plugin.configs = {
81
81
  }
82
82
  }
83
83
 
84
- for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat'])
84
+ for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'])
85
85
  plugin.configs[manager] = {
86
86
  plugins: { greasemonkey: plugin },
87
87
  rules: {
88
88
  ...plugin.configs.recommended.rules,
89
89
  'greasemonkey/no-invalid-grants': ['error', { target: manager }],
90
90
  'greasemonkey/no-invalid-headers': ['error', { target: manager }],
91
- ...(manager == 'tampermonkey' && { 'greasemonkey/require-update-download-pair': 'error' })
91
+ ...(['tampermonkey', 'scriptcat', 'adguard'].includes(manager) &&
92
+ { 'greasemonkey/require-update-download-pair': 'error' })
92
93
  }
93
94
  }
94
95
 
@@ -35,31 +35,21 @@ export default {
35
35
  },
36
36
  fixable: 'code',
37
37
  schema: [{
38
- oneOf: [
39
- {
38
+ type: 'object',
39
+ properties: {
40
+ order: {
40
41
  type: 'array', items: { type: 'string' },
41
42
  description: 'Metadata keys in preferred order; unspecified keys are ignored'
42
- },
43
- {
44
- type: 'object',
45
- properties: {
46
- order: {
47
- type: 'array', items: { type: 'string' },
48
- description: 'Metadata keys in preferred order; unspecified keys are ignored'
49
- }
50
- },
51
- additionalProperties: false,
52
- description: 'Options for metadata key ordering'
53
43
  }
54
- ]
44
+ },
45
+ additionalProperties: false
55
46
  }],
56
- defaultOptions: [[]],
47
+ defaultOptions: [{ order: [] }],
57
48
  messages: { wrongOrder: '@{{first}} should appear before @{{second}}' }
58
49
  },
59
50
 
60
51
  create(context) {
61
- const option = context.options[0] ?? [],
62
- custom = Array.isArray(option) ? option : option.order ?? [],
52
+ const custom = context.options[0]?.order ?? [],
63
53
  master = custom.length ? defaultOrder.filter(key => !custom.includes(key)) : defaultOrder
64
54
  return {
65
55
  Program() {
@@ -12,31 +12,21 @@ export default {
12
12
  },
13
13
  fixable: 'whitespace',
14
14
  schema: [{
15
- oneOf: [
16
- {
15
+ type: 'object',
16
+ properties: {
17
+ spaces: {
17
18
  type: 'number',
18
19
  description: 'Minimum spaces after the longest key (default 3)'
19
- },
20
- {
21
- type: 'object',
22
- properties: {
23
- spaces: {
24
- type: 'number',
25
- description: 'Minimum spaces after the longest key (default 3)'
26
- }
27
- },
28
- additionalProperties: false,
29
- description: 'Options for metadata spacing'
30
20
  }
31
- ]
21
+ },
22
+ additionalProperties: false
32
23
  }],
33
- defaultOptions: [3],
24
+ defaultOptions: [{ spaces: 3 }],
34
25
  messages: { misaligned: 'Metadata values should be aligned at column {{column}}' }
35
26
  },
36
27
 
37
28
  create(context) {
38
- const option = context.options[0] ?? 3,
39
- minSpaces = typeof option == 'number' ? option : option.spaces ?? 3
29
+ const minSpaces = context.options[0]?.spaces ?? 3
40
30
  return {
41
31
  Program() {
42
32
  const sourceCode = context.sourceCode,
@@ -1,7 +1,7 @@
1
1
  import { extractMetadata } from '../utils.js'
2
2
  import { allAPIs, supportsAPI } from '../data/apis.js'
3
3
 
4
- const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat'],
4
+ const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'],
5
5
  specialGrants = new Set(['none', 'unsafeWindow', 'window.close', 'window.focus', 'window.onurlchange']),
6
6
  allGrants = new Set([...allAPIs, ...specialGrants]),
7
7
  sortedGrants = new Map(Array.from(allGrants, grant => [grant.toLowerCase(), grant]))
@@ -1,6 +1,7 @@
1
1
  import { extractMetadata } from '../utils.js'
2
2
 
3
- const re = { hashFragment: /#([^#]+)$/, sriHash: /^(sha1|sha256|sha384|sha512|md5)-([a-z0-9+/]+={0,2})$/i }
3
+ const re = {
4
+ hashEntry: /^(sha1|sha256|sha384|sha512|md5)[=-]([a-z0-9+/]+={0,2})$/i, hashFragment: /#([^#]+)$/, hashSep: /[,;]/ }
4
5
 
5
6
  export default {
6
7
  meta: {
@@ -15,9 +16,14 @@ export default {
15
16
  description: 'SRI hashing options',
16
17
  properties: {
17
18
  algorithm: {
18
- type: 'string',
19
- description: 'SRI hash algorithm to require',
20
- enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512']
19
+ anyOf: [
20
+ { type: 'string', enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512'] },
21
+ { type: 'array',
22
+ items: { type: 'string', enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512'] },
23
+ minItems: 1, uniqueItems: true
24
+ }
25
+ ],
26
+ description: 'SRI hash algorithm(s) to accept, as a single string or array (default: sha256)'
21
27
  }
22
28
  },
23
29
  additionalProperties: false
@@ -25,31 +31,37 @@ export default {
25
31
  defaultOptions: [{ algorithm: 'sha256' }],
26
32
  messages: {
27
33
  missingHash: 'Missing SRI hash for @{{key}}.',
28
- invalidHash: 'Invalid SRI hash for @{{key}}.{{detected}} Expected {{expected}}.'
34
+ invalidHash: 'Invalid SRI hash for @{{key}}.',
35
+ unsupportedAlgorithm: 'SRI hash for @{{key}} uses {{detected}}; expected {{expected}}.'
29
36
  }
30
37
  },
31
38
 
32
39
  create(context) {
33
- const algorithm = (context.options[0]?.algorithm || 'sha256').toLowerCase()
40
+ const rawAlgorithm = context.options[0]?.algorithm ?? 'sha256',
41
+ algorithms = (Array.isArray(rawAlgorithm) ? rawAlgorithm : [rawAlgorithm]).map(a => a.toLowerCase()),
42
+ expected = algorithms.join(' or ')
34
43
  return {
35
44
  'Program:exit'() {
36
45
  for (const entry of extractMetadata(context.sourceCode)) {
37
46
  if (entry.key != 'require' && entry.key != 'resource') continue
38
- const hash = entry.value.trim().match(re.hashFragment)?.[1]
39
- if (!hash) {
47
+ const fragment = entry.value.trim().match(re.hashFragment)?.[1]
48
+ if (!fragment) {
40
49
  context.report({ node: entry.comment, messageId: 'missingHash', data: { key: entry.key } })
41
50
  continue
42
51
  }
43
- const match = hash.match(re.sriHash)
44
- if (!match || match[1].toLowerCase() != algorithm)
52
+ const hashes = fragment.split(re.hashSep).map(hash => hash.trim()).filter(Boolean),
53
+ parsed = hashes.map(hash => hash.match(re.hashEntry)).filter(Boolean)
54
+ if (!parsed.length) {
55
+ context.report({ node: entry.comment, messageId: 'invalidHash', data: { key: entry.key } })
56
+ continue
57
+ }
58
+ if (!parsed.some(match => algorithms.includes(match[1].toLowerCase()))) {
59
+ const detected = parsed.map(match => match[1].toLowerCase()).join(', ')
45
60
  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
- }
61
+ node: entry.comment, messageId: 'unsupportedAlgorithm',
62
+ data: { key: entry.key, detected, expected }
52
63
  })
64
+ }
53
65
  }
54
66
  }
55
67
  }
package/src/utils.js CHANGED
@@ -10,7 +10,7 @@ export function extractMetadata(sourceCode) {
10
10
  else if (inMetaBlock && text == '==/UserScript==') break
11
11
  else if (inMetaBlock && text.startsWith('@')) { // capture entry
12
12
  const spaceIdx = text.search(re.whitespace),
13
- key = spaceIdx == -1 ? text.slice(1).trim() : text.slice(1, spaceIdx).trim(),
13
+ key = ( spaceIdx == -1 ? text.slice(1) : text.slice(1, spaceIdx) ).trim(),
14
14
  value = spaceIdx == -1 ? '' : text.slice(spaceIdx +1).trim()
15
15
  if (key) metaEntries.push({ key, value, comment, line: comment.loc.start.line })
16
16
  }
@@ -19,40 +19,75 @@ export function extractMetadata(sourceCode) {
19
19
  }
20
20
 
21
21
  export function initDeprecatedAlias(plugin, [oldName, alias]) { /* eslint-disable security/detect-object-injection */
22
- const entries = typeof alias == 'string' ? [[alias, null]] // old: new
23
- : Array.isArray(alias) ? alias.flatMap(entry =>
24
- typeof entry == 'string' ? [[entry, null]] : Object.entries(entry)) // old: [new, ...]
25
- : Object.entries(alias) // old: { new: { old-option: new-option }}
26
- const newNames = entries.filter(([name]) => name != oldName).map(([name]) => name),
27
- newRule = plugin.rules[newNames[0] || oldName]
22
+ const routes = typeof alias == 'string' ? { [alias]: null } // 'old-rule': 'new-rule'
23
+ : Array.isArray(alias) ? Object.fromEntries(alias.map(entry =>
24
+ typeof entry == 'string' ? [entry, null] // ['new-rule-1', 'new-rule-2']
25
+ : Object.entries(entry)[0])) // [{ 'new-rule': { 'old-opt': 'new-opt' }}]
26
+ : alias // { 'new-rule': null | { 'new-opt': default } | { 'old-val': 'new-val' } | { ..., 'new-opt': default }}
27
+ const targetNames = Object.keys(routes),
28
+ isRename = !targetNames.includes(oldName),
29
+ originals = Object.fromEntries(targetNames.map(name => [name, plugin.rules[name]])),
30
+ source = originals[oldName] ?? originals[targetNames[0]]
28
31
  plugin.rules[oldName] = {
29
- ...newRule,
30
32
  meta: {
31
- ...newRule.meta,
32
- schema: typeof alias == 'string' ? newRule.meta.schema : [{ type: 'object' }],
33
- docs: { ...newRule.meta.docs, recommended: false },
34
- deprecated: {
35
- message: `Use ${newNames.map(name => `\`${name}\``).join(', ')} instead.`,
36
- replacedBy: newNames.map(name => ({ rule: { name }}))
37
- }
33
+ ...source.meta,
34
+ defaultOptions: [],
35
+ schema: [{ type: ['object', 'array', 'string', 'number', 'boolean', 'null'] }],
36
+ docs: { ...source.meta.docs, recommended: isRename ? false : source.meta.docs.recommended },
37
+ messages: {
38
+ ...source.meta.messages,
39
+ deprecatedOption: `'{{old}}' is deprecated. Use '{{new}}' instead.`
40
+ },
41
+ ...(isRename ? { deprecated: {
42
+ message: `Use ${targetNames.map(name => `\`${name}\``).join(', ')} instead.`,
43
+ replacedBy: targetNames.map(name => ({ rule: { name }}))
44
+ }} : {})
38
45
  },
39
46
  create(context) {
40
- const oldOptions = context.options[0]
41
- return Object.assign({}, ...entries.map(([name, mapping]) => {
42
- const newRule = plugin.rules[name]
43
- const options = mapping ? {
44
- ...oldOptions,
45
- ...Object.fromEntries(Object.entries(mapping).map(([oldKey, newKey]) => // old: new
46
- [newKey, oldOptions?.[oldKey] ?? (newKey == 'reverseDefault' ? true : newKey)]))
47
- } : oldOptions
48
- const aliasContext = new Proxy({}, {
49
- get(_, property) {
50
- if (property == 'options') return [options]
51
- const val = context[property]
52
- return typeof val == 'function' ? val.bind(context) : val
47
+ const opts = context.options[0],
48
+ isObj = opts?.constructor === Object
49
+ return Object.assign({}, ...Object.entries(routes).map(([targetName, mapping]) => {
50
+ let newOpts = opts, deprecation
51
+ if (mapping) {
52
+ const entries = Object.entries(mapping),
53
+ namedRemap = entries.every(([, val]) => typeof val == 'string'),
54
+ wrapKey = namedRemap && entries.length > 1 ? entries.at(-1)[0] : null,
55
+ valMap = wrapKey ? Object.fromEntries(entries.slice(0, -1)) : mapping
56
+ if (namedRemap) {
57
+ if (typeof opts == 'string') { // bare old val → remap, optionally wrapped
58
+ if (valMap[opts]) deprecation = { old: opts, new: valMap[opts] }
59
+ const remapped = valMap[opts] ?? opts
60
+ newOpts = wrapKey ? { [wrapKey]: remapped } : remapped
61
+ } else if (wrapKey) { // object opts (or defaults) → remap inner val, warn if deprecated
62
+ const raw = isObj ? (opts[wrapKey] ?? mapping[wrapKey]) : mapping[wrapKey],
63
+ remapped = valMap[raw]
64
+ if (remapped) deprecation = { old: raw, new: remapped }
65
+ newOpts = { [wrapKey]: remapped ?? raw }
66
+ } else { // object opts → old key → new key
67
+ newOpts = { ...opts }
68
+ for (const [oldKey, newKey] of entries)
69
+ if (opts?.[oldKey] !== undefined) {
70
+ newOpts[newKey] = opts[oldKey] ; delete newOpts[oldKey] }
71
+ }
72
+ } else { // { new-opt: default } — positional wrap
73
+ const [newKey, defaultValue] = entries[0]
74
+ if (opts !== undefined && !isObj)
75
+ deprecation = { old: JSON.stringify(opts), new: `{ ${newKey}: ${JSON.stringify(opts)} }` }
76
+ newOpts = { [newKey]: isObj ? (opts[newKey] ?? defaultValue) : (opts ?? defaultValue) }
53
77
  }
54
- })
55
- return newRule.create(aliasContext)
78
+ }
79
+ const aliasContext = Object.create(context)
80
+ Object.defineProperty(aliasContext, 'options',
81
+ { value: [newOpts], writable: true, configurable: true, enumerable: true })
82
+ const visitors = { ...originals[targetName].create(aliasContext) }
83
+ if (deprecation) {
84
+ const ruleProgram = visitors.Program
85
+ visitors.Program = function(node) {
86
+ context.report({ node, messageId: 'deprecatedOption', data: deprecation })
87
+ if (ruleProgram) ruleProgram.call(this, node)
88
+ }
89
+ }
90
+ return visitors
56
91
  }))
57
92
  }
58
93
  }