eslint-plugin-greasemonkey 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,8 @@
6
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
7
  <a href="#license">
8
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
- <img src="https://img.shields.io/npm/dm/eslint-plugin-greasemonkey?style=for-the-badge&logo=npm&color=black">
9
+ <a>
10
+ <img src="https://img.shields.io/npm/dm/eslint-plugin-greasemonkey?style=for-the-badge&logo=npm&color=black"></a>
10
11
  <a target="_blank" href="https://eslint.org/">
11
12
  <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>
12
13
  <br><br>
@@ -51,7 +52,7 @@ export default [
51
52
  ]
52
53
  ```
53
54
 
54
- Available bundles: `recommended`, `strict`, `problem`, `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`, `adguard`
55
+ Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only), `tampermonkey`, `violentmonkey`, `greasemonkey`, `scriptcat`, `adguard`
55
56
 
56
57
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
57
58
 
@@ -1087,8 +1088,6 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1087
1088
 
1088
1089
  #### See also
1089
1090
 
1090
- - [`no-invalid-headers`](#no-invalid-headers)
1091
- - [`require-required-fields`](#require-required-fields)
1092
1091
  - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey — Subresource Integrity</a>
1093
1092
  - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#resource-integrity-verification">ScriptCat — Resource Integrity Verification</a>
1094
1093
 
@@ -1140,7 +1139,7 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1140
1139
 
1141
1140
  #### When not to use it
1142
1141
 
1143
- If you don't target Tampermonkey, ScriptCat or AdGuard (where `@updateURL` avoids re-downloading the full script on every update check).
1142
+ If you don't strictly target Tampermonkey, ScriptCat or AdGuard (where `@updateURL` avoids re-downloading the full script on every update check).
1144
1143
 
1145
1144
  #### Introduced in
1146
1145
 
@@ -1148,7 +1147,6 @@ If you don't target Tampermonkey, ScriptCat or AdGuard (where `@updateURL` avoid
1148
1147
 
1149
1148
  #### See also
1150
1149
 
1151
- - [`require-homepage-pair`](#require-homepage-pair)
1152
1150
  - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL">Tampermonkey — `@updateURL`</a>
1153
1151
  - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#updateurl">ScriptCat — `@updateURL`</a>
1154
1152
 
@@ -1262,7 +1260,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
1262
1260
  #### See also
1263
1261
 
1264
1262
  - [`no-invalid-grants`](#no-invalid-grants)
1265
- - [`no-missing-resources`](#no-missing-resources)
1263
+ - [`no-invalid-headers`](#no-invalid-headers)
1266
1264
 
1267
1265
  <div align="center">
1268
1266
 
@@ -1274,7 +1272,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
1274
1272
 
1275
1273
  ---
1276
1274
 
1277
- ## <img height=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
1275
+ ## <img width=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
1278
1276
 
1279
1277
  <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1280
1278
  <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>
@@ -1282,7 +1280,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
1282
1280
  #### HTML
1283
1281
 
1284
1282
  ```html
1285
- <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1283
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1286
1284
  <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>
1287
1285
  ```
1288
1286
 
@@ -1316,6 +1314,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1316
1314
 
1317
1315
  ## <img height=30 width=29 align="top" src="https://api.iconify.design/bxs/package.svg?color=%232b2b2a"> Related
1318
1316
 
1317
+ <!-- eslint-plugin-chrome-extensions -->
1318
+
1319
+ ### <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#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-chrome-extensions</a>
1320
+
1321
+ <details>
1322
+
1323
+ <summary>ESLint rules for Chrome extensions.</summary><br>
1324
+
1325
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme"><img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/0b5e3e49/assets/images/screenshots/vs-code/squiggles.png"></a>
1326
+
1327
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#installation"><b>Install</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">Readme</a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#supported-rules">Supported rules</a>
1328
+
1329
+ </details>
1330
+
1319
1331
  <!-- eslint-plugin-constructors -->
1320
1332
 
1321
1333
  ### <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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-greasemonkey",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
5
5
  "type": "module",
6
6
  "engines": {
@@ -68,11 +68,11 @@
68
68
  "@eslint/json": "^2.1.0",
69
69
  "@eslint/markdown": "^8.0.3",
70
70
  "@stylistic/eslint-plugin": "^5.10.0",
71
- "eslint": "^10.11.0",
72
- "eslint-plugin-constructors": "^1.4.0",
71
+ "eslint": "^10.12.0",
72
+ "eslint-plugin-constructors": "^1.4.1",
73
73
  "eslint-plugin-eslint-plugin": "^7.6.2",
74
74
  "eslint-plugin-import-x": "^4.17.1",
75
- "eslint-plugin-n": "^18.4.0",
75
+ "eslint-plugin-n": "^18.4.1",
76
76
  "eslint-plugin-no-secrets": "^2.3.3",
77
77
  "eslint-plugin-no-unsanitized": "^4.1.5",
78
78
  "eslint-plugin-promise": "^7.3.0",
package/src/index.js CHANGED
@@ -22,7 +22,7 @@ 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'
25
+ import { bundleRules, initDeprecatedAlias } from './utils.js'
26
26
 
27
27
  const plugin = {
28
28
  rules: {
@@ -57,33 +57,15 @@ for (const entry of Object.entries(oldRules))
57
57
  plugin.configs = {
58
58
  recommended: {
59
59
  plugins: { greasemonkey: plugin },
60
- rules: {
61
- 'greasemonkey/meta-spacing': 'error',
62
- 'greasemonkey/no-duplicate-keys': 'error',
63
- 'greasemonkey/no-invalid-grants': 'error',
64
- 'greasemonkey/no-invalid-headers': 'error',
65
- 'greasemonkey/no-missing-grants': 'error',
66
- 'greasemonkey/no-unused-grants': 'error',
67
- 'greasemonkey/no-missing-resources': 'error',
68
- 'greasemonkey/no-unused-resources': 'error',
69
- 'greasemonkey/prefer-match': 'error',
70
- 'greasemonkey/require-blank-line-after-meta-block': 'error',
71
- 'greasemonkey/require-matching-localized-fields': 'error',
72
- 'greasemonkey/require-required-fields': 'error',
73
- 'greasemonkey/require-space-before-meta-key': 'error',
74
- 'greasemonkey/require-user-js-filename': 'error',
75
- 'greasemonkey/valid-field-values': 'error'
76
- }
60
+ rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'greasemonkey')
77
61
  },
78
62
  strict: {
79
63
  plugins: { greasemonkey: plugin },
80
- rules: Object.fromEntries(Object.keys(plugin.rules).map(ruleId => [`greasemonkey/${ruleId}`, 'error']))
64
+ rules: bundleRules(plugin.rules, () => true, 'greasemonkey')
81
65
  },
82
66
  problem: {
83
67
  plugins: { greasemonkey: plugin },
84
- rules: Object.fromEntries(Object.entries(plugin.rules)
85
- .filter(([, rule]) => rule.meta.type == 'problem')
86
- .map(([ruleId]) => [`greasemonkey/${ruleId}`, 'error']))
68
+ rules: bundleRules(plugin.rules, rule => rule.meta.type == 'problem', 'greasemonkey')
87
69
  }
88
70
  }
89
71
 
@@ -39,13 +39,13 @@ export default {
39
39
  properties: {
40
40
  order: {
41
41
  type: 'array', items: { type: 'string' },
42
- description: 'Metadata keys in preferred order; unspecified keys are ignored'
42
+ description: 'Metadata keys in preferred order'
43
43
  }
44
44
  },
45
45
  additionalProperties: false
46
46
  }],
47
47
  defaultOptions: [{ order: [] }],
48
- messages: { wrongOrder: '@{{first}} should appear before @{{second}}' }
48
+ messages: { wrongOrder: '@{{first}} should appear before @{{second}}.' }
49
49
  },
50
50
 
51
51
  create(context) {
@@ -22,7 +22,7 @@ export default {
22
22
  additionalProperties: false
23
23
  }],
24
24
  defaultOptions: [{ spaces: 3 }],
25
- messages: { misaligned: 'Metadata values should be aligned at column {{column}}' }
25
+ messages: { misaligned: 'Metadata values should be aligned at column {{column}}.' }
26
26
  },
27
27
 
28
28
  create(context) {
@@ -14,7 +14,7 @@ export default {
14
14
  recommended: true
15
15
  },
16
16
  schema: [],
17
- messages: { dupeKey: 'Duplicate @{{key}}' }
17
+ messages: { dupeKey: 'Duplicate @{{key}}.' }
18
18
  },
19
19
 
20
20
  create(context) {
@@ -1,6 +1,7 @@
1
- import { extractMetadata } from '../utils.js'
2
1
  import { allAPIs, supportsAPI } from '../data/apis.js'
3
2
 
3
+ import { extractMetadata } from '../utils.js'
4
+
4
5
  const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'],
5
6
  specialGrants = new Set(['none', 'unsafeWindow', 'window.close', 'window.focus', 'window.onurlchange']),
6
7
  allGrants = new Set([...allAPIs, ...specialGrants]),
@@ -37,7 +38,7 @@ export default {
37
38
  defaultOptions: [{}],
38
39
  messages: {
39
40
  invalidGrant: 'Invalid @grant: "{{grant}}"',
40
- unsupportedGrant: '"@{{grant}}" is not supported by {{target}} (only by {{supporting}})',
41
+ unsupportedGrant: '"@{{grant}}" is not supported by {{target}} (only by {{supporting}}).',
41
42
  wrongCase: 'Invalid @grant: "{{grant}}" (did you mean "{{suggestion}}"?)'
42
43
  }
43
44
  },
@@ -46,14 +47,15 @@ export default {
46
47
  const options = context.options[0] || {},
47
48
  rawTarget = options.target ?? 'all',
48
49
  targets = Array.isArray(rawTarget) ? rawTarget : [rawTarget],
49
- allowedGrants = [options.allowedGrants ?? []].flat()
50
+ rawAllowed = options.allowedGrants ?? [],
51
+ allowedGrants = new Set(Array.isArray(rawAllowed) ? rawAllowed : [rawAllowed])
50
52
  return {
51
53
  Program() {
52
54
  const metaEntries = extractMetadata(context.sourceCode),
53
55
  grantEntries = metaEntries.filter(entry => entry.key == 'grant')
54
56
  for (const grant of grantEntries) {
55
57
  const grantName = grant.value.trim()
56
- if (specialGrants.has(grantName) || allowedGrants.includes(grantName)) continue
58
+ if (specialGrants.has(grantName) || allowedGrants.has(grantName)) continue
57
59
  if (!allGrants.has(grantName)) {
58
60
  const suggestion = sortedGrants.get(grantName.toLowerCase())
59
61
  if (suggestion && suggestion != grantName)
@@ -1,6 +1,7 @@
1
- import { extractMetadata } from '../utils.js'
2
1
  import { managerHeaders, managers, localizedHeaders, validHeaders } from '../data/headers.js'
3
2
 
3
+ import { extractMetadata } from '../utils.js'
4
+
4
5
  function parseAllowedPattern(entry) {
5
6
  if (typeof entry != 'string' || !entry.startsWith('/')) return null
6
7
  const lastSlash = entry.lastIndexOf('/')
@@ -37,8 +38,8 @@ export default {
37
38
  }],
38
39
  defaultOptions: [{ allowedHeaders: [] }],
39
40
  messages: {
40
- invalidHeader: 'Invalid header: "@{{header}}"',
41
- unsupportedHeader: '"@{{header}}" is not supported by {{target}} (only by {{supporting}})'
41
+ invalidHeader: 'Invalid header: "@{{header}}".',
42
+ unsupportedHeader: '"@{{header}}" is not supported by {{target}} (only by {{supporting}}).'
42
43
  }
43
44
  },
44
45
 
@@ -1,6 +1,7 @@
1
- import { extractMetadata } from '../utils.js'
2
1
  import { gmLegacyAPIs, gmModernAPIs, scAPIs } from '../data/apis.js'
3
2
 
3
+ import { extractMetadata } from '../utils.js'
4
+
4
5
  const apis = new Set([...gmLegacyAPIs, ...gmModernAPIs, ...scAPIs]),
5
6
  grantlessAPIs = new Set(['GM_info', 'GM.info']) // always available, no grant required
6
7
 
@@ -13,7 +14,7 @@ export default {
13
14
  recommended: true
14
15
  },
15
16
  schema: [],
16
- messages: { missingGrant: 'Missing @grant: "{{api}}"' }
17
+ messages: { missingGrant: 'Missing @grant: "{{api}}".' }
17
18
  },
18
19
 
19
20
  create(context) {
@@ -13,8 +13,8 @@ export default {
13
13
  },
14
14
  schema: [],
15
15
  messages: {
16
- missingResource: 'Missing @resource: "{{name}}"',
17
- noMatchingResource: 'No declared @resource matches `{{pattern}}`'
16
+ missingResource: 'Missing @resource: "{{name}}".',
17
+ noMatchingResource: 'No declared @resource matches `{{pattern}}`.'
18
18
  }
19
19
  },
20
20
 
@@ -9,7 +9,7 @@ export default {
9
9
  recommended: true
10
10
  },
11
11
  schema: [],
12
- messages: { preferMatch: 'Use @match instead of @include' }
12
+ messages: { preferMatch: 'Use @match instead of @include.' }
13
13
  },
14
14
 
15
15
  create(context) {
@@ -8,7 +8,7 @@ export default {
8
8
  },
9
9
  fixable: 'whitespace',
10
10
  schema: [],
11
- messages: { missingBlankLine: 'Missing blank line after metadata block' }
11
+ messages: { missingBlankLine: 'Missing blank line after metadata block.' }
12
12
  },
13
13
 
14
14
  create(context) {
@@ -10,7 +10,7 @@ export default {
10
10
  },
11
11
  fixable: 'code',
12
12
  schema: [],
13
- messages: { missingPair: 'Missing @{{missing}} (required when @{{present}} is used)' }
13
+ messages: { missingPair: 'Missing @{{missing}} (required when @{{present}} is used).' }
14
14
  },
15
15
 
16
16
  create(context) {
@@ -4,7 +4,7 @@ export default {
4
4
  meta: {
5
5
  type: 'problem',
6
6
  docs: {
7
- description: 'Require matching localized name and description fields',
7
+ description: 'Require matching localized @name and @description fields',
8
8
  category: 'Possible Errors',
9
9
  recommended: true
10
10
  },
@@ -22,12 +22,13 @@ export default {
22
22
  additionalProperties: false
23
23
  }],
24
24
  defaultOptions: [{}],
25
- messages: { missingPair: 'Missing matching @{{missing}} for @{{present}}' }
25
+ messages: { missingPair: 'Missing matching @{{missing}} for @{{present}}.' }
26
26
  },
27
27
 
28
28
  create(context) {
29
29
  const options = context.options[0] || {},
30
- ignoreLanguages = [options.ignoreLanguages ?? []].flat()
30
+ rawLangs = options.ignoreLanguages ?? [],
31
+ ignoreLangs = new Set(Array.isArray(rawLangs) ? rawLangs : [rawLangs])
31
32
  return {
32
33
  Program() {
33
34
  const metaEntries = extractMetadata(context.sourceCode),
@@ -36,14 +37,14 @@ export default {
36
37
  nameLangs = new Set(nameEntries.map(entry => entry.key.split(':')[1])),
37
38
  descLangs = new Set(descEntries.map(entry => entry.key.split(':')[1]))
38
39
  for (const lang of nameLangs)
39
- if (!ignoreLanguages.includes(lang) && !descLangs.has(lang))
40
+ if (!ignoreLangs.has(lang) && !descLangs.has(lang))
40
41
  context.report({
41
42
  node: nameEntries.find(entry => entry.key == `name:${lang}`).comment,
42
43
  messageId: 'missingPair',
43
44
  data: { missing: `description:${lang}`, present: `name:${lang}` }
44
45
  })
45
46
  for (const lang of descLangs)
46
- if (!ignoreLanguages.includes(lang) && !nameLangs.has(lang))
47
+ if (!ignoreLangs.has(lang) && !nameLangs.has(lang))
47
48
  context.report({
48
49
  node: descEntries.find(entry => entry.key == `description:${lang}`).comment,
49
50
  messageId: 'missingPair',
@@ -24,7 +24,7 @@ export default {
24
24
  additionalProperties: false
25
25
  }],
26
26
  defaultOptions: [{}],
27
- messages: { missingField: 'Missing required metadata field: @{{field}}' }
27
+ messages: { missingField: 'Missing required metadata field: @{{field}}.' }
28
28
  },
29
29
 
30
30
  create(context) {
@@ -12,7 +12,7 @@ export default {
12
12
  },
13
13
  fixable: 'whitespace',
14
14
  schema: [],
15
- messages: { badPrefix: '`{{actual}}` should be `// @{{key}}`' }
15
+ messages: { badPrefix: '`{{actual}}` should be `// @{{key}}`.' }
16
16
  },
17
17
 
18
18
  create(context) {
@@ -9,7 +9,7 @@ export default {
9
9
  recommended: false
10
10
  },
11
11
  schema: [],
12
- messages: { missingPair: 'Missing @{{missing}} (required when @{{present}} is used)' }
12
+ messages: { missingPair: 'Missing @{{missing}} (required when @{{present}} is used).' }
13
13
  },
14
14
 
15
15
  create(context) {
@@ -9,7 +9,7 @@ export default {
9
9
  recommended: true
10
10
  },
11
11
  schema: [],
12
- messages: { requireUserJS: 'Userscript filename should end in ".user.js"' }
12
+ messages: { requireUserJS: 'Userscript filename should end in ".user.js".' }
13
13
  },
14
14
 
15
15
  create(context) {
@@ -42,17 +42,17 @@ export default {
42
42
  }],
43
43
  defaultOptions: [{}],
44
44
  messages: {
45
- invalidVersion: 'Invalid @version: must be a valid {{versionType}} (expected format: {{example}})',
46
- futureVersion: 'Invalid @version: date cannot be in the future',
47
- invalidURL: 'Invalid URL for @{{field}}',
48
- invalidGrant: 'Unknown @grant: "{{grant}}"'
45
+ invalidVersion: 'Invalid @version: must be a valid {{versionType}} (expected format: {{example}}).',
46
+ futureVersion: 'Invalid @version: date cannot be in the future.',
47
+ invalidURL: 'Invalid URL for @{{field}}.',
48
+ invalidGrant: 'Unknown @grant: "{{grant}}".'
49
49
  }
50
50
  },
51
51
 
52
52
  create(context) {
53
53
  const options = context.options[0] || {},
54
54
  versionType = options.versionType || 'semver',
55
- allowedGrants = options.allowedGrants || [],
55
+ allowedGrants = new Set(options.allowedGrants || []),
56
56
  allowFutureDate = options.allowFutureDate ?? false,
57
57
  timezone = options.timezone,
58
58
  example = versionType == 'semver' ? '1.2.3' : 'YYYY.M.D or YYYY.MM.DD'
@@ -66,7 +66,7 @@ export default {
66
66
  Program() {
67
67
  const metaEntries = extractMetadata(context.sourceCode)
68
68
  for (const entry of metaEntries) {
69
- if (entry.key == 'grant' && allowedGrants.length && !allowedGrants.includes(entry.value))
69
+ if (entry.key == 'grant' && allowedGrants.size && !allowedGrants.has(entry.value))
70
70
  context.report({ node: entry.comment, messageId: 'invalidGrant', data: { grant: entry.value }})
71
71
  else if (entry.key == 'resource') {
72
72
  const url = entry.value.trim().split(re.whitespace)[1]
package/src/utils.js CHANGED
@@ -1,5 +1,11 @@
1
1
  const re = { whitespace: /\s/ }
2
2
 
3
+ export function bundleRules(rules, predicate, pluginName) {
4
+ return Object.fromEntries(Object.entries(rules)
5
+ .filter(([, rule]) => predicate(rule))
6
+ .map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
7
+ }
8
+
3
9
  export function extractMetadata(sourceCode) {
4
10
  const comments = sourceCode.getAllComments(),
5
11
  metaEntries = []