eslint-plugin-greasemonkey 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -31
- 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
|
|
|
@@ -35,7 +38,7 @@ export default [
|
|
|
35
38
|
{
|
|
36
39
|
files: ['**/*.user.js'], plugins: { greasemonkey }
|
|
37
40
|
rules: {
|
|
38
|
-
...greasemonkey.configs.recommended.rules,
|
|
41
|
+
...greasemonkey.configs.recommended.rules,
|
|
39
42
|
'greasemonkey/no-unused-resources': 'off', // selectively disable rule
|
|
40
43
|
'greasemonkey/meta-spacing': ['error', 2] // selectively tweak rule
|
|
41
44
|
}
|
|
@@ -43,6 +46,8 @@ export default [
|
|
|
43
46
|
]
|
|
44
47
|
```
|
|
45
48
|
|
|
49
|
+
Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`
|
|
50
|
+
|
|
46
51
|
**Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
|
|
47
52
|
|
|
48
53
|
---
|
|
@@ -51,6 +56,7 @@ export default [
|
|
|
51
56
|
|
|
52
57
|
| ID | Description | Auto-fixable | Recommended |
|
|
53
58
|
| --- | --- | :-: | :-: |
|
|
59
|
+
| [`meta-order`](#meta-order) | Enforces a preferred order for metadata keys | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> |
|
|
54
60
|
| [`meta-spacing`](#meta-spacing) | Aligns metadata values to the column after the longest key | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
55
61
|
| [`no-duplicate-keys`](#no-duplicate-keys) | Disallows duplicate metadata keys | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
56
62
|
| [`no-invalid-headers`](#no-invalid-headers) | Disallows invalid or unsupported metadata headers | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
@@ -64,6 +70,7 @@ export default [
|
|
|
64
70
|
| [`require-matching-localized-fields`](#require-matching-localized-fields) | Requires matching localized `@name:xx` / `@description:xx` pairs | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
65
71
|
| [`require-required-fields`](#require-required-fields) | Requires essential metadata fields | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
66
72
|
| [`require-space-before-meta-key`](#require-space-before-meta-key) | Requires exactly one space between `//` and metadata keys | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
73
|
+
| [`require-sri-hashing`](#require-sri-hashing) | Requires SRI hashes for external `@require` URLs | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
67
74
|
| [`require-update-download-pair`](#require-update-download-pair) | Requires `@updateURL` and `@downloadURL` to be used together | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> |
|
|
68
75
|
| [`require-user-js-filename`](#require-user-js-filename) | Requires userscript filenames end in `.user.js` | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
69
76
|
| [`valid-field-values`](#valid-field-values) | Validates `@version` and URL fields | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
|
|
@@ -71,6 +78,95 @@ export default [
|
|
|
71
78
|
|
|
72
79
|
---
|
|
73
80
|
|
|
81
|
+
<h3 id="meta-order">
|
|
82
|
+
<code>meta-order</code>
|
|
83
|
+
<a title="Only included in strict config" href="#usage">
|
|
84
|
+
<img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
|
|
85
|
+
<a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
|
|
86
|
+
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
|
|
87
|
+
<a title="Doesn't catch bugs" href="#meta-order">
|
|
88
|
+
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
89
|
+
<a title="Accepts array w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
|
|
90
|
+
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
91
|
+
<a title="Copy rule URL" target="_blank"
|
|
92
|
+
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23meta-order">
|
|
93
|
+
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
94
|
+
</h3>
|
|
95
|
+
|
|
96
|
+
> Enforces a preferred order for metadata keys.
|
|
97
|
+
|
|
98
|
+
#### Options
|
|
99
|
+
|
|
100
|
+
- `string[]` (default `[]`) – metadata keys in custom order
|
|
101
|
+
|
|
102
|
+
For example:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
/* eslint greasemonkey/meta-order: ['error', ['run-in', 'inject-into']] */
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
109
|
+
|
|
110
|
+
```diff
|
|
111
|
+
// ==UserScript==
|
|
112
|
+
// @name My Script
|
|
113
|
+
! // @version 1.0.0
|
|
114
|
+
// @description A script
|
|
115
|
+
! // @namespace example.com
|
|
116
|
+
// @author John Doe
|
|
117
|
+
! // @license MIT
|
|
118
|
+
// @icon https://example.com/icon.png
|
|
119
|
+
! // @match https://example.com/*
|
|
120
|
+
// @grant GM_setValue
|
|
121
|
+
// @require https://example.com/lib.js
|
|
122
|
+
// @resource logo https://example.com/logo.png
|
|
123
|
+
! // @connect example.com
|
|
124
|
+
// ==/UserScript==
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
|
|
128
|
+
|
|
129
|
+
```diff
|
|
130
|
+
// ==UserScript==
|
|
131
|
+
// @name My Script
|
|
132
|
+
- // @version 1.0.0
|
|
133
|
+
// @description A script
|
|
134
|
+
- // @namespace example.com
|
|
135
|
+
// @author John Doe
|
|
136
|
+
- // @license MIT
|
|
137
|
+
+ // @namespace example.com
|
|
138
|
+
+ // @version 1.0.0
|
|
139
|
+
+ // @license MIT
|
|
140
|
+
// @icon https://example.com/icon.png
|
|
141
|
+
- // @match https://example.com/*
|
|
142
|
+
+ // @connect example.com
|
|
143
|
+
// @grant GM_setValue
|
|
144
|
+
- // @require https://example.com/lib.js
|
|
145
|
+
- // @resource logo https://example.com/logo.png
|
|
146
|
+
- // @connect example.com
|
|
147
|
+
+ // @match https://example.com/*
|
|
148
|
+
+ // @require https://example.com/lib.js
|
|
149
|
+
+ // @resource logo https://example.com/logo.png
|
|
150
|
+
// ==/UserScript==
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### When not to use it
|
|
154
|
+
|
|
155
|
+
If you don't care about the order of metadata fields.
|
|
156
|
+
|
|
157
|
+
#### See also
|
|
158
|
+
|
|
159
|
+
- [`meta-spacing`](#meta-spacing)
|
|
160
|
+
- [`no-invalid-headers`](#no-invalid-headers)
|
|
161
|
+
|
|
162
|
+
<div align="center">
|
|
163
|
+
|
|
164
|
+
[Back to index ↑][epg-rules]
|
|
165
|
+
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
74
170
|
<h3 id="meta-spacing">
|
|
75
171
|
<code>meta-spacing</code>
|
|
76
172
|
<a title="Included in recommended config" href="#usage">
|
|
@@ -204,8 +300,8 @@ If you intentionally repeat single-value keys.
|
|
|
204
300
|
|
|
205
301
|
#### Options
|
|
206
302
|
|
|
207
|
-
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'
|
|
208
|
-
- `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes) as a single value or array
|
|
303
|
+
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`), as a single string or array
|
|
304
|
+
- `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
|
|
209
305
|
|
|
210
306
|
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
211
307
|
|
|
@@ -236,7 +332,7 @@ If you intentionally repeat single-value keys.
|
|
|
236
332
|
|
|
237
333
|
#### When not to use it
|
|
238
334
|
|
|
239
|
-
If your script relies
|
|
335
|
+
If your script relies heavily on custom headers and you'd rather not maintain `allowedHeaders`.
|
|
240
336
|
|
|
241
337
|
#### See also
|
|
242
338
|
|
|
@@ -425,7 +521,7 @@ If you access the granted API through an alias or computed property rather than
|
|
|
425
521
|
|
|
426
522
|
#### When not to use it
|
|
427
523
|
|
|
428
|
-
If you pass
|
|
524
|
+
If you pass resource names through variables or aliases rather than use them literally.
|
|
429
525
|
|
|
430
526
|
#### See also
|
|
431
527
|
|
|
@@ -448,7 +544,7 @@ If you pass the resource name through a variable or alias rather than using it l
|
|
|
448
544
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
449
545
|
</h3>
|
|
450
546
|
|
|
451
|
-
> Prefers `@match` over `@include`
|
|
547
|
+
> Prefers `@match` over `@include` to align w/ <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/userScripts/">`chrome.userScripts`</a> syntax.
|
|
452
548
|
|
|
453
549
|
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
454
550
|
|
|
@@ -469,7 +565,11 @@ If you pass the resource name through a variable or alias rather than using it l
|
|
|
469
565
|
|
|
470
566
|
#### When not to use it
|
|
471
567
|
|
|
472
|
-
If you
|
|
568
|
+
If you rely on `@include` patterns that cannot be expressed using `@match`.
|
|
569
|
+
|
|
570
|
+
#### See also
|
|
571
|
+
|
|
572
|
+
- <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome match patterns</a>
|
|
473
573
|
|
|
474
574
|
<div align="center">
|
|
475
575
|
|
|
@@ -590,7 +690,7 @@ If your target manager does not fall back to `@namespace` when a homepage key is
|
|
|
590
690
|
|
|
591
691
|
#### Options
|
|
592
692
|
|
|
593
|
-
- `ignoreLanguages` (default `[]`) – language
|
|
693
|
+
- `ignoreLanguages` (default `[]`) – language code(s) to skip (e.g. `'de'` or `['es', 'fr']`)
|
|
594
694
|
|
|
595
695
|
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
596
696
|
|
|
@@ -743,6 +843,67 @@ If you intentionally use a different prefix style.
|
|
|
743
843
|
|
|
744
844
|
---
|
|
745
845
|
|
|
846
|
+
<h3 id="require-sri-hashing">
|
|
847
|
+
<code>require-sri-hashing</code>
|
|
848
|
+
<a title="Only included in strict config" href="#usage">
|
|
849
|
+
<img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
|
|
850
|
+
<a title="Doesn't catch bugs" href="#require-sri-hashing">
|
|
851
|
+
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
852
|
+
<a title="Accepts object w/ options" target="_blank" href="https://eslint.org/docs/latest/extend/custom-rules#option-defaults">
|
|
853
|
+
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
854
|
+
<a title="Copy rule URL" target="_blank"
|
|
855
|
+
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-sri-hashing">
|
|
856
|
+
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
857
|
+
</h3>
|
|
858
|
+
|
|
859
|
+
> Requires SRI hashes on `@require` and `@resource` URLs.
|
|
860
|
+
|
|
861
|
+
#### Options
|
|
862
|
+
|
|
863
|
+
- `algorithm` (default `'sha256'`) – SRI hash algorithm to enforce (`'md5'`, `'sha1'`, `'sha256'`, `'sha384'`, `'sha512'`)
|
|
864
|
+
|
|
865
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
866
|
+
|
|
867
|
+
```diff
|
|
868
|
+
/* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
|
|
869
|
+
|
|
870
|
+
// ==UserScript==
|
|
871
|
+
! // @require https://example.com/library.js#sha256-BASE64HASH
|
|
872
|
+
! // @resource logo https://example.com/logo.png#sha256-BASE64HASH
|
|
873
|
+
// ==/UserScript==
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
|
|
877
|
+
|
|
878
|
+
```diff
|
|
879
|
+
/* eslint greasemonkey/require-sri-hashing: ['error', { algorithm: 'sha384' }] */
|
|
880
|
+
|
|
881
|
+
// ==UserScript==
|
|
882
|
+
- // @require https://example.com/library.js#sha256-BASE64HASH
|
|
883
|
+
- // @resource logo https://example.com/logo.png#sha256-BASE64HASH
|
|
884
|
+
+ // @require https://example.com/library.js#sha384-BASE64HASH
|
|
885
|
+
+ // @resource logo https://example.com/logo.png#sha384-BASE64HASH
|
|
886
|
+
// ==/UserScript==
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
#### When not to use it
|
|
890
|
+
|
|
891
|
+
If you prefer shorter URLs, or don't support users who enforce subresource integrity validation via manager settings.
|
|
892
|
+
|
|
893
|
+
#### See also
|
|
894
|
+
|
|
895
|
+
- [`no-invalid-headers`](#no-invalid-headers)
|
|
896
|
+
- [`require-required-fields`](#require-required-fields)
|
|
897
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey Subresource Integrity</a>
|
|
898
|
+
|
|
899
|
+
<div align="center">
|
|
900
|
+
|
|
901
|
+
[Back to index ↑][epg-rules]
|
|
902
|
+
|
|
903
|
+
</div>
|
|
904
|
+
|
|
905
|
+
----
|
|
906
|
+
|
|
746
907
|
<h3 id="require-update-download-pair">
|
|
747
908
|
<code>require-update-download-pair</code>
|
|
748
909
|
<a title="Only included in strict config" href="#usage">
|
|
@@ -777,7 +938,7 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
|
|
|
777
938
|
|
|
778
939
|
#### See also
|
|
779
940
|
|
|
780
|
-
-
|
|
941
|
+
- <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL"><code>@updateURL</code></a>
|
|
781
942
|
- [`require-homepage-pair`](#require-homepage-pair)
|
|
782
943
|
|
|
783
944
|
<div align="center">
|
|
@@ -799,6 +960,19 @@ If you don't target Tampermonkey (where `@updateURL` avoids re-downloading the f
|
|
|
799
960
|
|
|
800
961
|
> Requires that files containing a userscript metadata block end in `.user.js`.
|
|
801
962
|
|
|
963
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect filename
|
|
964
|
+
|
|
965
|
+
```diff
|
|
966
|
+
! /my-userscript.js
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct filename
|
|
970
|
+
|
|
971
|
+
```diff
|
|
972
|
+
- /my-userscript.js
|
|
973
|
+
+ /my-userscript.user.js
|
|
974
|
+
```
|
|
975
|
+
|
|
802
976
|
#### When not to use it
|
|
803
977
|
|
|
804
978
|
If you intentionally name your userscripts something other than `*.user.js`.
|
|
@@ -884,8 +1058,8 @@ If you use a custom versioning scheme or URLs that aren't standard.
|
|
|
884
1058
|
|
|
885
1059
|
#### Options
|
|
886
1060
|
|
|
887
|
-
- `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`) as a single string or array
|
|
888
|
-
- `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat)
|
|
1061
|
+
- `target` (default `'all'`) – manager(s) whose grants to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
|
|
1062
|
+
- `allowedGrants` (default `[]`) – additional `@grant` values to allow beyond the built-in list (covering Tampermonkey, Violentmonkey and ScriptCat), as a single string or array
|
|
889
1063
|
|
|
890
1064
|
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
891
1065
|
|
|
@@ -930,7 +1104,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
930
1104
|
|
|
931
1105
|
## Shield embed
|
|
932
1106
|
|
|
933
|
-
<a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
|
|
1107
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
|
|
934
1108
|
<img height=32 alt="[Linted by eslint-plugin-greasemonkey]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--greasemonkey-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
|
|
935
1109
|
|
|
936
1110
|
#### HTML
|
|
@@ -950,11 +1124,12 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
950
1124
|
|
|
951
1125
|
## License
|
|
952
1126
|
|
|
953
|
-
|
|
1127
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
|
|
1128
|
+
<img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
|
|
954
1129
|
|
|
955
1130
|
<details>
|
|
956
1131
|
|
|
957
|
-
<summary>Copyright © 2026 <a href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
|
|
1132
|
+
<summary>Copyright © 2026 <a target="_blank" href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
|
|
958
1133
|
|
|
959
1134
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
960
1135
|
|
|
@@ -968,24 +1143,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
968
1143
|
|
|
969
1144
|
## Related
|
|
970
1145
|
|
|
971
|
-
|
|
1146
|
+
<!-- eslint-plugin-constructors -->
|
|
972
1147
|
|
|
973
|
-
###
|
|
1148
|
+
### <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img height=15 width=auto src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon16.png"> eslint-plugin-constructors</a>
|
|
974
1149
|
|
|
975
1150
|
<details>
|
|
976
1151
|
|
|
977
1152
|
<summary>ESLint rules for JavaScript constructor usage.</summary><br>
|
|
978
1153
|
|
|
979
|
-
>
|
|
1154
|
+
> <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png"></a>
|
|
980
1155
|
|
|
981
|
-
>
|
|
1156
|
+
> <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#installation"><b>Install</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme">Readme</a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#supported-rules">Supported rules</a>
|
|
982
1157
|
|
|
983
1158
|
</details>
|
|
984
1159
|
|
|
985
|
-
[epc-install]: https://codeberg.org/adamlui/eslint-plugin-constructors/#installation
|
|
986
|
-
[epc-readme]: https://codeberg.org/adamlui/eslint-plugin-constructors/#readme
|
|
987
|
-
[epc-rules]: https://codeberg.org/adamlui/eslint-plugin-constructors/#supported-rules
|
|
988
|
-
|
|
989
1160
|
<!-- Footer -->
|
|
990
1161
|
|
|
991
1162
|
<img height=10 width="100%" src="https://cdn.staticdelivr.com/gl/adamlui/ai-web-extensions/d11d2ee/assets/images/separators/gradient-aqua.png">
|
|
@@ -993,15 +1164,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
993
1164
|
<div align="center">
|
|
994
1165
|
|
|
995
1166
|
<picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/white/icon16.svg"><img height=14 src="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/dark-gray/icon16.svg"></picture>
|
|
996
|
-
|
|
1167
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases"><b>Latest releases</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/issues/new">Report a bug</a> / <a target="_blank" href="https://npmx.dev/org/adamlui">More packages by author</a> / [Back to top ↑](#top)
|
|
997
1168
|
|
|
998
1169
|
</div>
|
|
999
1170
|
|
|
1000
1171
|
<!-- Links -->
|
|
1001
1172
|
|
|
1002
|
-
[adamlui-npmx]: https://npmx.dev/org/adamlui
|
|
1003
|
-
[epg-bugs]: https://codeberg.org/adamlui/eslint-plugin-greasemonkey/issues/new
|
|
1004
|
-
[epg-license]: https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md
|
|
1005
|
-
[epg-npm]: https://www.npmjs.com/package/eslint-plugin-greasemonkey
|
|
1006
|
-
[epg-releases]: https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases
|
|
1007
1173
|
[epg-rules]: #supported-rules
|
package/package.json
CHANGED
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
|
}
|