eslint-plugin-greasemonkey 1.0.0 → 1.2.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 +492 -37
- package/docs/LICENSE.md +9 -0
- package/docs/SECURITY.md +3 -0
- package/package.json +46 -12
- package/src/index.d.ts +6 -0
- package/src/index.js +42 -33
- package/src/rules/meta-spacing.js +51 -47
- package/src/rules/no-duplicate-keys.js +35 -0
- package/src/rules/no-unused-grants.js +33 -36
- package/src/rules/no-unused-resources.js +36 -40
- package/src/rules/require-matching-localized-fields.js +53 -53
- package/src/rules/require-required-fields.js +41 -41
- package/src/rules/require-user-js-filename.js +25 -0
- package/src/rules/valid-field-values.js +79 -60
- package/src/rules/valid-grants.js +79 -0
- package/src/utils.js +19 -19
package/src/index.js
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
import metaSpacing from './rules/meta-spacing.js'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
'no-
|
|
15
|
-
'no-unused-grants': noUnusedGrants
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
import metaSpacing from './rules/meta-spacing.js'
|
|
2
|
+
import noDuplicateKeys from './rules/no-duplicate-keys.js'
|
|
3
|
+
import noUnusedGrants from './rules/no-unused-grants.js'
|
|
4
|
+
import noUnusedResources from './rules/no-unused-resources.js'
|
|
5
|
+
import requireMatchingLocalizedFields from './rules/require-matching-localized-fields.js'
|
|
6
|
+
import requireRequiredFields from './rules/require-required-fields.js'
|
|
7
|
+
import requireUserJsFilename from './rules/require-user-js-filename.js'
|
|
8
|
+
import validFieldValues from './rules/valid-field-values.js'
|
|
9
|
+
import validGrants from './rules/valid-grants.js'
|
|
10
|
+
|
|
11
|
+
const plugin = {
|
|
12
|
+
rules: {
|
|
13
|
+
'meta-spacing': metaSpacing,
|
|
14
|
+
'no-duplicate-keys': noDuplicateKeys,
|
|
15
|
+
'no-unused-grants': noUnusedGrants,
|
|
16
|
+
'no-unused-resources': noUnusedResources,
|
|
17
|
+
'require-matching-localized-fields': requireMatchingLocalizedFields,
|
|
18
|
+
'require-required-fields': requireRequiredFields,
|
|
19
|
+
'require-user-js-filename': requireUserJsFilename,
|
|
20
|
+
'valid-field-values': validFieldValues,
|
|
21
|
+
'valid-grants': validGrants
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
plugin.configs = {
|
|
26
|
+
recommended: {
|
|
27
|
+
plugins: { greasemonkey: plugin },
|
|
28
|
+
rules: {
|
|
29
|
+
'greasemonkey/meta-spacing': 'error',
|
|
30
|
+
'greasemonkey/no-duplicate-keys': 'error',
|
|
31
|
+
'greasemonkey/no-unused-grants': 'error',
|
|
32
|
+
'greasemonkey/no-unused-resources': 'error',
|
|
33
|
+
'greasemonkey/require-matching-localized-fields': 'error',
|
|
34
|
+
'greasemonkey/require-required-fields': 'error',
|
|
35
|
+
'greasemonkey/require-user-js-filename': 'error',
|
|
36
|
+
'greasemonkey/valid-field-values': 'error',
|
|
37
|
+
'greasemonkey/valid-grants': 'error'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default plugin
|
|
@@ -1,47 +1,51 @@
|
|
|
1
|
-
import { extractMetadata } from '../utils.js'
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
meta: {
|
|
7
|
-
type: 'layout',
|
|
8
|
-
docs: {
|
|
9
|
-
description: 'Align metadata values to the column after the longest key',
|
|
10
|
-
category: 'Stylistic Issues',
|
|
11
|
-
recommended: true
|
|
12
|
-
},
|
|
13
|
-
fixable: 'whitespace',
|
|
14
|
-
schema: [{ type: 'number', description: 'Minimum spaces after the longest key (default 3)' }],
|
|
15
|
-
defaultOptions: [3],
|
|
16
|
-
messages: { misaligned: 'Metadata values should be aligned at column {{column}}' }
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
create(context) {
|
|
20
|
-
const minSpaces = context.options[0] ?? 3
|
|
21
|
-
return {
|
|
22
|
-
Program() {
|
|
23
|
-
const sourceCode = context.sourceCode,
|
|
24
|
-
metaEntries = extractMetadata(sourceCode)
|
|
25
|
-
if (!metaEntries.length) return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
const re = { leadingWhitespace: /^\s*/, whitespace: /\s+/ }
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'layout',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Align metadata values to the column after the longest key',
|
|
10
|
+
category: 'Stylistic Issues',
|
|
11
|
+
recommended: true
|
|
12
|
+
},
|
|
13
|
+
fixable: 'whitespace',
|
|
14
|
+
schema: [{ type: 'number', description: 'Minimum spaces after the longest key (default 3)' }],
|
|
15
|
+
defaultOptions: [3],
|
|
16
|
+
messages: { misaligned: 'Metadata values should be aligned at column {{column}}' }
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
create(context) {
|
|
20
|
+
const minSpaces = context.options[0] ?? 3
|
|
21
|
+
return {
|
|
22
|
+
Program() {
|
|
23
|
+
const sourceCode = context.sourceCode,
|
|
24
|
+
metaEntries = extractMetadata(sourceCode)
|
|
25
|
+
if (!metaEntries.length) return
|
|
26
|
+
let targetCol = metaEntries.reduce((max, entry) => Math.max(max, entry.key.length), 0) + minSpaces +4
|
|
27
|
+
for (const entry of metaEntries)
|
|
28
|
+
if (entry.key == 'resource') {
|
|
29
|
+
const name = entry.value.trim().split(re.whitespace)[0],
|
|
30
|
+
requiredCol = 4 + entry.key.length +1 + name.length + minSpaces
|
|
31
|
+
if (requiredCol > targetCol) targetCol = requiredCol
|
|
32
|
+
}
|
|
33
|
+
for (const entry of metaEntries) {
|
|
34
|
+
const commentText = sourceCode.getText(entry.comment),
|
|
35
|
+
keyIdx = commentText.indexOf(entry.key) + entry.key.length,
|
|
36
|
+
parts = entry.value.trim().split(re.whitespace),
|
|
37
|
+
named = entry.key == 'resource' && parts.length >= 2,
|
|
38
|
+
prefix = named ? ` ${parts[0]} ` : '',
|
|
39
|
+
suffix = named ? parts.slice(1).join(' ') : entry.value.trim(),
|
|
40
|
+
fixedText = commentText.slice(0, keyIdx) + prefix
|
|
41
|
+
+ ' '.repeat(targetCol - keyIdx - prefix.length) + suffix
|
|
42
|
+
if (commentText !== fixedText)
|
|
43
|
+
context.report({
|
|
44
|
+
node: entry.comment, messageId: 'misaligned', data: { column: targetCol },
|
|
45
|
+
fix(fixer) { return fixer.replaceText(entry.comment, fixedText) }
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
const MULTI_VALUE_KEYS = new Set([
|
|
4
|
+
'compatible', 'connect', 'description', 'exclude', 'exclude-match', 'grant',
|
|
5
|
+
'homepageURL', 'include', 'license', 'match', 'require', 'resource', 'supportURL'
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Disallow duplicate metadata keys',
|
|
13
|
+
category: 'Possible Errors',
|
|
14
|
+
recommended: true
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: { dupeKey: 'Duplicate @{{key}}' }
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
Program() {
|
|
23
|
+
const metaEntries = extractMetadata(context.sourceCode),
|
|
24
|
+
seen = new Set()
|
|
25
|
+
for (const entry of metaEntries) {
|
|
26
|
+
const baseKey = entry.key.split(':')[0]
|
|
27
|
+
if (!entry.key.includes(':') && MULTI_VALUE_KEYS.has(baseKey)) continue
|
|
28
|
+
if (seen.has(entry.key))
|
|
29
|
+
context.report({ node: entry.comment, messageId: 'dupeKey', data: { key: entry.key }})
|
|
30
|
+
else seen.add(entry.key)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,36 +1,33 @@
|
|
|
1
|
-
import { extractMetadata } from '../utils.js'
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
meta: {
|
|
5
|
-
type: 'problem',
|
|
6
|
-
docs: {
|
|
7
|
-
description: 'Flag @grant entries whose APIs are never used',
|
|
8
|
-
category: 'Possible Errors',
|
|
9
|
-
recommended: true
|
|
10
|
-
},
|
|
11
|
-
schema: [],
|
|
12
|
-
messages: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Flag @grant entries whose APIs are never used',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true
|
|
10
|
+
},
|
|
11
|
+
schema: [],
|
|
12
|
+
messages: { unusedGrant: 'Unused @grant: "{{grant}}".' }
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
const usedIdentifiers = new Set()
|
|
17
|
+
return {
|
|
18
|
+
Identifier(node) { usedIdentifiers.add(node.name) },
|
|
19
|
+
'Program:exit'() {
|
|
20
|
+
const sourceCode = context.sourceCode,
|
|
21
|
+
metaEntries = extractMetadata(sourceCode),
|
|
22
|
+
grants = metaEntries.filter(e => e.key == 'grant')
|
|
23
|
+
if (!grants.length) return
|
|
24
|
+
for (const grant of grants) {
|
|
25
|
+
const grantName = grant.value.trim()
|
|
26
|
+
if (['none', 'unsafeWindow'].includes(grantName)) continue
|
|
27
|
+
if (!usedIdentifiers.has(grantName))
|
|
28
|
+
context.report({ node: grant.comment, messageId: 'unusedGrant', data: { grant: grantName }})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,40 +1,36 @@
|
|
|
1
|
-
import { extractMetadata } from '../utils.js'
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
meta: {
|
|
7
|
-
type: 'problem',
|
|
8
|
-
docs: {
|
|
9
|
-
description: 'Flag @resource entries that are never referenced directly',
|
|
10
|
-
category: 'Possible Errors',
|
|
11
|
-
recommended: true
|
|
12
|
-
},
|
|
13
|
-
schema: [],
|
|
14
|
-
messages: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
const re = { resourceGetter: /GM_getResource(?:Text|URL)\s*\(\s*['"]([^'"]+)['"]\s*\)/g, whitespace: /\s+/ }
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Flag @resource entries that are never referenced directly',
|
|
10
|
+
category: 'Possible Errors',
|
|
11
|
+
recommended: true
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
messages: { unusedResource: 'Unused @resource: "{{name}}".' }
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
create(context) {
|
|
18
|
+
return {
|
|
19
|
+
Program() {
|
|
20
|
+
const sourceCode = context.sourceCode,
|
|
21
|
+
metaEntries = extractMetadata(sourceCode),
|
|
22
|
+
resources = metaEntries.filter(e => e.key == 'resource')
|
|
23
|
+
if (!resources.length) return
|
|
24
|
+
const text = sourceCode.getText(),
|
|
25
|
+
usedResources = new Set()
|
|
26
|
+
let match
|
|
27
|
+
while ((match = re.resourceGetter.exec(text)) !== null) usedResources.add(match[1])
|
|
28
|
+
for (const res of resources) {
|
|
29
|
+
const name = res.value.split(re.whitespace)[0]
|
|
30
|
+
if (!usedResources.has(name))
|
|
31
|
+
context.report({ node: res.comment, messageId: 'unusedResource', data: { name }})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { extractMetadata } from '../utils.js'
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
meta: {
|
|
5
|
-
type: 'problem',
|
|
6
|
-
docs: {
|
|
7
|
-
description: 'Require matching localized name and description fields',
|
|
8
|
-
category: 'Possible Errors',
|
|
9
|
-
recommended: true
|
|
10
|
-
},
|
|
11
|
-
schema: [{
|
|
12
|
-
type: 'object',
|
|
13
|
-
properties: {
|
|
14
|
-
ignoreLanguages: {
|
|
15
|
-
type: 'array',
|
|
16
|
-
items: { type: 'string' },
|
|
17
|
-
description: 'Language codes to ignore (e.g., ["fr", "de"])'
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
additionalProperties: false
|
|
21
|
-
}],
|
|
22
|
-
defaultOptions: [{}],
|
|
23
|
-
messages: { missingPair: 'Missing matching @{{missing}} for @{{present}}' }
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
create(context) {
|
|
27
|
-
const options = context.options[0] || {},
|
|
28
|
-
ignoreLanguages = options.ignoreLanguages || []
|
|
29
|
-
return {
|
|
30
|
-
Program() {
|
|
31
|
-
const metaEntries = extractMetadata(context.sourceCode),
|
|
32
|
-
nameEntries = metaEntries.filter(e => e.key.startsWith('name:')),
|
|
33
|
-
descEntries = metaEntries.filter(e => e.key.startsWith('description:')),
|
|
34
|
-
nameLangs = new Set(nameEntries.map(e => e.key.split(':')[1])),
|
|
35
|
-
descLangs = new Set(descEntries.map(e => e.key.split(':')[1]))
|
|
36
|
-
for (const lang of nameLangs)
|
|
37
|
-
if (!ignoreLanguages.includes(lang) && !descLangs.has(lang))
|
|
38
|
-
context.report({
|
|
39
|
-
node: nameEntries.find(entry => entry.key == `name:${lang}`).comment,
|
|
40
|
-
messageId: 'missingPair',
|
|
41
|
-
data: { missing: `description:${lang}`, present: `name:${lang}` }
|
|
42
|
-
})
|
|
43
|
-
for (const lang of descLangs)
|
|
44
|
-
if (!ignoreLanguages.includes(lang) && !nameLangs.has(lang))
|
|
45
|
-
context.report({
|
|
46
|
-
node: descEntries.find(e => e.key == `description:${lang}`).comment,
|
|
47
|
-
messageId: 'missingPair',
|
|
48
|
-
data: { missing: `name:${lang}`, present: `description:${lang}` }
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Require matching localized name and description fields',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true
|
|
10
|
+
},
|
|
11
|
+
schema: [{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
ignoreLanguages: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: { type: 'string' },
|
|
17
|
+
description: 'Language codes to ignore (e.g., ["fr", "de"])'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
additionalProperties: false
|
|
21
|
+
}],
|
|
22
|
+
defaultOptions: [{}],
|
|
23
|
+
messages: { missingPair: 'Missing matching @{{missing}} for @{{present}}' }
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
create(context) {
|
|
27
|
+
const options = context.options[0] || {},
|
|
28
|
+
ignoreLanguages = options.ignoreLanguages || []
|
|
29
|
+
return {
|
|
30
|
+
Program() {
|
|
31
|
+
const metaEntries = extractMetadata(context.sourceCode),
|
|
32
|
+
nameEntries = metaEntries.filter(e => e.key.startsWith('name:')),
|
|
33
|
+
descEntries = metaEntries.filter(e => e.key.startsWith('description:')),
|
|
34
|
+
nameLangs = new Set(nameEntries.map(e => e.key.split(':')[1])),
|
|
35
|
+
descLangs = new Set(descEntries.map(e => e.key.split(':')[1]))
|
|
36
|
+
for (const lang of nameLangs)
|
|
37
|
+
if (!ignoreLanguages.includes(lang) && !descLangs.has(lang))
|
|
38
|
+
context.report({
|
|
39
|
+
node: nameEntries.find(entry => entry.key == `name:${lang}`).comment,
|
|
40
|
+
messageId: 'missingPair',
|
|
41
|
+
data: { missing: `description:${lang}`, present: `name:${lang}` }
|
|
42
|
+
})
|
|
43
|
+
for (const lang of descLangs)
|
|
44
|
+
if (!ignoreLanguages.includes(lang) && !nameLangs.has(lang))
|
|
45
|
+
context.report({
|
|
46
|
+
node: descEntries.find(e => e.key == `description:${lang}`).comment,
|
|
47
|
+
messageId: 'missingPair',
|
|
48
|
+
data: { missing: `name:${lang}`, present: `description:${lang}` }
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { extractMetadata } from '../utils.js'
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
meta: {
|
|
5
|
-
type: 'problem',
|
|
6
|
-
docs: {
|
|
7
|
-
description: 'Require essential metadata fields',
|
|
8
|
-
category: 'Possible Errors',
|
|
9
|
-
recommended: true
|
|
10
|
-
},
|
|
11
|
-
schema: [{
|
|
12
|
-
type: 'object',
|
|
13
|
-
properties: {
|
|
14
|
-
fields: {
|
|
15
|
-
type: 'array',
|
|
16
|
-
items: { type: 'string' },
|
|
17
|
-
description: 'Required metadata fields (default: name, namespace, version)'
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
additionalProperties: false
|
|
21
|
-
}],
|
|
22
|
-
defaultOptions: [{}],
|
|
23
|
-
messages: { missingField: 'Missing required metadata field: @{{field}}' }
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
create(context) {
|
|
27
|
-
const options = context.options[0] || {},
|
|
28
|
-
requiredFields = options.fields || ['name', 'namespace', 'version']
|
|
29
|
-
return {
|
|
30
|
-
Program(node) {
|
|
31
|
-
const metaEntries = extractMetadata(context.sourceCode),
|
|
32
|
-
keys = new Set(metaEntries.map(e => e.key))
|
|
33
|
-
for (const field of requiredFields)
|
|
34
|
-
if (!keys.has(field))
|
|
35
|
-
context.report({ node, messageId: 'missingField', data: { field }})
|
|
36
|
-
if (!metaEntries.some(entry => ['include', 'match'].includes(entry.key)))
|
|
37
|
-
context.report({ node, messageId: 'missingField', data: { field: 'match or include' }})
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Require essential metadata fields',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true
|
|
10
|
+
},
|
|
11
|
+
schema: [{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
fields: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: { type: 'string' },
|
|
17
|
+
description: 'Required metadata fields (default: name, namespace, version)'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
additionalProperties: false
|
|
21
|
+
}],
|
|
22
|
+
defaultOptions: [{}],
|
|
23
|
+
messages: { missingField: 'Missing required metadata field: @{{field}}' }
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
create(context) {
|
|
27
|
+
const options = context.options[0] || {},
|
|
28
|
+
requiredFields = options.fields || ['name', 'namespace', 'version']
|
|
29
|
+
return {
|
|
30
|
+
Program(node) {
|
|
31
|
+
const metaEntries = extractMetadata(context.sourceCode),
|
|
32
|
+
keys = new Set(metaEntries.map(e => e.key))
|
|
33
|
+
for (const field of requiredFields)
|
|
34
|
+
if (!keys.has(field))
|
|
35
|
+
context.report({ node, messageId: 'missingField', data: { field }})
|
|
36
|
+
if (!metaEntries.some(entry => ['include', 'match'].includes(entry.key)))
|
|
37
|
+
context.report({ node, messageId: 'missingField', data: { field: 'match or include' }})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { extractMetadata } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Require that userscript filenames end in ".user.js"',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true
|
|
10
|
+
},
|
|
11
|
+
schema: [],
|
|
12
|
+
messages: { requireUserJs: 'Userscript filename should end in ".user.js"' }
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
Program(node) {
|
|
18
|
+
const metaEntries = extractMetadata(context.sourceCode)
|
|
19
|
+
if (!metaEntries.length) return
|
|
20
|
+
if (!context.filename.endsWith('.user.js'))
|
|
21
|
+
context.report({ node, messageId: 'requireUserJs' })
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|