eslint-plugin-greasemonkey 1.8.4 → 1.9.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/docs/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🏛️ MIT License
1
+ # MIT License
2
2
 
3
3
  **Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui) & contributors.**
4
4
 
package/package.json CHANGED
@@ -1,22 +1,26 @@
1
1
  {
2
2
  "name": "eslint-plugin-greasemonkey",
3
- "version": "1.8.4",
3
+ "version": "1.9.0",
4
4
  "description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
5
- "type": "module",
6
- "engines": {
7
- "node": ">=18.18.0"
8
- },
9
- "author": {
10
- "name": "Adam Lui",
11
- "email": "adam@kudoai.org",
12
- "url": "https://codeberg.org/adamlui"
13
- },
5
+ "keywords": [
6
+ "eslint",
7
+ "eslint-plugin",
8
+ "eslintplugin",
9
+ "greasemonkey",
10
+ "metadata",
11
+ "security",
12
+ "tampermonkey",
13
+ "userscript"
14
+ ],
14
15
  "homepage": "https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme",
15
16
  "bugs": {
16
17
  "url": "https://codeberg.org/adamlui/eslint-plugin-greasemonkey/issues",
17
18
  "email": "support@adamlui.com"
18
19
  },
19
- "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://codeberg.org/adamlui/eslint-plugin-greasemonkey.git"
23
+ },
20
24
  "funding": [
21
25
  {
22
26
  "type": "ko-fi",
@@ -27,39 +31,34 @@
27
31
  "url": "https://paypal.me/adamlui"
28
32
  }
29
33
  ],
30
- "main": "src/index.js",
34
+ "license": "MIT",
35
+ "author": {
36
+ "name": "Adam Lui",
37
+ "email": "adam@kudoai.org",
38
+ "url": "https://codeberg.org/adamlui"
39
+ },
40
+ "type": "module",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./src/index.d.ts",
44
+ "default": "./src/index.js"
45
+ }
46
+ },
47
+ "main": "./src/index.js",
48
+ "types": "./src/index.d.ts",
31
49
  "files": [
32
50
  "docs/",
33
51
  "src/"
34
52
  ],
35
- "types": "src/index.d.ts",
36
53
  "scripts": {
37
- "lint": "eslint . --cache && yamllint .",
38
- "lint:all": "eslint . && yamllint .",
39
- "lint:fix": "eslint . --fix --cache && yamllint .",
40
- "lint:fix-all": "eslint . --fix && yamllint .",
41
- "test": "node tests/run.js",
42
- "demo": "node demo/run.js",
43
- "bump:patch": "bash utils/bump.sh patch",
44
- "bump:minor": "bash utils/bump.sh minor",
45
54
  "bump:feat": "npm run bump:minor",
46
- "bump:major": "bash utils/bump.sh major"
47
- },
48
- "repository": {
49
- "type": "git",
50
- "url": "git+https://codeberg.org/adamlui/eslint-plugin-greasemonkey.git"
51
- },
52
- "keywords": [
53
- "eslint",
54
- "eslint-plugin",
55
- "eslintplugin",
56
- "greasemonkey",
57
- "metadata",
58
- "tampermonkey",
59
- "userscript"
60
- ],
61
- "peerDependencies": {
62
- "eslint": ">=9.0.0"
55
+ "bump:major": "bash utils/bump.sh major",
56
+ "bump:minor": "bash utils/bump.sh minor",
57
+ "bump:patch": "bash utils/bump.sh patch",
58
+ "demo": "eslint demo",
59
+ "lint": "pre-commit run",
60
+ "lint:all": "pre-commit run --all-files",
61
+ "test": "node tests/run"
63
62
  },
64
63
  "devDependencies": {
65
64
  "@e18e/eslint-plugin": "^0.8.1",
@@ -68,16 +67,23 @@
68
67
  "@eslint/markdown": "^7.5.1",
69
68
  "@stylistic/eslint-plugin": "^5.10.0",
70
69
  "eslint": "^10.12.0",
71
- "eslint-plugin-constructors": "^1.4.2",
70
+ "eslint-plugin-constructors": "^1.4.3",
72
71
  "eslint-plugin-eslint-plugin": "^7.6.2",
73
72
  "eslint-plugin-import-x": "^4.17.1",
74
73
  "eslint-plugin-n": "^18.4.1",
75
74
  "eslint-plugin-no-secrets": "^2.3.3",
76
75
  "eslint-plugin-no-unsanitized": "^4.1.5",
76
+ "eslint-plugin-package-json": "^1.10.1",
77
77
  "eslint-plugin-promise": "^7.3.0",
78
78
  "eslint-plugin-regexp": "^3.3.1",
79
79
  "eslint-plugin-security": "^4.2.0",
80
- "globals": "^17.13.0",
81
- "yamllint-js": "^0.2.4"
80
+ "eslint-plugin-woodpecker": "^1.0.1",
81
+ "globals": "^17.13.0"
82
+ },
83
+ "peerDependencies": {
84
+ "eslint": ">=9.0.0"
85
+ },
86
+ "engines": {
87
+ "node": ">=18.18.0"
82
88
  }
83
89
  }
@@ -34,3 +34,27 @@ for (const manager of managers) {
34
34
  .map(([header]) => header)
35
35
  validHeaders.set(manager, new Set([...universalHeaders, ...specific]))
36
36
  }
37
+
38
+ const urlHeaderTypes = {
39
+ connect: 'host-pattern',
40
+ contributionURL: 'url',
41
+ defaulticon: 'url',
42
+ downloadURL: 'url',
43
+ exclude: 'pattern',
44
+ 'exclude-match': 'pattern',
45
+ homepage: 'url',
46
+ homepageURL: 'url',
47
+ icon: 'url',
48
+ icon64: 'url',
49
+ include: 'pattern',
50
+ match: 'pattern',
51
+ require: 'url',
52
+ 'require-css': 'url',
53
+ resource: 'named-url',
54
+ source: 'url',
55
+ supportURL: 'url',
56
+ updateURL: 'url',
57
+ website: 'url'
58
+ }
59
+ const knownHeaders = new Set([...universalHeaders, ...managerHeaders.keys()])
60
+ export const urlHeaders = new Map(Object.entries(urlHeaderTypes).filter(([name]) => knownHeaders.has(name)))
package/src/index.d.ts CHANGED
@@ -1,6 +1,36 @@
1
+ type RuleSeverity = 0 | 1 | 2 | 'off' | 'warn' | 'error'
2
+ type RuleSetting = RuleSeverity | [RuleSeverity, ...unknown[]]
3
+
4
+ type ConfigName =
5
+ | 'recommended'
6
+ | 'strict'
7
+ | 'problem'
8
+ | 'tampermonkey'
9
+ | 'violentmonkey'
10
+ | 'greasemonkey'
11
+ | 'scriptcat'
12
+ | 'adguard'
13
+
14
+ interface PluginConfig {
15
+ plugins: Record<string, unknown>
16
+ rules: Record<string, RuleSetting>
17
+ }
18
+
19
+ interface PluginRule {
20
+ meta: {
21
+ type?: 'problem' | 'suggestion' | 'layout'
22
+ deprecated?: boolean | {
23
+ message?: string
24
+ replacedBy?: Array<{ rule: { name: string } }>
25
+ }
26
+ docs?: Record<string, unknown>
27
+ }
28
+ create: (context: unknown) => Record<string, (...args: unknown[]) => void>
29
+ }
30
+
1
31
  declare const plugin: {
2
- rules: Record<string, unknown>
3
- configs: { recommended: Record<string, unknown> }
32
+ rules: Record<string, PluginRule>
33
+ configs: Record<ConfigName, PluginConfig>
4
34
  }
5
35
 
6
- export default plugin
36
+ export { plugin as default, plugin as greasemonkey }
package/src/index.js CHANGED
@@ -1,84 +1,9 @@
1
- /* eslint-disable security/detect-object-injection -- false positives */
1
+ import init from './utils/init.js'
2
2
 
3
- import { rules as oldRules } from './data/deprecations.js'
3
+ const plugin = init.plugin()
4
4
 
5
- import metaOrder from './rules/meta-order.js'
6
- import metaSpacing from './rules/meta-spacing.js'
7
- import noDuplicateKeys from './rules/no-duplicate-keys.js'
8
- import noInvalidGrants from './rules/no-invalid-grants.js'
9
- import noInvalidHeaders from './rules/no-invalid-headers.js'
10
- import noMissingGrants from './rules/no-missing-grants.js'
11
- import noMissingResources from './rules/no-missing-resources.js'
12
- import noUnusedGrants from './rules/no-unused-grants.js'
13
- import noUnusedResources from './rules/no-unused-resources.js'
14
- import preferMatch from './rules/prefer-match.js'
15
- import requireBlankLineAfterMetaBlock from './rules/require-blank-line-after-meta-block.js'
16
- import requireHomepagePair from './rules/require-homepage-pair.js'
17
- import requireMatchingLocalizedFields from './rules/require-matching-localized-fields.js'
18
- import requireRequiredFields from './rules/require-required-fields.js'
19
- import requireSpaceBeforeMetaKey from './rules/require-space-before-meta-key.js'
20
- import requireSRIhashing from './rules/require-sri-hashing.js'
21
- import requireUpdateDownloadPair from './rules/require-update-download-pair.js'
22
- import requireUserJsFilename from './rules/require-user-js-filename.js'
23
- import validFieldValues from './rules/valid-field-values.js'
24
-
25
- import { bundleRules, initDeprecatedAlias } from './utils.js'
26
-
27
- const plugin = {
28
- rules: {
29
- 'meta-order': metaOrder,
30
- 'meta-spacing': metaSpacing,
31
- 'no-duplicate-keys': noDuplicateKeys,
32
- 'no-invalid-grants': noInvalidGrants,
33
- 'no-invalid-headers': noInvalidHeaders,
34
- 'no-missing-grants': noMissingGrants,
35
- 'no-unused-grants': noUnusedGrants,
36
- 'no-missing-resources': noMissingResources,
37
- 'no-unused-resources': noUnusedResources,
38
- 'prefer-match': preferMatch,
39
- 'require-blank-line-after-meta-block': requireBlankLineAfterMetaBlock,
40
- 'require-homepage-pair': requireHomepagePair,
41
- 'require-matching-localized-fields': requireMatchingLocalizedFields,
42
- 'require-required-fields': requireRequiredFields,
43
- 'require-space-before-meta-key': requireSpaceBeforeMetaKey,
44
- 'require-sri-hashing': requireSRIhashing,
45
- 'require-update-download-pair': requireUpdateDownloadPair,
46
- 'require-user-js-filename': requireUserJsFilename,
47
- 'valid-field-values': validFieldValues
48
- }
49
- }
50
-
51
- for (const [ruleId, rule] of Object.entries(plugin.rules))
52
- rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#${ruleId}`
53
-
54
- for (const entry of Object.entries(oldRules))
55
- initDeprecatedAlias(plugin, entry)
56
-
57
- plugin.configs = {
58
- recommended: {
59
- plugins: { greasemonkey: plugin },
60
- rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'greasemonkey')
61
- },
62
- strict: {
63
- plugins: { greasemonkey: plugin },
64
- rules: bundleRules(plugin.rules, () => true, 'greasemonkey')
65
- },
66
- problem: {
67
- plugins: { greasemonkey: plugin },
68
- rules: bundleRules(plugin.rules, rule => rule.meta.type == 'problem', 'greasemonkey')
69
- }
70
- }
71
-
72
- for (const manager of ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'])
73
- plugin.configs[manager] = {
74
- plugins: { greasemonkey: plugin },
75
- rules: {
76
- ...plugin.configs.recommended.rules,
77
- 'greasemonkey/no-invalid-grants': ['error', { target: manager }],
78
- 'greasemonkey/no-invalid-headers': ['error', { target: manager }],
79
- ...(['tampermonkey', 'scriptcat', 'adguard'].includes(manager) &&
80
- { 'greasemonkey/require-update-download-pair': 'error' })
81
- }
82
- }
5
+ init.rules(plugin)
6
+ init.deprecations(plugin)
7
+ init.configs(plugin)
83
8
 
84
9
  export { plugin as default, plugin as greasemonkey }
@@ -1,4 +1,4 @@
1
- import { extractMetadata } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
3
  const defaultOrder = [
4
4
  'name', 'description', 'author', 'namespace', 'version', 'license', 'icon', 'icon64',
@@ -30,8 +30,7 @@ export default {
30
30
  type: 'layout',
31
31
  docs: {
32
32
  description: 'Enforce an order for metadata keys',
33
- category: 'Stylistic Issues',
34
- recommended: false
33
+ category: 'Stylistic Issues'
35
34
  },
36
35
  fixable: 'code',
37
36
  schema: [{
@@ -54,7 +53,7 @@ export default {
54
53
  return {
55
54
  Program() {
56
55
  const sourceCode = context.sourceCode,
57
- entries = extractMetadata(sourceCode),
56
+ entries = metadata.extract(sourceCode),
58
57
  violation = findViolation(entries, master) ?? (custom.length && findViolation(entries, custom))
59
58
  if (!violation) return
60
59
  const [first, second] = violation
@@ -1,4 +1,4 @@
1
- import { extractMetadata } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
3
  const re = { leadingWhitespace: /^\s*/, whitespace: /\s+/ }
4
4
 
@@ -30,7 +30,7 @@ export default {
30
30
  return {
31
31
  Program() {
32
32
  const sourceCode = context.sourceCode,
33
- metaEntries = extractMetadata(sourceCode)
33
+ metaEntries = metadata.extract(sourceCode)
34
34
  if (!metaEntries.length) return
35
35
  let targetCol = metaEntries.reduce((max, entry) => Math.max(max, entry.key.length), 0) + minSpaces +4
36
36
  for (const entry of metaEntries)
@@ -1,4 +1,4 @@
1
- import { extractMetadata } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
3
  const multiValKeys = new Set([
4
4
  'compatible', 'connect', 'description', 'exclude', 'exclude-match', 'grant',
@@ -20,7 +20,7 @@ export default {
20
20
  create(context) {
21
21
  return {
22
22
  Program() {
23
- const metaEntries = extractMetadata(context.sourceCode),
23
+ const metaEntries = metadata.extract(context.sourceCode),
24
24
  seenEntries = new Set()
25
25
  for (const entry of metaEntries) {
26
26
  const baseKey = entry.key.split(':')[0]
@@ -0,0 +1,109 @@
1
+ import { urlHeaders } from '../data/headers.js'
2
+
3
+ const re = {
4
+ insecureURL: /^http:\/\/(?!localhost\b|127\.0\.0\.1\b|\[::1\])/i,
5
+ meta: {
6
+ end: /^\/\/\s*==\/UserScript==\s*$/,
7
+ line: /^\/\/\s*@([\w-]+)(?::[\w-]+)?\s+(\S.*)$/, // eslint-disable-line security/detect-unsafe-regex
8
+ start: /^\/\/\s*==UserScript==\s*$/
9
+ },
10
+ newline: /\r?\n/,
11
+ whitespace: /\s+/
12
+ }
13
+
14
+ export default {
15
+ meta: {
16
+ type: 'problem',
17
+ docs: {
18
+ description: 'Flags `fetch()`, `XMLHttpRequest.open()`, `GM_xmlhttpRequest()`, `GM.xmlHttpRequest()` calls and URL fields that use `http://`',
19
+ category: 'Possible Errors',
20
+ recommended: true,
21
+ security: true
22
+ },
23
+ schema: [],
24
+ messages: {
25
+ insecureFetch: '`fetch()` to "{{url}}" insecure. Use HTTPS or a relative path instead.',
26
+ insecureXHR: '`XMLHttpRequest.open()` to "{{url}}" insecure. Use HTTPS or a relative path instead.',
27
+ insecureGMXHR: '`{{callee}}()` to "{{url}}" insecure. Use HTTPS or a relative path instead.',
28
+ insecureMetadata: '`@{{key}}` URL "{{url}}" insecure. Use HTTPS instead.'
29
+ }
30
+ },
31
+
32
+ create(context) {
33
+ const xhrVars = new Set(),
34
+ sourceCode = context.sourceCode ?? context.getSourceCode()
35
+ function isInsecure(url) { return typeof url == 'string' && re.insecureURL.test(url) }
36
+ function reportMetadata() {
37
+ const lines = sourceCode.text.split(re.newline)
38
+ let inBlock = false
39
+ for (const [i, rawLine] of lines.entries()) {
40
+ const line = rawLine.trimEnd()
41
+ if (re.meta.start.test(line.trim())) { inBlock = true ; continue }
42
+ if (inBlock && re.meta.end.test(line.trim())) break
43
+ if (!inBlock) continue
44
+ const match = line.match(re.meta.line) ; if (!match) continue
45
+ const key = match[1]
46
+ const type = urlHeaders.get(key) ; if (!type) continue
47
+ const tokens = match[2].split(re.whitespace),
48
+ url = type == 'named-url' ? tokens[1] : tokens[0]
49
+ if (!url || !isInsecure(url)) continue
50
+ const col = line.lastIndexOf(url)
51
+ context.report({
52
+ loc: { start: { line: i +1, column: col },
53
+ end: { line: i +1, column: col + url.length }},
54
+ messageId: 'insecureMetadata', data: { key, url }
55
+ })
56
+ }
57
+ }
58
+ function reportGMXHR(node) {
59
+ const url = urlFromLiteral(node.arguments[0]) ?? urlFromOptions(node.arguments[0]) ; if (!url) return
60
+ const callee = node.callee.type == 'MemberExpression'
61
+ ? `${node.callee.object.name}.${node.callee.property.name}`
62
+ : node.callee.name
63
+ context.report({ node: node.arguments[0], messageId: 'insecureGMXHR', data: { callee, url }})
64
+ }
65
+ function urlFromLiteral(node) {
66
+ return (node?.type == 'Literal'
67
+ && typeof node.value == 'string'
68
+ && isInsecure(node.value)
69
+ ) ? node.value : null
70
+ }
71
+ function urlFromOptions(node) {
72
+ if (node?.type != 'ObjectExpression') return null
73
+ for (const prop of node.properties)
74
+ if (prop.type == 'Property' && (prop.key?.name == 'url' || prop.key?.value == 'url')) {
75
+ const val = prop.value
76
+ if (val?.type == 'Literal' && isInsecure(val.value)) return val.value
77
+ }
78
+ return null
79
+ }
80
+
81
+ return {
82
+ Program() { reportMetadata() },
83
+ VariableDeclarator(node) {
84
+ if (node.init?.type == 'NewExpression'
85
+ && node.init.callee?.name == 'XMLHttpRequest'
86
+ && node.id.type == 'Identifier'
87
+ ) xhrVars.add(node.id.name)
88
+ },
89
+ CallExpression(node) {
90
+ if (node.callee.type == 'Identifier' && node.callee.name == 'fetch') {
91
+ const url = urlFromLiteral(node.arguments[0])
92
+ if (url) context.report({ node: node.arguments[0], messageId: 'insecureFetch', data: { url }})
93
+ } else if (node.callee.type == 'MemberExpression'
94
+ && node.callee.property?.name == 'open'
95
+ && node.callee.object?.type == 'Identifier'
96
+ && xhrVars.has(node.callee.object.name)
97
+ ) { // xhr.open('GET', 'http://…')
98
+ const url = urlFromLiteral(node.arguments[1])
99
+ if (url) context.report({ node: node.arguments[1], messageId: 'insecureXHR', data: { url }})
100
+ } else if (node.callee.type == 'Identifier'
101
+ && node.callee.name == 'GM_xmlhttpRequest'
102
+ || node.callee.type == 'MemberExpression'
103
+ && node.callee.object?.name == 'GM'
104
+ && node.callee.property?.name == 'xmlHttpRequest'
105
+ ) reportGMXHR(node)
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,68 @@
1
+ const re = {
2
+ messageTargets: new Set(['window', 'self', 'unsafeWindow']),
3
+ postmsgValidation: /\b\w+\.(?:origin|source)\b/
4
+ }
5
+
6
+ export default {
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description: 'Requires origin/source validation + explicit `postMessage()` target',
11
+ category: 'Possible Errors',
12
+ recommended: true,
13
+ security: true
14
+ },
15
+ defaultOptions: [{ disallowWildcardTarget: true }],
16
+ schema: [{
17
+ type: 'object',
18
+ properties: {
19
+ disallowWildcardTarget: {
20
+ type: 'boolean',
21
+ description: 'Flag postMessage calls that use "*" as targetOrigin'
22
+ }
23
+ },
24
+ additionalProperties: false
25
+ }],
26
+ messages: {
27
+ unvalidatedPostMessage: '`{{target}}` message handler must validate `event.origin`/`event.source` '
28
+ + 'before processing messages.',
29
+ wildcardTarget: '`postMessage` to `"*"` leaks data to any origin. '
30
+ + 'Explicitly specify `targetOrigin` instead.'
31
+ }
32
+ },
33
+
34
+ create(context) {
35
+ const opts = context.options[0] || {},
36
+ disallowWildcard = opts.disallowWildcardTarget !== false,
37
+ sourceCode = context.sourceCode ?? context.getSourceCode()
38
+ function checkPostMessageHandler(node, handlerNode, target) {
39
+ if (!handlerNode) return
40
+ const body = sourceCode.getText(handlerNode)
41
+ if (re.postmsgValidation.test(body)) return
42
+ context.report({ node, messageId: 'unvalidatedPostMessage', data: { target }})
43
+ }
44
+ function isMessageTarget(obj) {
45
+ return obj?.type == 'Identifier' && re.messageTargets.has(obj.name)
46
+ }
47
+ return {
48
+ CallExpression(node) {
49
+ if (node.callee.type != 'MemberExpression') return
50
+ if (node.callee.property?.name == 'addEventListener'
51
+ && node.arguments[0]?.value == 'message'
52
+ && isMessageTarget(node.callee.object)
53
+ ) checkPostMessageHandler(node, node.arguments[1], node.callee.object.name)
54
+ if (disallowWildcard && node.callee.property?.name == 'postMessage') {
55
+ const target = node.arguments[1]
56
+ if (target?.type == 'Literal' && target.value == '*')
57
+ context.report({ node: target, messageId: 'wildcardTarget' })
58
+ }
59
+ },
60
+ AssignmentExpression(node) {
61
+ if (node.left.type == 'MemberExpression'
62
+ && node.left.property?.name == 'onmessage'
63
+ && isMessageTarget(node.left.object)
64
+ ) checkPostMessageHandler(node, node.right, node.left.object.name)
65
+ }
66
+ }
67
+ }
68
+ }
@@ -1,6 +1,6 @@
1
- import { allAPIs, supportsAPI } from '../data/apis.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
- import { extractMetadata } from '../utils.js'
3
+ import { allAPIs, supportsAPI } from '../data/apis.js'
4
4
 
5
5
  const managers = ['tampermonkey', 'violentmonkey', 'greasemonkey', 'scriptcat', 'adguard'],
6
6
  specialGrants = new Set(['none', 'unsafeWindow', 'window.close', 'window.focus', 'window.onurlchange']),
@@ -51,7 +51,7 @@ export default {
51
51
  allowedGrants = new Set(Array.isArray(rawAllowed) ? rawAllowed : [rawAllowed])
52
52
  return {
53
53
  Program() {
54
- const metaEntries = extractMetadata(context.sourceCode),
54
+ const metaEntries = metadata.extract(context.sourceCode),
55
55
  grantEntries = metaEntries.filter(entry => entry.key == 'grant')
56
56
  for (const grant of grantEntries) {
57
57
  const grantName = grant.value.trim()
@@ -1,6 +1,6 @@
1
- import { managerHeaders, managers, localizedHeaders, validHeaders } from '../data/headers.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
- import { extractMetadata } from '../utils.js'
3
+ import { managerHeaders, managers, localizedHeaders, validHeaders } from '../data/headers.js'
4
4
 
5
5
  function parseAllowedPattern(entry) {
6
6
  if (typeof entry != 'string' || !entry.startsWith('/')) return null
@@ -62,7 +62,7 @@ export default {
62
62
  }
63
63
  return {
64
64
  Program() {
65
- const metaEntries = extractMetadata(context.sourceCode)
65
+ const metaEntries = metadata.extract(context.sourceCode)
66
66
  for (const entry of metaEntries) {
67
67
  if (allowedExact.has(entry.key)) continue
68
68
  if (allowedRegexes.some(re => re.test(entry.key))) continue
@@ -1,6 +1,6 @@
1
- import { gmLegacyAPIs, gmModernAPIs, scAPIs } from '../data/apis.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
- import { extractMetadata } from '../utils.js'
3
+ import { gmLegacyAPIs, gmModernAPIs, scAPIs } from '../data/apis.js'
4
4
 
5
5
  const apis = new Set([...gmLegacyAPIs, ...gmModernAPIs, ...scAPIs]),
6
6
  grantlessAPIs = new Set(['GM_info', 'GM.info']) // always available, no grant required
@@ -37,7 +37,7 @@ export default {
37
37
  'Program:exit'() {
38
38
  if (!usedAPIs.size) return
39
39
  const declared = new Set(
40
- extractMetadata(context.sourceCode)
40
+ metadata.extract(context.sourceCode)
41
41
  .filter(entry => entry.key == 'grant')
42
42
  .map(entry => entry.value.trim())
43
43
  )
@@ -1,4 +1,5 @@
1
- import { extractMetadata, resolveTemplate } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
+ import template from '../utils/template.js'
2
3
 
3
4
  const resourceGetterNames = new Set(['GM_getResourceText', 'GM_getResourceURL']),
4
5
  re = { specialChars: /[.*+?^${}()|[\]\\]/g, whitespace: /\s+/ }
@@ -24,7 +25,7 @@ export default {
24
25
  if (arg?.type == 'Literal' && typeof arg.value == 'string')
25
26
  usedRefs.push({ names: [arg.value], pattern: null, node })
26
27
  else if (arg?.type == 'TemplateLiteral') {
27
- const resolved = resolveTemplate(arg)
28
+ const resolved = template.resolve(arg)
28
29
  if (resolved) usedRefs.push({ names: resolved, pattern: null, node })
29
30
  else usedRefs.push({
30
31
  names: null,
@@ -42,7 +43,7 @@ export default {
42
43
  'Program:exit'() {
43
44
  if (!usedRefs.length) return
44
45
  const declared = new Set(
45
- extractMetadata(context.sourceCode)
46
+ metadata.extract(context.sourceCode)
46
47
  .filter(entry => entry.key == 'resource')
47
48
  .map(entry => entry.value.trim().split(re.whitespace)[0])
48
49
  )
@@ -1,4 +1,4 @@
1
- import { extractMetadata } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
2
 
3
3
  export default {
4
4
  meta: {
@@ -30,7 +30,7 @@ export default {
30
30
  },
31
31
  'Program:exit'() {
32
32
  const sourceCode = context.sourceCode,
33
- metaEntries = extractMetadata(sourceCode),
33
+ metaEntries = metadata.extract(sourceCode),
34
34
  grants = metaEntries.filter(entry => entry.key == 'grant')
35
35
  if (!grants.length) return
36
36
  for (const grant of grants) {
@@ -1,7 +1,8 @@
1
- import { extractMetadata, resolveTemplate } from '../utils.js'
1
+ import metadata from '../utils/metadata.js'
2
+ import template from '../utils/template.js'
2
3
 
3
- const resourceGetterNames = new Set(['GM_getResourceText', 'GM_getResourceURL']),
4
- re = { specialChars: /[.*+?^${}()|[\]\\]/g, whitespace: /\s+/ }
4
+ const re = { specialChars: /[.*+?^${}()|[\]\\]/g, whitespace: /\s+/ },
5
+ resourceGetterNames = new Set(['GM_getResourceText', 'GM_getResourceURL'])
5
6
 
6
7
  export default {
7
8
  meta: {
@@ -25,7 +26,7 @@ export default {
25
26
  if (arg?.type == 'Literal' && typeof arg.value == 'string')
26
27
  usedResources.add(arg.value)
27
28
  else if (arg?.type == 'TemplateLiteral') {
28
- const resolved = resolveTemplate(arg)
29
+ const resolved = template.resolve(arg)
29
30
  if (resolved)
30
31
  for (const name of resolved) usedResources.add(name)
31
32
  else usedPatterns.push(new RegExp( // eslint-disable-line security/detect-non-literal-regexp
@@ -33,7 +34,7 @@ export default {
33
34
  }
34
35
  },
35
36
  'Program:exit'() {
36
- const metaEntries = extractMetadata(context.sourceCode),
37
+ const metaEntries = metadata.extract(context.sourceCode),
37
38
  resources = metaEntries.filter(entry => entry.key == 'resource')
38
39
  if (!resources.length) return
39
40
  for (const res of resources) {