eslint-plugin-greasemonkey 1.8.2 → 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
@@ -52,7 +52,7 @@ export default [
52
52
  ]
53
53
  ```
54
54
 
55
- 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`
56
56
 
57
57
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
58
58
 
@@ -1088,8 +1088,6 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1088
1088
 
1089
1089
  #### See also
1090
1090
 
1091
- - [`no-invalid-headers`](#no-invalid-headers)
1092
- - [`require-required-fields`](#require-required-fields)
1093
1091
  - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=sri">Tampermonkey — Subresource Integrity</a>
1094
1092
  - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#resource-integrity-verification">ScriptCat — Resource Integrity Verification</a>
1095
1093
 
@@ -1141,7 +1139,7 @@ If you prefer shorter URLs, or don't support users who strictly enforce subresou
1141
1139
 
1142
1140
  #### When not to use it
1143
1141
 
1144
- 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).
1145
1143
 
1146
1144
  #### Introduced in
1147
1145
 
@@ -1149,7 +1147,6 @@ If you don't target Tampermonkey, ScriptCat or AdGuard (where `@updateURL` avoid
1149
1147
 
1150
1148
  #### See also
1151
1149
 
1152
- - [`require-homepage-pair`](#require-homepage-pair)
1153
1150
  - <a target="_blank" href="https://www.tampermonkey.net/documentation.php?q=update_url#meta:updateURL">Tampermonkey — `@updateURL`</a>
1154
1151
  - <a target="_blank" href="https://docs.scriptcat.org/en/docs/dev/meta/#updateurl">ScriptCat — `@updateURL`</a>
1155
1152
 
@@ -1263,7 +1260,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
1263
1260
  #### See also
1264
1261
 
1265
1262
  - [`no-invalid-grants`](#no-invalid-grants)
1266
- - [`no-missing-resources`](#no-missing-resources)
1263
+ - [`no-invalid-headers`](#no-invalid-headers)
1267
1264
 
1268
1265
  <div align="center">
1269
1266
 
@@ -1283,7 +1280,7 @@ If you use a custom versioning scheme or URLs that aren't standard.
1283
1280
  #### HTML
1284
1281
 
1285
1282
  ```html
1286
- <a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1283
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">
1287
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>
1288
1285
  ```
1289
1286
 
@@ -1317,6 +1314,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1317
1314
 
1318
1315
  ## <img height=30 width=29 align="top" src="https://api.iconify.design/bxs/package.svg?color=%232b2b2a"> Related
1319
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
+
1320
1331
  <!-- eslint-plugin-constructors -->
1321
1332
 
1322
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.2",
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 = []