eslint-plugin-greasemonkey 1.5.2 → 1.6.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 +28 -22
- package/package.json +2 -2
- package/src/data/deprecations.js +4 -2
- package/src/data/headers.js +12 -6
- package/src/index.js +6 -11
- package/src/rules/meta-order.js +19 -4
- package/src/rules/meta-spacing.js +21 -2
- package/src/rules/require-required-fields.js +7 -2
- package/src/rules/valid-field-values.js +23 -0
- package/src/utils.js +46 -6
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ export default [
|
|
|
46
46
|
rules: {
|
|
47
47
|
...greasemonkey.configs.recommended.rules,
|
|
48
48
|
'greasemonkey/no-unused-resources': 'off', // selectively disable rule
|
|
49
|
-
'greasemonkey/meta-spacing': ['error', 2]
|
|
49
|
+
'greasemonkey/meta-spacing': ['error', { spaces: 2 }] // selectively tweak rule
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
]
|
|
@@ -73,7 +73,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
73
73
|
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
|
|
74
74
|
<a title="Doesn't prevent bugs" href="#meta-order">
|
|
75
75
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
76
|
-
<a title="Accepts array w/ options"
|
|
76
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
77
77
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
78
78
|
<a title="Copy rule URL" target="_blank"
|
|
79
79
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23meta-order">
|
|
@@ -86,7 +86,7 @@ Avaialble bundles: `recommended`, `strict`, `tampermonkey`, `violentmonkey`, `gr
|
|
|
86
86
|
|
|
87
87
|
#### Options
|
|
88
88
|
|
|
89
|
-
- `
|
|
89
|
+
- `order` (default `[]`) – metadata keys in custom order
|
|
90
90
|
|
|
91
91
|
#### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
|
|
92
92
|
|
|
@@ -148,7 +148,7 @@ If you don't care about the order of metadata fields.
|
|
|
148
148
|
<img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
|
|
149
149
|
<a title="Doesn't prevent bugs" href="#meta-spacing">
|
|
150
150
|
<img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
|
|
151
|
-
<a title="Accepts array w/ options"
|
|
151
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
152
152
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
153
153
|
<a title="Copy rule URL" target="_blank"
|
|
154
154
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23meta-spacing">
|
|
@@ -161,12 +161,12 @@ If you don't care about the order of metadata fields.
|
|
|
161
161
|
|
|
162
162
|
#### Options
|
|
163
163
|
|
|
164
|
-
- `
|
|
164
|
+
- `spaces` (default `3`) – minimum spaces after the longest key
|
|
165
165
|
|
|
166
166
|
#### <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
|
|
167
167
|
|
|
168
168
|
```diff
|
|
169
|
-
/* eslint greasemonkey/meta-spacing: ['error', 1] */
|
|
169
|
+
/* eslint greasemonkey/meta-spacing: ['error', { spaces: 1 }] */
|
|
170
170
|
|
|
171
171
|
// ==UserScript==
|
|
172
172
|
! // @name My Script
|
|
@@ -178,7 +178,7 @@ If you don't care about the order of metadata fields.
|
|
|
178
178
|
#### <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
|
|
179
179
|
|
|
180
180
|
```diff
|
|
181
|
-
/* eslint greasemonkey/meta-spacing: ['error', 1] */
|
|
181
|
+
/* eslint greasemonkey/meta-spacing: ['error', { spaces: 1 }] */
|
|
182
182
|
|
|
183
183
|
// ==UserScript==
|
|
184
184
|
- // @name My Script
|
|
@@ -276,11 +276,11 @@ If you intentionally repeat single-value keys.
|
|
|
276
276
|
|
|
277
277
|
</details>
|
|
278
278
|
|
|
279
|
-
<h3 id="no-invalid-grants">
|
|
279
|
+
<h3 id="no-invalid-grants"><a deprecated id="valid-grants"></a>
|
|
280
280
|
<code>no-invalid-grants</code>
|
|
281
281
|
<a title="Included in recommended config" href="#usage">
|
|
282
282
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
283
|
-
<a title="Accepts array w/ options"
|
|
283
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
284
284
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
285
285
|
<a title="Copy rule URL" target="_blank"
|
|
286
286
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23no-invalid-grants">
|
|
@@ -363,7 +363,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
363
363
|
<code>no-invalid-headers</code>
|
|
364
364
|
<a title="Included in recommended config" href="#usage">
|
|
365
365
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
366
|
-
<a title="Accepts array w/ options"
|
|
366
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
367
367
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
368
368
|
<a title="Copy rule URL" target="_blank"
|
|
369
369
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23no-invalid-headers">
|
|
@@ -376,7 +376,7 @@ If you don't want `@grant` validation at all. For individual uncovered grants, p
|
|
|
376
376
|
|
|
377
377
|
#### Options
|
|
378
378
|
|
|
379
|
-
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`), as a single string or array
|
|
379
|
+
- `target` (default `'all'`) – manager(s) whose headers to validate against (`'all'`, `'tampermonkey'`, `'violentmonkey'`, `'greasemonkey'`, `'scriptcat'`), as a single string or array
|
|
380
380
|
- `allowedHeaders` (default `[]`) – additional headers to allow (strings or regexes), as a single value or array
|
|
381
381
|
|
|
382
382
|
#### <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
|
|
@@ -701,7 +701,7 @@ If you rely on `@include` patterns that cannot be expressed using `@match`.
|
|
|
701
701
|
|
|
702
702
|
</details>
|
|
703
703
|
|
|
704
|
-
<h3 id="require-blank-line-after-meta-block">
|
|
704
|
+
<h3 id="require-blank-line-after-meta-block"><a deprecated id="require-meta-newline"></a>
|
|
705
705
|
<code>require-blank-line-after-meta-block</code>
|
|
706
706
|
<a title="Included in recommended config" href="#usage">
|
|
707
707
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
@@ -822,7 +822,7 @@ If your target manager does not fall back to `@namespace` when a homepage key is
|
|
|
822
822
|
<code>require-matching-localized-fields</code>
|
|
823
823
|
<a title="Included in recommended config" href="#usage">
|
|
824
824
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
825
|
-
<a title="Accepts array w/ options"
|
|
825
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
826
826
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
827
827
|
<a title="Copy rule URL" target="_blank"
|
|
828
828
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-matching-localized-fields">
|
|
@@ -891,7 +891,7 @@ If you intentionally provide localized names without descriptions (or vice versa
|
|
|
891
891
|
<code>require-required-fields</code>
|
|
892
892
|
<a title="Included in recommended config" href="#usage">
|
|
893
893
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
894
|
-
<a title="Accepts array w/ options"
|
|
894
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
895
895
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
896
896
|
<a title="Copy rule URL" target="_blank"
|
|
897
897
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-required-fields">
|
|
@@ -900,16 +900,17 @@ If you intentionally provide localized names without descriptions (or vice versa
|
|
|
900
900
|
|
|
901
901
|
<details>
|
|
902
902
|
|
|
903
|
-
<summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code>
|
|
903
|
+
<summary>Requires essential metadata fields (<code>@name</code>, <code>@version</code> and at least one <code>@match</code> or <code>@include</code>by default).</summary>
|
|
904
904
|
|
|
905
905
|
#### Options
|
|
906
906
|
|
|
907
907
|
- `fields` (default `['name', 'version']`) – override the required field list
|
|
908
|
+
- `requireMatchOrInclude` (default `true`) – require `@match` or `@include`
|
|
908
909
|
|
|
909
910
|
#### <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
|
|
910
911
|
|
|
911
912
|
```diff
|
|
912
|
-
/* eslint greasemonkey/require-required-fields: ['error', { fields: ['name', 'version', '
|
|
913
|
+
/* eslint greasemonkey/require-required-fields: ['error', { fields: ['name', 'version', 'license'] }] */
|
|
913
914
|
|
|
914
915
|
// ==UserScript==
|
|
915
916
|
// @name My Script
|
|
@@ -919,11 +920,12 @@ If you intentionally provide localized names without descriptions (or vice versa
|
|
|
919
920
|
#### <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
|
|
920
921
|
|
|
921
922
|
```diff
|
|
922
|
-
/* eslint greasemonkey/require-required-fields: ['error', { fields: ['name', 'version', '
|
|
923
|
+
/* eslint greasemonkey/require-required-fields: ['error', { fields: ['name', 'version', 'license'] }] */
|
|
923
924
|
|
|
924
925
|
// ==UserScript==
|
|
925
926
|
// @name My Script
|
|
926
927
|
+ // @version 1.0.0
|
|
928
|
+
+ // @license MIT
|
|
927
929
|
+ // @match https://example.com/*
|
|
928
930
|
// ==/UserScript==
|
|
929
931
|
```
|
|
@@ -934,7 +936,7 @@ If you use a subset of metadata fields that doesn't match the convention.
|
|
|
934
936
|
|
|
935
937
|
#### Introduced in
|
|
936
938
|
|
|
937
|
-
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
|
|
939
|
+
<a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026) as `require-required-fields`
|
|
938
940
|
|
|
939
941
|
#### See also
|
|
940
942
|
|
|
@@ -1014,7 +1016,7 @@ If you intentionally use a different prefix style.
|
|
|
1014
1016
|
<code>require-sri-hashing</code>
|
|
1015
1017
|
<a title="Only included in strict config" href="#usage">
|
|
1016
1018
|
<img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
|
|
1017
|
-
<a title="Accepts array w/ options"
|
|
1019
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
1018
1020
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1019
1021
|
<a title="Copy rule URL" target="_blank"
|
|
1020
1022
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23require-sri-hashing">
|
|
@@ -1180,9 +1182,9 @@ If you intentionally name your userscripts something other than `*.user.js`.
|
|
|
1180
1182
|
<code>valid-field-values</code>
|
|
1181
1183
|
<a title="Included in recommended config" href="#usage">
|
|
1182
1184
|
<img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
|
|
1183
|
-
<a title="Accepts array w/ options"
|
|
1185
|
+
<a title="Accepts array w/ options" href="#usage">
|
|
1184
1186
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1185
|
-
<a title="Copy rule URL" target="_blank"
|
|
1187
|
+
<a title="Copy rule URL" target="_blank"
|
|
1186
1188
|
href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-greasemonkey%2F%23valid-field-values">
|
|
1187
1189
|
<img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
|
|
1188
1190
|
</h3>
|
|
@@ -1194,6 +1196,8 @@ If you intentionally name your userscripts something other than `*.user.js`.
|
|
|
1194
1196
|
#### Options
|
|
1195
1197
|
|
|
1196
1198
|
- `versionType` (default `'semver'`) – version format to enforce (`'semver'`, `'date'`)
|
|
1199
|
+
- `allowFutureDate` (default `false`) – allow `@version` dates later than the current date
|
|
1200
|
+
- `timezone` (default `''`) – <a target="_blank" href="https://data.iana.org/time-zones/tzdb/zone1970.tab">IANA timezone</a> used to determine the current date (when omitted, only year/month are enforced)
|
|
1197
1201
|
|
|
1198
1202
|
#### <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
|
|
1199
1203
|
|
|
@@ -1238,6 +1242,8 @@ If you use a custom versioning scheme or URLs that aren't standard.
|
|
|
1238
1242
|
|
|
1239
1243
|
</div>
|
|
1240
1244
|
|
|
1245
|
+
</details>
|
|
1246
|
+
|
|
1241
1247
|
---
|
|
1242
1248
|
|
|
1243
1249
|
## Shield embed
|
|
@@ -1303,7 +1309,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
1303
1309
|
<div align="center">
|
|
1304
1310
|
|
|
1305
1311
|
<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>
|
|
1306
|
-
<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://
|
|
1312
|
+
<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://www.npmjs.com/~adamlui">More packages by author</a> / [Back to top ↑](#top)
|
|
1307
1313
|
|
|
1308
1314
|
</div>
|
|
1309
1315
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-greasemonkey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"eslint": ">=9.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@e18e/eslint-plugin": "^0.8.
|
|
66
|
+
"@e18e/eslint-plugin": "^0.8.1",
|
|
67
67
|
"@eslint/js": "^10.0.1",
|
|
68
68
|
"@eslint/json": "^2.1.0",
|
|
69
69
|
"@eslint/markdown": "^8.0.3",
|
package/src/data/deprecations.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
// Delete a line to end support
|
|
2
|
+
|
|
1
3
|
export const rules = { // old: new
|
|
2
|
-
'
|
|
3
|
-
'
|
|
4
|
+
'valid-grants': 'no-invalid-grants', // 2026-9-16 announced removal @ v2
|
|
5
|
+
'require-meta-newline': 'require-blank-line-after-meta-block' // 2026-9-13 announced removal @ v2
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
export default { rules }
|
package/src/data/headers.js
CHANGED
|
@@ -6,17 +6,23 @@ export const universalHeaders = new Set([
|
|
|
6
6
|
])
|
|
7
7
|
|
|
8
8
|
export const managerHeaders = new Map([
|
|
9
|
-
['
|
|
10
|
-
['
|
|
11
|
-
['user-agent', ['tampermonkey']],
|
|
12
|
-
['run-in', ['tampermonkey']],
|
|
9
|
+
['run-in', ['tampermonkey', 'scriptcat']],
|
|
10
|
+
['nocompat', ['tampermonkey']],
|
|
13
11
|
['sandbox', ['tampermonkey']],
|
|
12
|
+
['user-agent', ['tampermonkey']],
|
|
14
13
|
['webRequest', ['tampermonkey']],
|
|
15
|
-
['
|
|
14
|
+
['inject-into', ['violentmonkey', 'scriptcat']],
|
|
15
|
+
['top-level-await', ['violentmonkey']],
|
|
16
|
+
['background', ['scriptcat']],
|
|
17
|
+
['crontab', ['scriptcat']],
|
|
18
|
+
['definition', ['scriptcat']],
|
|
19
|
+
['early-start', ['scriptcat']],
|
|
20
|
+
['require-css', ['scriptcat']],
|
|
21
|
+
['storageName', ['scriptcat']]
|
|
16
22
|
])
|
|
17
23
|
|
|
18
24
|
export const localizedHeaders = ['name', 'description', 'antifeature']
|
|
19
|
-
export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey']
|
|
25
|
+
export const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat']
|
|
20
26
|
export const validHeaders = new Map()
|
|
21
27
|
|
|
22
28
|
validHeaders.set('all', new Set([...universalHeaders, ...managerHeaders.keys()]))
|
package/src/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import requireUpdateDownloadPair from './rules/require-update-download-pair.js'
|
|
|
22
22
|
import requireUserJsFilename from './rules/require-user-js-filename.js'
|
|
23
23
|
import validFieldValues from './rules/valid-field-values.js'
|
|
24
24
|
|
|
25
|
+
import { initDeprecatedAlias } from './utils.js'
|
|
26
|
+
|
|
25
27
|
const plugin = {
|
|
26
28
|
rules: {
|
|
27
29
|
'meta-order': metaOrder,
|
|
@@ -49,16 +51,8 @@ const plugin = {
|
|
|
49
51
|
for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
50
52
|
rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#${ruleId}`
|
|
51
53
|
|
|
52
|
-
for (const
|
|
53
|
-
|
|
54
|
-
plugin.rules[oldName] = {
|
|
55
|
-
...newRule,
|
|
56
|
-
meta: {
|
|
57
|
-
...newRule.meta, docs: { ...newRule.meta.docs, recommended: false },
|
|
58
|
-
deprecated: { message: `Use \`${newName}\` instead.`, replacedBy: [{ rule: { name: newName }}] }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
54
|
+
for (const entry of Object.entries(oldRules))
|
|
55
|
+
initDeprecatedAlias(plugin, entry)
|
|
62
56
|
|
|
63
57
|
plugin.configs = {
|
|
64
58
|
recommended: {
|
|
@@ -93,7 +87,8 @@ for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptc
|
|
|
93
87
|
rules: {
|
|
94
88
|
...plugin.configs.recommended.rules,
|
|
95
89
|
'greasemonkey/no-invalid-grants': ['error', { target: manager }],
|
|
96
|
-
'greasemonkey/no-invalid-headers': ['error', { target: manager }]
|
|
90
|
+
'greasemonkey/no-invalid-headers': ['error', { target: manager }],
|
|
91
|
+
...(manager == 'tampermonkey' && { 'greasemonkey/require-update-download-pair': 'error' })
|
|
97
92
|
}
|
|
98
93
|
}
|
|
99
94
|
|
package/src/rules/meta-order.js
CHANGED
|
@@ -35,16 +35,31 @@ export default {
|
|
|
35
35
|
},
|
|
36
36
|
fixable: 'code',
|
|
37
37
|
schema: [{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
oneOf: [
|
|
39
|
+
{
|
|
40
|
+
type: 'array', items: { type: 'string' },
|
|
41
|
+
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
|
+
}
|
|
54
|
+
]
|
|
41
55
|
}],
|
|
42
56
|
defaultOptions: [[]],
|
|
43
57
|
messages: { wrongOrder: '@{{first}} should appear before @{{second}}' }
|
|
44
58
|
},
|
|
45
59
|
|
|
46
60
|
create(context) {
|
|
47
|
-
const
|
|
61
|
+
const option = context.options[0] ?? [],
|
|
62
|
+
custom = Array.isArray(option) ? option : option.order ?? [],
|
|
48
63
|
master = custom.length ? defaultOrder.filter(key => !custom.includes(key)) : defaultOrder
|
|
49
64
|
return {
|
|
50
65
|
Program() {
|
|
@@ -11,13 +11,32 @@ export default {
|
|
|
11
11
|
recommended: true
|
|
12
12
|
},
|
|
13
13
|
fixable: 'whitespace',
|
|
14
|
-
schema: [{
|
|
14
|
+
schema: [{
|
|
15
|
+
oneOf: [
|
|
16
|
+
{
|
|
17
|
+
type: 'number',
|
|
18
|
+
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
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}],
|
|
15
33
|
defaultOptions: [3],
|
|
16
34
|
messages: { misaligned: 'Metadata values should be aligned at column {{column}}' }
|
|
17
35
|
},
|
|
18
36
|
|
|
19
37
|
create(context) {
|
|
20
|
-
const
|
|
38
|
+
const option = context.options[0] ?? 3,
|
|
39
|
+
minSpaces = typeof option == 'number' ? option : option.spaces ?? 3
|
|
21
40
|
return {
|
|
22
41
|
Program() {
|
|
23
42
|
const sourceCode = context.sourceCode,
|
|
@@ -15,6 +15,10 @@ export default {
|
|
|
15
15
|
type: 'array',
|
|
16
16
|
items: { type: 'string' },
|
|
17
17
|
description: 'Required metadata fields (default: name, version)'
|
|
18
|
+
},
|
|
19
|
+
requireMatchOrInclude: {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
description: 'Require a match or include metadata field (default: true)'
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
additionalProperties: false
|
|
@@ -25,7 +29,8 @@ export default {
|
|
|
25
29
|
|
|
26
30
|
create(context) {
|
|
27
31
|
const options = context.options[0] || {},
|
|
28
|
-
requiredFields = options.fields || ['name', 'version']
|
|
32
|
+
requiredFields = options.fields || ['name', 'version'],
|
|
33
|
+
requireMatchOrInclude = options.requireMatchOrInclude ?? true
|
|
29
34
|
return {
|
|
30
35
|
Program(node) {
|
|
31
36
|
const metaEntries = extractMetadata(context.sourceCode),
|
|
@@ -33,7 +38,7 @@ export default {
|
|
|
33
38
|
for (const field of requiredFields)
|
|
34
39
|
if (!keys.has(field))
|
|
35
40
|
context.report({ node, messageId: 'missingField', data: { field }})
|
|
36
|
-
if (
|
|
41
|
+
if (requireMatchOrInclude && !['include', 'match'].some(field => keys.has(field)))
|
|
37
42
|
context.report({ node, messageId: 'missingField', data: { field: 'match or include' }})
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -28,6 +28,14 @@ export default {
|
|
|
28
28
|
type: 'array',
|
|
29
29
|
items: { type: 'string' },
|
|
30
30
|
description: 'Allowed @grant values to check against; if empty, no grant validation'
|
|
31
|
+
},
|
|
32
|
+
allowFutureDate: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
description: 'Allow @version dates later than the current date (default: false)'
|
|
35
|
+
},
|
|
36
|
+
timezone: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'IANA timezone for determining the current date (when omitted, only year and month are checked)'
|
|
31
39
|
}
|
|
32
40
|
},
|
|
33
41
|
additionalProperties: false
|
|
@@ -35,6 +43,7 @@ export default {
|
|
|
35
43
|
defaultOptions: [{}],
|
|
36
44
|
messages: {
|
|
37
45
|
invalidVersion: 'Invalid @version: must be a valid {{versionType}} (expected format: {{example}})',
|
|
46
|
+
futureVersion: 'Invalid @version: date cannot be in the future',
|
|
38
47
|
invalidURL: 'Invalid URL for @{{field}}',
|
|
39
48
|
invalidGrant: 'Unknown @grant: "{{grant}}"'
|
|
40
49
|
}
|
|
@@ -44,7 +53,15 @@ export default {
|
|
|
44
53
|
const options = context.options[0] || {},
|
|
45
54
|
versionType = options.versionType || 'semver',
|
|
46
55
|
allowedGrants = options.allowedGrants || [],
|
|
56
|
+
allowFutureDate = options.allowFutureDate ?? false,
|
|
57
|
+
timezone = options.timezone,
|
|
47
58
|
example = versionType == 'semver' ? '1.2.3' : 'YYYY.M.D or YYYY.MM.DD'
|
|
59
|
+
const now = timezone
|
|
60
|
+
? Object.fromEntries(new Intl.DateTimeFormat('en', {
|
|
61
|
+
timeZone: timezone, year: 'numeric', month: 'numeric', day: 'numeric'
|
|
62
|
+
}).formatToParts().filter(({ type }) => ['year', 'month', 'day'].includes(type))
|
|
63
|
+
.map(({ type, value }) => [type, Number(value)]))
|
|
64
|
+
: { year: new Date().getUTCFullYear(), month: new Date().getUTCMonth() +1 }
|
|
48
65
|
return {
|
|
49
66
|
Program() {
|
|
50
67
|
const metaEntries = extractMetadata(context.sourceCode)
|
|
@@ -66,9 +83,15 @@ export default {
|
|
|
66
83
|
const isValid = versionType == 'semver' ? re.semVer.test(entry.value)
|
|
67
84
|
: versionType == 'date' ? re.dateVer.test(entry.value)
|
|
68
85
|
: false
|
|
86
|
+
const [year, month, day] = versionType == 'date' ? entry.value.split('.').map(Number) : []
|
|
69
87
|
if (!isValid)
|
|
70
88
|
context.report({
|
|
71
89
|
node: entry.comment, messageId: 'invalidVersion', data: { versionType, example }})
|
|
90
|
+
else if (versionType == 'date' && !allowFutureDate && (
|
|
91
|
+
year > now.year || year == now.year && month > now.month
|
|
92
|
+
|| timezone && year == now.year && month == now.month && day > now.day
|
|
93
|
+
))
|
|
94
|
+
context.report({ node: entry.comment, messageId: 'futureVersion' })
|
|
72
95
|
}
|
|
73
96
|
}
|
|
74
97
|
}
|
package/src/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const re = { whitespace: /\s/ }
|
|
2
|
+
|
|
1
3
|
export function extractMetadata(sourceCode) {
|
|
2
4
|
const comments = sourceCode.getAllComments(),
|
|
3
5
|
metaEntries = []
|
|
@@ -7,17 +9,55 @@ export function extractMetadata(sourceCode) {
|
|
|
7
9
|
if (!inMetaBlock && text == '==UserScript==') inMetaBlock = true
|
|
8
10
|
else if (inMetaBlock && text == '==/UserScript==') break
|
|
9
11
|
else if (inMetaBlock && text.startsWith('@')) { // capture entry
|
|
10
|
-
const spaceIdx = text.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
metaEntries.push({ key, value, comment, line: comment.loc.start.line })
|
|
15
|
-
}
|
|
12
|
+
const spaceIdx = text.search(re.whitespace),
|
|
13
|
+
key = spaceIdx == -1 ? text.slice(1).trim() : text.slice(1, spaceIdx).trim(),
|
|
14
|
+
value = spaceIdx == -1 ? '' : text.slice(spaceIdx +1).trim()
|
|
15
|
+
if (key) metaEntries.push({ key, value, comment, line: comment.loc.start.line })
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
return metaEntries
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export function initDeprecatedAlias(plugin, [oldName, alias]) { /* eslint-disable security/detect-object-injection */
|
|
22
|
+
const entries = typeof alias == 'string' ? [[alias, null]] // old: new
|
|
23
|
+
: Array.isArray(alias) ? alias.flatMap(entry =>
|
|
24
|
+
typeof entry == 'string' ? [[entry, null]] : Object.entries(entry)) // old: [new, ...]
|
|
25
|
+
: Object.entries(alias) // old: { new: { old-option: new-option }}
|
|
26
|
+
const newNames = entries.filter(([name]) => name != oldName).map(([name]) => name),
|
|
27
|
+
newRule = plugin.rules[newNames[0] || oldName]
|
|
28
|
+
plugin.rules[oldName] = {
|
|
29
|
+
...newRule,
|
|
30
|
+
meta: {
|
|
31
|
+
...newRule.meta,
|
|
32
|
+
schema: typeof alias == 'string' ? newRule.meta.schema : [{ type: 'object' }],
|
|
33
|
+
docs: { ...newRule.meta.docs, recommended: false },
|
|
34
|
+
deprecated: {
|
|
35
|
+
message: `Use ${newNames.map(name => `\`${name}\``).join(', ')} instead.`,
|
|
36
|
+
replacedBy: newNames.map(name => ({ rule: { name }}))
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
create(context) {
|
|
40
|
+
const oldOptions = context.options[0]
|
|
41
|
+
return Object.assign({}, ...entries.map(([name, mapping]) => {
|
|
42
|
+
const newRule = plugin.rules[name]
|
|
43
|
+
const options = mapping ? {
|
|
44
|
+
...oldOptions,
|
|
45
|
+
...Object.fromEntries(Object.entries(mapping).map(([oldKey, newKey]) => // old: new
|
|
46
|
+
[newKey, oldOptions?.[oldKey] ?? (newKey == 'reverseDefault' ? true : newKey)]))
|
|
47
|
+
} : oldOptions
|
|
48
|
+
const aliasContext = new Proxy({}, {
|
|
49
|
+
get(_, property) {
|
|
50
|
+
if (property == 'options') return [options]
|
|
51
|
+
const val = context[property]
|
|
52
|
+
return typeof val == 'function' ? val.bind(context) : val
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
return newRule.create(aliasContext)
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} /* eslint-enable security/detect-object-injection */
|
|
60
|
+
|
|
21
61
|
export function resolveTemplate(node) {
|
|
22
62
|
if (node.type == 'ConditionalExpression') {
|
|
23
63
|
const branches = [node.consequent, node.alternate].map(resolveTemplate)
|