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 +194 -30
- package/package.json +1 -1
- package/src/data/apis.js +6 -5
- package/src/data/deprecations.js +1 -1
- package/src/data/headers.js +2 -3
- package/src/index.js +17 -20
- package/src/rules/meta-order.js +76 -0
- package/src/rules/no-duplicate-keys.js +3 -3
- package/src/rules/no-invalid-headers.js +1 -2
- package/src/rules/no-missing-grants.js +1 -2
- package/src/rules/no-missing-resources.js +2 -2
- package/src/rules/require-blank-line-after-meta-block.js +1 -2
- package/src/rules/require-homepage-pair.js +6 -6
- package/src/rules/require-matching-localized-fields.js +6 -4
- package/src/rules/require-sri-hashing.js +57 -0
- package/src/rules/valid-grants.js +12 -7
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
|
-
|
|
6
|
-
[
|
|
7
|
-
|
|
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'
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
1144
|
+
<!-- eslint-plugin-constructors -->
|
|
972
1145
|
|
|
973
|
-
###
|
|
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
|
-
>
|
|
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
|
-
>
|
|
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
|
-
|
|
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
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
}
|
package/src/data/deprecations.js
CHANGED
package/src/data/headers.js
CHANGED
|
@@ -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'
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
'greasemonkey/no-
|
|
86
|
-
'greasemonkey/
|
|
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
|
-
|
|
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 (
|
|
28
|
+
if (seenEntries.has(entry.key))
|
|
29
29
|
context.report({ node: entry.comment, messageId: 'dupeKey', data: { key: entry.key }})
|
|
30
|
-
else
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
if (homepageEntry && !
|
|
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 (
|
|
31
|
+
else if (homepageURLentry && !homepageEntry)
|
|
32
32
|
context.report({
|
|
33
|
-
node:
|
|
33
|
+
node: homepageURLentry.comment, messageId: 'missingPair',
|
|
34
34
|
data: { missing: 'homepage', present: 'homepageURL' },
|
|
35
35
|
fix(fixer) {
|
|
36
|
-
return fixer.insertTextBefore(
|
|
37
|
-
`// @homepage ${
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
65
|
-
|
|
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
|
}
|