eslint-plugin-greasemonkey 1.6.0 → 1.8.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 +70 -43
- package/docs/LICENSE.md +1 -1
- package/package.json +4 -4
- package/src/data/apis.js +19 -0
- package/src/data/deprecations.js +2 -0
- package/src/data/headers.js +3 -2
- package/src/index.js +9 -2
- package/src/rules/meta-order.js +7 -17
- package/src/rules/meta-spacing.js +9 -18
- package/src/rules/no-invalid-grants.js +1 -1
- package/src/rules/prefer-match.js +1 -1
- package/src/rules/require-sri-hashing.js +28 -16
- package/src/utils.js +65 -30
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ ESLint rules for Greasemonkey userscripts.
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## Installation
|
|
19
|
+
## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/sharp-install-desktop.svg?color=%232b2b2a"> Installation
|
|
20
20
|
|
|
21
21
|
From your project root:
|
|
22
22
|
|
|
@@ -27,10 +27,8 @@ npm i -D eslint-plugin-greasemonkey
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
29
|
<h2 id="usage">
|
|
30
|
+
<img height=30 width=28 align="top" src="https://api.iconify.design/entypo/power-plug.svg?color=%232b2b2a">
|
|
30
31
|
Usage
|
|
31
|
-
<a title="Copy section URL" target="_blank"
|
|
32
|
-
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23usage">
|
|
33
|
-
<img height=17 width=22 alt="[Copy section URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="bottom"></a>
|
|
34
32
|
</h2>
|
|
35
33
|
|
|
36
34
|
Add a bundled config to your `eslint.config.*js`:
|
|
@@ -52,17 +50,15 @@ export default [
|
|
|
52
50
|
]
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
Available bundles: `recommended`, `strict`, `problem`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`, `adguard`
|
|
56
54
|
|
|
57
55
|
**Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
|
|
58
56
|
|
|
59
57
|
---
|
|
60
58
|
|
|
61
59
|
<h2 id="supported-rules">
|
|
60
|
+
<img height=30 width=27 align="top" src="https://api.iconify.design/mdi/format-list-checks.svg?color=%232b2b2a">
|
|
62
61
|
Supported rules
|
|
63
|
-
<a title="Copy section URL" target="_blank"
|
|
64
|
-
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23supported-rules">
|
|
65
|
-
<img height=17 width=22 alt="[Copy section URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="bottom"></a>
|
|
66
62
|
</h2>
|
|
67
63
|
|
|
68
64
|
<h3 id="meta-order">
|
|
@@ -70,7 +66,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
70
66
|
<a title="Only included in strict config" href="#usage">
|
|
71
67
|
<img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
|
|
72
68
|
<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-
|
|
69
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
74
70
|
<a title="Doesn't prevent bugs" href="#meta-order">
|
|
75
71
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
76
72
|
<a title="Accepts array w/ options" href="#usage">
|
|
@@ -86,7 +82,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
86
82
|
|
|
87
83
|
#### Options
|
|
88
84
|
|
|
89
|
-
- `order` (default `[]`) – metadata keys in custom order
|
|
85
|
+
- `order` (default `[]`) – metadata keys in custom order (e.g. `['license', 'namespace']`) to selectively override built-in order with
|
|
90
86
|
|
|
91
87
|
#### <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
88
|
|
|
@@ -97,7 +93,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
97
93
|
// @description A script
|
|
98
94
|
! // @namespace example.com
|
|
99
95
|
// @author John Doe
|
|
100
|
-
|
|
96
|
+
// @license MIT
|
|
101
97
|
// ==/UserScript==
|
|
102
98
|
```
|
|
103
99
|
|
|
@@ -110,10 +106,9 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
110
106
|
// @description A script
|
|
111
107
|
- // @namespace example.com
|
|
112
108
|
// @author John Doe
|
|
113
|
-
- // @license MIT
|
|
114
109
|
+ // @namespace example.com
|
|
115
110
|
+ // @version 1.0.0
|
|
116
|
-
|
|
111
|
+
// @license MIT
|
|
117
112
|
// ==/UserScript==
|
|
118
113
|
```
|
|
119
114
|
|
|
@@ -145,7 +140,7 @@ If you don't care about the order of metadata fields.
|
|
|
145
140
|
<a title="Included in recommended config" href="#usage">
|
|
146
141
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
147
142
|
<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-
|
|
143
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
149
144
|
<a title="Doesn't prevent bugs" href="#meta-spacing">
|
|
150
145
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
151
146
|
<a title="Accepts array w/ options" href="#usage">
|
|
@@ -280,6 +275,16 @@ If you intentionally repeat single-value keys.
|
|
|
280
275
|
<code>no-invalid-grants</code>
|
|
281
276
|
<a title="Included in recommended config" href="#usage">
|
|
282
277
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
278
|
+
<a title="Optimized in tampermonkey config" href="#usage">
|
|
279
|
+
<img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
|
|
280
|
+
<a title="Optimized in violentmonkey config" href="#usage">
|
|
281
|
+
<img height=18 alt="[Violentmonkey]" src="https://img.shields.io/badge/Violentmonkey-f01e07?style=flat-square" align="middle"></a>
|
|
282
|
+
<a title="Optimized in greasemonkey config" href="#usage">
|
|
283
|
+
<img height=18 alt="[Greasemonkey]" src="https://img.shields.io/badge/Greasemonkey-6c4d8c?style=flat-square" align="middle"></a>
|
|
284
|
+
<a title="Optimized in scriptcat config" href="#usage">
|
|
285
|
+
<img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
|
|
286
|
+
<a title="Optimized in adguard config" href="#usage">
|
|
287
|
+
<img height=18 alt="[AdGuard]" src="https://img.shields.io/badge/AdGuard-68bd72?style=flat-square" align="middle"></a>
|
|
283
288
|
<a title="Accepts array w/ options" href="#usage">
|
|
284
289
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
285
290
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -293,8 +298,8 @@ If you intentionally repeat single-value keys.
|
|
|
293
298
|
|
|
294
299
|
#### Options
|
|
295
300
|
|
|
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
|
|
301
|
+
- `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`, `'adguard'`), as a single string or array
|
|
302
|
+
- `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
303
|
|
|
299
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
|
|
300
305
|
|
|
@@ -363,6 +368,16 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
363
368
|
<code>no-invalid-headers</code>
|
|
364
369
|
<a title="Included in recommended config" href="#usage">
|
|
365
370
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
371
|
+
<a title="Optimized in tampermonkey config" href="#usage">
|
|
372
|
+
<img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
|
|
373
|
+
<a title="Optimized in violentmonkey config" href="#usage">
|
|
374
|
+
<img height=18 alt="[Violentmonkey]" src="https://img.shields.io/badge/Violentmonkey-f01e07?style=flat-square" align="middle"></a>
|
|
375
|
+
<a title="Optimized in greasemonkey config" href="#usage">
|
|
376
|
+
<img height=18 alt="[Greasemonkey]" src="https://img.shields.io/badge/Greasemonkey-6c4d8c?style=flat-square" align="middle"></a>
|
|
377
|
+
<a title="Optimized in scriptcat config" href="#usage">
|
|
378
|
+
<img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
|
|
379
|
+
<a title="Optimized in adguard config" href="#usage">
|
|
380
|
+
<img height=18 alt="[AdGuard]" src="https://img.shields.io/badge/AdGuard-68bd72?style=flat-square" align="middle"></a>
|
|
366
381
|
<a title="Accepts array w/ options" href="#usage">
|
|
367
382
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
368
383
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -376,7 +391,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
376
391
|
|
|
377
392
|
#### Options
|
|
378
393
|
|
|
379
|
-
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
|
|
394
|
+
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`, `'adguard'`), as a single string or array
|
|
380
395
|
- `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
|
|
381
396
|
|
|
382
397
|
#### <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 +402,8 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
387
402
|
// ==UserScript==
|
|
388
403
|
// @name My Script
|
|
389
404
|
! // @runin normal-tabs
|
|
405
|
+
! // @customURI https://example.com
|
|
390
406
|
! // @inject-into page
|
|
391
|
-
// @customURL https://example.com
|
|
392
407
|
// ==/UserScript==
|
|
393
408
|
```
|
|
394
409
|
|
|
@@ -401,8 +416,9 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
401
416
|
// @name My Script
|
|
402
417
|
- // @runin normal-tabs
|
|
403
418
|
+ // @run-in normal-tabs
|
|
419
|
+
- // @customURI https://example.com
|
|
420
|
+
+ // @customURL https://example.com
|
|
404
421
|
- // @inject-into page
|
|
405
|
-
// @customURL https://example.com
|
|
406
422
|
// ==/UserScript==
|
|
407
423
|
```
|
|
408
424
|
|
|
@@ -440,7 +456,7 @@ If your script relies heavily on custom headers and you'd rather not maintain `a
|
|
|
440
456
|
|
|
441
457
|
<details>
|
|
442
458
|
|
|
443
|
-
<summary>Flags API calls (e.g. <code>GM_setValue</code>, <code>
|
|
459
|
+
<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
460
|
|
|
445
461
|
#### <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
462
|
|
|
@@ -497,7 +513,7 @@ If you use APIs through aliases or wrappers that obscure the original call site.
|
|
|
497
513
|
|
|
498
514
|
<details>
|
|
499
515
|
|
|
500
|
-
<summary>Flags <code>@resource</code> references via <code>
|
|
516
|
+
<summary>Flags <code>@resource</code> references via <code>GM_getResource<Text|URL>()</code> w/o a matching <code>@resource</code> declaration.</summary>
|
|
501
517
|
|
|
502
518
|
#### <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
519
|
|
|
@@ -660,7 +676,7 @@ If you pass resource names through variables or aliases rather than use them lit
|
|
|
660
676
|
|
|
661
677
|
<details>
|
|
662
678
|
|
|
663
|
-
<summary>Prefers <code>@match</code> over <code>@include</code>
|
|
679
|
+
<summary>Prefers <code>@match</code> over <code>@include</code> for safer URL matching.</summary>
|
|
664
680
|
|
|
665
681
|
#### <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
682
|
|
|
@@ -689,7 +705,8 @@ If you rely on `@include` patterns that cannot be expressed using `@match`.
|
|
|
689
705
|
|
|
690
706
|
#### See also
|
|
691
707
|
|
|
692
|
-
- <a target="_blank" href="https://
|
|
708
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?locale=en&q=include#meta:match">Tampermonkey — `@match`</a>
|
|
709
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?locale=en&q=include#meta:include">Tampermonkey — `@include`</a>
|
|
693
710
|
|
|
694
711
|
<div align="center">
|
|
695
712
|
|
|
@@ -706,7 +723,7 @@ If you rely on `@include` patterns that cannot be expressed using `@match`.
|
|
|
706
723
|
<a title="Included in recommended config" href="#usage">
|
|
707
724
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
708
725
|
<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-
|
|
726
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
710
727
|
<a title="Doesn't prevent bugs" href="#require-blank-line-after-meta-block">
|
|
711
728
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
712
729
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -769,7 +786,7 @@ If you don't care about visual separation between metadata and code.
|
|
|
769
786
|
<a title="Only included in strict config" href="#usage">
|
|
770
787
|
<img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
|
|
771
788
|
<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-
|
|
789
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
773
790
|
<a title="Copy rule URL" target="_blank"
|
|
774
791
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-homepage-pair">
|
|
775
792
|
<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 +815,7 @@ If you don't care about visual separation between metadata and code.
|
|
|
798
815
|
|
|
799
816
|
#### When not to use it
|
|
800
817
|
|
|
801
|
-
If
|
|
818
|
+
If you don't care about ensuring homepage URL compatibility across different managers.
|
|
802
819
|
|
|
803
820
|
#### Introduced in
|
|
804
821
|
|
|
@@ -807,6 +824,8 @@ If your target manager does not fall back to `@namespace` when a homepage key is
|
|
|
807
824
|
#### See also
|
|
808
825
|
|
|
809
826
|
- [`require-update-download-pair`](#require-update-download-pair)
|
|
827
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=homepage#meta:homepage">Tampermonkey `@homepage`, `@homepageURL`, `@website`, `@source`</a>
|
|
828
|
+
- <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#homepage-homepageurl-website">ScriptCat `@homepage`, `@homepageURL`, `@website`</a>
|
|
810
829
|
|
|
811
830
|
<div align="center">
|
|
812
831
|
|
|
@@ -900,7 +919,7 @@ If you intentionally provide localized names without descriptions (or vice versa
|
|
|
900
919
|
|
|
901
920
|
<details>
|
|
902
921
|
|
|
903
|
-
<summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code> and at least one <code>@match</code> or <code>@include</code>
|
|
922
|
+
<summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code> and at least one <code>@match</code> or <code>@include</code>).</summary>
|
|
904
923
|
|
|
905
924
|
#### Options
|
|
906
925
|
|
|
@@ -932,11 +951,11 @@ If you intentionally provide localized names without descriptions (or vice versa
|
|
|
932
951
|
|
|
933
952
|
#### When not to use it
|
|
934
953
|
|
|
935
|
-
If
|
|
954
|
+
If your script is a work-in-progress, or metadata is injected at build time.
|
|
936
955
|
|
|
937
956
|
#### Introduced in
|
|
938
957
|
|
|
939
|
-
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
|
|
958
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
|
|
940
959
|
|
|
941
960
|
#### See also
|
|
942
961
|
|
|
@@ -958,7 +977,7 @@ If you use a subset of metadata fields that doesn't match the convention.
|
|
|
958
977
|
<a title="Included in recommended config" href="#usage">
|
|
959
978
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
960
979
|
<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-
|
|
980
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
|
|
962
981
|
<a title="Doesn't prevent bugs" href="#require-space-before-meta-key">
|
|
963
982
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
964
983
|
<a title="Copy rule URL" target="_blank"
|
|
@@ -1029,7 +1048,7 @@ If you intentionally use a different prefix style.
|
|
|
1029
1048
|
|
|
1030
1049
|
#### Options
|
|
1031
1050
|
|
|
1032
|
-
- `algorithm` (default `'sha256'`) – SRI hash algorithm to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`)
|
|
1051
|
+
- `algorithm` (default `'sha256'`) – SRI hash algorithm(s) to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`), as a single string or array
|
|
1033
1052
|
|
|
1034
1053
|
#### <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
1054
|
|
|
@@ -1069,7 +1088,8 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
|
|
|
1069
1088
|
|
|
1070
1089
|
- [`no-invalid-headers`](#no-invalid-headers)
|
|
1071
1090
|
- [`require-required-fields`](#require-required-fields)
|
|
1072
|
-
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey Subresource Integrity</a>
|
|
1091
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey — Subresource Integrity</a>
|
|
1092
|
+
- <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#resource-integrity-verification">ScriptCat — Resource Integrity Verification</a>
|
|
1073
1093
|
|
|
1074
1094
|
<div align="center">
|
|
1075
1095
|
|
|
@@ -1083,8 +1103,14 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
|
|
|
1083
1103
|
|
|
1084
1104
|
<h3 id="require-update-download-pair">
|
|
1085
1105
|
<code>require-update-download-pair</code>
|
|
1086
|
-
<a title="
|
|
1087
|
-
<img height=18 alt="[Strict
|
|
1106
|
+
<a title="Included in strict config" href="#usage">
|
|
1107
|
+
<img height=18 alt="[Strict]" src="https://img.shields.io/badge/Strict-ff8c42?style=flat-square" align="middle"></a>
|
|
1108
|
+
<a title="Included in tampermonkey config" href="#usage">
|
|
1109
|
+
<img height=18 alt="[Tampermonkey]" src="https://img.shields.io/badge/Tampermonkey-00485b?style=flat-square" align="middle"></a>
|
|
1110
|
+
<a title="Included in scriptcat config" href="#usage">
|
|
1111
|
+
<img height=18 alt="[ScriptCat]" src="https://img.shields.io/badge/ScriptCat-1296dc?style=flat-square" align="middle"></a>
|
|
1112
|
+
<a title="Included in adguard config" href="#usage">
|
|
1113
|
+
<img height=18 alt="[AdGuard]" src="https://img.shields.io/badge/AdGuard-68bd72?style=flat-square" align="middle"></a>
|
|
1088
1114
|
<a title="Copy rule URL" target="_blank"
|
|
1089
1115
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-update-download-pair">
|
|
1090
1116
|
<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 +1139,7 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
|
|
|
1113
1139
|
|
|
1114
1140
|
#### When not to use it
|
|
1115
1141
|
|
|
1116
|
-
If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the full script on every update check).
|
|
1142
|
+
If you don't target Tampermonkey or ScriptCat (where `@updateURL` avoids re-downloading the full script on every update check).
|
|
1117
1143
|
|
|
1118
1144
|
#### Introduced in
|
|
1119
1145
|
|
|
@@ -1121,8 +1147,9 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
|
|
|
1121
1147
|
|
|
1122
1148
|
#### See also
|
|
1123
1149
|
|
|
1124
|
-
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL"><code>@updateURL</code></a>
|
|
1125
1150
|
- [`require-homepage-pair`](#require-homepage-pair)
|
|
1151
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL">Tampermonkey — `@updateURL`</a>
|
|
1152
|
+
- <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#updateurl">ScriptCat — `@updateURL`</a>
|
|
1126
1153
|
|
|
1127
1154
|
<div align="center">
|
|
1128
1155
|
|
|
@@ -1246,7 +1273,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
|
|
|
1246
1273
|
|
|
1247
1274
|
---
|
|
1248
1275
|
|
|
1249
|
-
## Shield embed
|
|
1276
|
+
## <img height=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
|
|
1250
1277
|
|
|
1251
1278
|
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
|
|
1252
1279
|
<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>
|
|
@@ -1261,20 +1288,20 @@ If you use a custom versioning scheme or URLs that aren't standard.
|
|
|
1261
1288
|
#### Markdown
|
|
1262
1289
|
|
|
1263
1290
|
```md
|
|
1264
|
-
[](https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme)
|
|
1291
|
+
[![[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
1292
|
```
|
|
1266
1293
|
|
|
1267
1294
|
---
|
|
1268
1295
|
|
|
1269
|
-
## License
|
|
1296
|
+
## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/baseline-account-balance.svg?color=%232b2b2a"> License
|
|
1270
1297
|
|
|
1271
|
-
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-
|
|
1272
|
-
<img height=
|
|
1298
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md">
|
|
1299
|
+
<img height=85 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
|
|
1273
1300
|
<br><br>
|
|
1274
1301
|
|
|
1275
1302
|
<details>
|
|
1276
1303
|
|
|
1277
|
-
<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>
|
|
1304
|
+
<summary>Copyright © 2026 <a target="_blank" href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> & contributors 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>
|
|
1278
1305
|
|
|
1279
1306
|
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:
|
|
1280
1307
|
|
|
@@ -1286,7 +1313,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
1286
1313
|
|
|
1287
1314
|
---
|
|
1288
1315
|
|
|
1289
|
-
## Related
|
|
1316
|
+
## <img height=30 width=29 align="top" src="https://api.iconify.design/bxs/package.svg?color=%232b2b2a"> Related
|
|
1290
1317
|
|
|
1291
1318
|
<!-- eslint-plugin-constructors -->
|
|
1292
1319
|
|
package/docs/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🏛️ MIT License
|
|
2
2
|
|
|
3
|
-
**Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui).**
|
|
3
|
+
**Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui) & contributors.**
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-greasemonkey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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.
|
|
72
|
-
"eslint-plugin-constructors": "^1.
|
|
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.
|
|
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
|
}
|
package/src/data/deprecations.js
CHANGED
|
@@ -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 }}, // 2026-9-25 announced possible removal
|
|
5
|
+
'meta-order': { 'meta-order': { order: [] }}, // 2026-9-25 announced possible removal
|
|
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
|
}
|
package/src/data/headers.js
CHANGED
|
@@ -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', '
|
|
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
|
@@ -78,17 +78,24 @@ plugin.configs = {
|
|
|
78
78
|
strict: {
|
|
79
79
|
plugins: { greasemonkey: plugin },
|
|
80
80
|
rules: Object.fromEntries(Object.keys(plugin.rules).map(ruleId => [`greasemonkey/${ruleId}`, 'error']))
|
|
81
|
+
},
|
|
82
|
+
problem: {
|
|
83
|
+
plugins: { greasemonkey: plugin },
|
|
84
|
+
rules: Object.fromEntries(Object.entries(plugin.rules)
|
|
85
|
+
.filter(([, rule]) => rule.meta.type == 'problem')
|
|
86
|
+
.map(([id]) => [`greasemonkey/${id}`, 'error']))
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat'])
|
|
90
|
+
for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'])
|
|
85
91
|
plugin.configs[manager] = {
|
|
86
92
|
plugins: { greasemonkey: plugin },
|
|
87
93
|
rules: {
|
|
88
94
|
...plugin.configs.recommended.rules,
|
|
89
95
|
'greasemonkey/no-invalid-grants': ['error', { target: manager }],
|
|
90
96
|
'greasemonkey/no-invalid-headers': ['error', { target: manager }],
|
|
91
|
-
...(
|
|
97
|
+
...(['tampermonkey', 'scriptcat', 'adguard'].includes(manager) &&
|
|
98
|
+
{ 'greasemonkey/require-update-download-pair': 'error' })
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
package/src/rules/meta-order.js
CHANGED
|
@@ -35,31 +35,21 @@ export default {
|
|
|
35
35
|
},
|
|
36
36
|
fixable: 'code',
|
|
37
37
|
schema: [{
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
@@ -44,12 +34,13 @@ export default {
|
|
|
44
34
|
if (!metaEntries.length) return
|
|
45
35
|
let targetCol = metaEntries.reduce((max, entry) => Math.max(max, entry.key.length), 0) + minSpaces +4
|
|
46
36
|
for (const entry of metaEntries)
|
|
47
|
-
if (entry.key == 'resource') {
|
|
37
|
+
if (entry.key == 'resource' && entry.value.trim()) {
|
|
48
38
|
const name = entry.value.trim().split(re.whitespace)[0],
|
|
49
39
|
requiredCol = 4 + entry.key.length +1 + name.length + minSpaces
|
|
50
40
|
if (requiredCol > targetCol) targetCol = requiredCol
|
|
51
41
|
}
|
|
52
42
|
for (const entry of metaEntries) {
|
|
43
|
+
if (!entry.value.trim()) continue
|
|
53
44
|
const commentText = sourceCode.getText(entry.comment),
|
|
54
45
|
keyIdx = commentText.indexOf(entry.key) + entry.key.length,
|
|
55
46
|
parts = entry.value.trim().split(re.whitespace),
|
|
@@ -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 = {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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}}.
|
|
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
|
|
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
|
|
39
|
-
if (!
|
|
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
|
|
44
|
-
|
|
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: '
|
|
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)
|
|
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
|
|
23
|
-
: Array.isArray(alias) ? alias.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
...
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
}
|