eslint-plugin-greasemonkey 1.10.0 → 1.10.2
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 +3 -3
- package/package.json +10 -8
- package/src/rules/meta-order.js +6 -5
- package/src/rules/meta-spacing.js +7 -5
- package/src/rules/no-insecure-messaging.js +5 -5
- package/src/rules/no-invalid-grants.js +9 -10
- package/src/rules/no-invalid-headers.js +10 -9
- package/src/rules/no-sensitive-grants.js +8 -10
- package/src/rules/require-matching-localized-fields.js +6 -5
- package/src/rules/require-required-fields.js +5 -5
- package/src/rules/require-sri-hashing.js +10 -6
- package/src/rules/valid-field-values.js +9 -11
- package/src/rules/valid-license-header.js +9 -8
- package/src/rules/valid-url-headers.js +11 -13
- package/src/rules/valid-version-header.js +5 -8
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/src/branch/main/docs/LICENSE.md">
|
|
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
9
|
<a target="_blank" href="https://getnpmstats.com/?package=eslint-plugin-greasemonkey&range=last-year&interval=monthly">
|
|
10
|
-
<img src="https://img.shields.io/npm/dm/eslint-plugin-greasemonkey?style=for-the-badge&logo=npm&color=black"></a>
|
|
10
|
+
<img alt="[Monthly npm downloads]" src="https://img.shields.io/npm/dm/eslint-plugin-greasemonkey?style=for-the-badge&logo=npm&color=black"></a>
|
|
11
11
|
<a href="#usage">
|
|
12
12
|
<img alt="[Supports ESLint >=9.0.0 / Oxlint >=1.65.0]" src="https://img.shields.io/badge/Supports-ESLint_%3E%3D9.0.0_%2F_Oxlint_%3E%3D1.65.0-4B32C3?style=for-the-badge&logo=eslint"></a>
|
|
13
13
|
<br><br>
|
|
@@ -44,7 +44,7 @@ import greasemonkey from 'eslint-plugin-greasemonkey'
|
|
|
44
44
|
export default [
|
|
45
45
|
// other configs
|
|
46
46
|
{
|
|
47
|
-
files: ['**/*.user.js'], plugins: { greasemonkey }
|
|
47
|
+
files: ['**/*.user.js'], plugins: { greasemonkey },
|
|
48
48
|
rules: {
|
|
49
49
|
...greasemonkey.configs.recommended.rules,
|
|
50
50
|
'greasemonkey/no-unused-resources': 'off', // selectively disable rule
|
|
@@ -60,7 +60,7 @@ export default [
|
|
|
60
60
|
|
|
61
61
|
### <img height=15 width=auto src="https://www.google.com/s2/favicons?domain=oxc.rs&sz=64"> Oxlint
|
|
62
62
|
|
|
63
|
-
Add a
|
|
63
|
+
Add a bundled config to your `oxlint.config.*ts`:
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
66
|
...
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-greasemonkey",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "ESLint rules for Greasemonkey/Tampermonkey userscripts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -69,20 +69,22 @@
|
|
|
69
69
|
"@eslint/json": "^2.1.1",
|
|
70
70
|
"@eslint/markdown": "^7.5.1",
|
|
71
71
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
72
|
-
"eslint": "^10.
|
|
73
|
-
"eslint-plugin-constructors": "^1.
|
|
72
|
+
"eslint": "^10.14.0",
|
|
73
|
+
"eslint-plugin-constructors": "^1.7.0",
|
|
74
74
|
"eslint-plugin-eslint-plugin": "^7.6.2",
|
|
75
75
|
"eslint-plugin-import-x": "^4.17.1",
|
|
76
76
|
"eslint-plugin-n": "^18.5.0",
|
|
77
77
|
"eslint-plugin-no-secrets": "^2.3.3",
|
|
78
78
|
"eslint-plugin-no-unsanitized": "^4.1.5",
|
|
79
|
-
"eslint-plugin-package-json": "^1.
|
|
79
|
+
"eslint-plugin-package-json": "^1.12.0",
|
|
80
80
|
"eslint-plugin-promise": "^7.3.0",
|
|
81
|
-
"eslint-plugin-regexp": "^3.3.
|
|
82
|
-
"eslint-plugin-security": "^4.2.
|
|
81
|
+
"eslint-plugin-regexp": "^3.3.2",
|
|
82
|
+
"eslint-plugin-security": "^4.2.1",
|
|
83
83
|
"eslint-plugin-sonarjs": "^4.2.2",
|
|
84
|
-
"eslint-plugin-woodpecker": "^1.1.
|
|
85
|
-
"oxlint": "^1.
|
|
84
|
+
"eslint-plugin-woodpecker": "^1.1.2",
|
|
85
|
+
"oxlint": "^1.90.0",
|
|
86
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
87
|
+
"typescript-latest": "npm:typescript@^7.0.2"
|
|
86
88
|
},
|
|
87
89
|
"peerDependencies": {
|
|
88
90
|
"eslint": ">=9.0.0"
|
package/src/rules/meta-order.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
|
|
3
|
+
const defaultOptions = { order: [] }
|
|
3
4
|
const defaultOrder = [
|
|
4
5
|
'name', 'description', 'author', 'namespace', 'version', 'license', 'icon', 'icon64',
|
|
5
6
|
'compatible', 'match', 'exclude', 'include', 'connect', 'require', 'resource', 'grant',
|
|
@@ -43,21 +44,21 @@ export default {
|
|
|
43
44
|
},
|
|
44
45
|
additionalProperties: false
|
|
45
46
|
}],
|
|
46
|
-
defaultOptions: [
|
|
47
|
+
defaultOptions: [defaultOptions],
|
|
47
48
|
messages: { wrongOrder: '@{{first}} should appear before @{{second}}.' }
|
|
48
49
|
},
|
|
49
50
|
|
|
50
51
|
create(context) {
|
|
51
|
-
const
|
|
52
|
-
|
|
52
|
+
const { order } = { ...defaultOptions, ...context.options[0] },
|
|
53
|
+
master = order.length ? defaultOrder.filter(key => !order.includes(key)) : defaultOrder
|
|
53
54
|
return {
|
|
54
55
|
Program() {
|
|
55
56
|
const sourceCode = context.sourceCode,
|
|
56
57
|
entries = metadata.extract(sourceCode),
|
|
57
|
-
violation = findViolation(entries, master) ?? (
|
|
58
|
+
violation = findViolation(entries, master) ?? (order.length && findViolation(entries, order))
|
|
58
59
|
if (!violation) return
|
|
59
60
|
const [first, second] = violation
|
|
60
|
-
const sorted = sortEntries(sortEntries(entries, master),
|
|
61
|
+
const sorted = sortEntries(sortEntries(entries, master), order)
|
|
61
62
|
const replacements = // oxlint-disable-next-line security/detect-object-injection
|
|
62
63
|
new Map(sorted.map((entry, idx) => [entries[idx], sourceCode.getText(entry.comment)]))
|
|
63
64
|
const start = entries[0].comment.range[0]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
+
const defaultOptions = { spaces: 3 }
|
|
5
|
+
|
|
4
6
|
export default {
|
|
5
7
|
meta: {
|
|
6
8
|
type: 'layout',
|
|
@@ -15,27 +17,27 @@ export default {
|
|
|
15
17
|
properties: {
|
|
16
18
|
spaces: {
|
|
17
19
|
type: 'number',
|
|
18
|
-
description: 'Minimum spaces after the longest key
|
|
20
|
+
description: 'Minimum spaces after the longest key'
|
|
19
21
|
}
|
|
20
22
|
},
|
|
21
23
|
additionalProperties: false
|
|
22
24
|
}],
|
|
23
|
-
defaultOptions: [
|
|
25
|
+
defaultOptions: [defaultOptions],
|
|
24
26
|
messages: { misaligned: 'Metadata values should be aligned at column {{column}}.' }
|
|
25
27
|
},
|
|
26
28
|
|
|
27
29
|
create(context) {
|
|
28
|
-
const
|
|
30
|
+
const { spaces } = { ...defaultOptions, ...context.options[0] }
|
|
29
31
|
return {
|
|
30
32
|
Program() {
|
|
31
33
|
const sourceCode = context.sourceCode,
|
|
32
34
|
metaEntries = metadata.extract(sourceCode)
|
|
33
35
|
if (!metaEntries.length) return
|
|
34
|
-
let targetCol = metaEntries.reduce((max, entry) => Math.max(max, entry.key.length), 0) +
|
|
36
|
+
let targetCol = metaEntries.reduce((max, entry) => Math.max(max, entry.key.length), 0) + spaces +4
|
|
35
37
|
for (const entry of metaEntries)
|
|
36
38
|
if (entry.key == 'resource' && entry.value.trim()) {
|
|
37
39
|
const name = entry.value.trim().split(regex.whitespace.all)[0],
|
|
38
|
-
requiredCol = 4 + entry.key.length +1 + name.length +
|
|
40
|
+
requiredCol = 4 + entry.key.length +1 + name.length + spaces
|
|
39
41
|
if (requiredCol > targetCol) targetCol = requiredCol
|
|
40
42
|
}
|
|
41
43
|
for (const entry of metaEntries) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const defaultOptions = { disallowWildcardTarget: true }
|
|
1
2
|
const re = {
|
|
2
3
|
messageTargets: new Set(['window', 'self', 'unsafeWindow']),
|
|
3
4
|
postmsgValidation: /\b\w+\.(?:origin|source)\b/
|
|
@@ -14,7 +15,7 @@ export default {
|
|
|
14
15
|
recommended: true,
|
|
15
16
|
security: true
|
|
16
17
|
},
|
|
17
|
-
defaultOptions: [
|
|
18
|
+
defaultOptions: [defaultOptions],
|
|
18
19
|
schema: [{
|
|
19
20
|
type: 'object',
|
|
20
21
|
properties: {
|
|
@@ -34,9 +35,8 @@ export default {
|
|
|
34
35
|
},
|
|
35
36
|
|
|
36
37
|
create(context) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
38
|
+
const { disallowWildcardTarget } = { ...defaultOptions, ...context.options[0] },
|
|
39
|
+
sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
40
40
|
function checkPostMessageHandler(node, handlerNode, target) {
|
|
41
41
|
if (!handlerNode) return
|
|
42
42
|
const body = sourceCode.getText(handlerNode)
|
|
@@ -50,7 +50,7 @@ export default {
|
|
|
50
50
|
&& node.arguments[0]?.value == 'message'
|
|
51
51
|
&& isMessageTarget(node.callee.object)
|
|
52
52
|
) checkPostMessageHandler(node, node.arguments[1], node.callee.object.name)
|
|
53
|
-
if (
|
|
53
|
+
if (disallowWildcardTarget && node.callee.property?.name == 'postMessage') {
|
|
54
54
|
const target = node.arguments[1]
|
|
55
55
|
if (target?.type == 'Literal' && target.value == '*')
|
|
56
56
|
context.report({ node: target, messageId: 'wildcardTarget' })
|
|
@@ -4,7 +4,8 @@ import metadata from '../utils/metadata.js'
|
|
|
4
4
|
import apis from '../data/apis.js'
|
|
5
5
|
import headers from '../data/headers.js'
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const defaultOptions = { target: 'all', allowedGrants: [] },
|
|
8
|
+
specialGrants = new Set(['none', 'unsafeWindow', 'window.close', 'window.focus', 'window.onurlchange']),
|
|
8
9
|
allGrants = new Set([...apis.all, ...specialGrants]),
|
|
9
10
|
sortedGrants = new Map(Array.from(allGrants, grant => [grant.toLowerCase(), grant]))
|
|
10
11
|
|
|
@@ -23,21 +24,21 @@ export default {
|
|
|
23
24
|
anyOf: [
|
|
24
25
|
{ type: 'string', enum: ['all', ...headers.managers] },
|
|
25
26
|
{ type: 'array', items: { type: 'string', enum: headers.managers },
|
|
26
|
-
|
|
27
|
+
uniqueItems: true }
|
|
27
28
|
],
|
|
28
29
|
description: 'Manager(s) whose grants to validate against (default: all)'
|
|
29
30
|
},
|
|
30
31
|
allowedGrants: {
|
|
31
32
|
anyOf: [
|
|
32
33
|
{ type: 'string' },
|
|
33
|
-
{ type: 'array', items: { type: 'string' },
|
|
34
|
+
{ type: 'array', items: { type: 'string' }, uniqueItems: true }
|
|
34
35
|
],
|
|
35
36
|
description: 'Additional @grant values to allow beyond the built-in list'
|
|
36
37
|
}
|
|
37
38
|
},
|
|
38
39
|
additionalProperties: false
|
|
39
40
|
}],
|
|
40
|
-
defaultOptions: [
|
|
41
|
+
defaultOptions: [defaultOptions],
|
|
41
42
|
messages: {
|
|
42
43
|
invalidGrant: 'Invalid @grant: "{{grant}}"',
|
|
43
44
|
unsupportedGrant: '"@{{grant}}" is not supported by {{target}} (only by {{supporting}}).',
|
|
@@ -46,18 +47,16 @@ export default {
|
|
|
46
47
|
},
|
|
47
48
|
|
|
48
49
|
create(context) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
rawAllowed = options.allowedGrants ?? [],
|
|
53
|
-
allowedGrants = new Set(Array.isArray(rawAllowed) ? rawAllowed : [rawAllowed])
|
|
50
|
+
const { target, allowedGrants } = { ...defaultOptions, ...context.options[0] },
|
|
51
|
+
targets = Array.isArray(target) ? target : [target],
|
|
52
|
+
allowedGrantSet = new Set(Array.isArray(allowedGrants) ? allowedGrants : [allowedGrants])
|
|
54
53
|
return {
|
|
55
54
|
Program() {
|
|
56
55
|
const metaEntries = metadata.extract(context.sourceCode),
|
|
57
56
|
grantEntries = metaEntries.filter(entry => entry.key == 'grant')
|
|
58
57
|
for (const grant of grantEntries) {
|
|
59
58
|
const grantName = grant.value.trim()
|
|
60
|
-
if (specialGrants.has(grantName) ||
|
|
59
|
+
if (specialGrants.has(grantName) || allowedGrantSet.has(grantName)) continue
|
|
61
60
|
if (!allGrants.has(grantName)) {
|
|
62
61
|
const suggestion = sortedGrants.get(grantName.toLowerCase())
|
|
63
62
|
if (suggestion && suggestion != grantName)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
import headers from '../data/headers.js'
|
|
3
3
|
|
|
4
|
+
const defaultOptions = { target: 'all', allowedHeaders: [] }
|
|
5
|
+
|
|
4
6
|
function parseAllowedPattern(entry) {
|
|
5
7
|
if (typeof entry != 'string' || !entry.startsWith('/')) return null
|
|
6
8
|
const lastSlash = entry.lastIndexOf('/')
|
|
@@ -36,7 +38,7 @@ export default {
|
|
|
36
38
|
},
|
|
37
39
|
additionalProperties: false
|
|
38
40
|
}],
|
|
39
|
-
defaultOptions: [
|
|
41
|
+
defaultOptions: [defaultOptions],
|
|
40
42
|
messages: {
|
|
41
43
|
invalidHeader: 'Invalid header: "@{{header}}".',
|
|
42
44
|
unsupportedHeader: '"@{{header}}" is not supported by {{target}} (only by {{supporting}}).'
|
|
@@ -44,14 +46,13 @@ export default {
|
|
|
44
46
|
},
|
|
45
47
|
|
|
46
48
|
create(context) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
: new Set(targets.flatMap(target => [...headers.valid.get(target)]))
|
|
49
|
+
const { target, allowedHeaders } = { ...defaultOptions, ...context.options[0] },
|
|
50
|
+
targets = Array.isArray(target) ? target : [target],
|
|
51
|
+
allowedList = [allowedHeaders].flat(),
|
|
52
|
+
allowedExact = new Set(),
|
|
53
|
+
allowedRegexes = [],
|
|
54
|
+
validSet = targets.includes('all') ? headers.valid.get('all')
|
|
55
|
+
: new Set(targets.flatMap(target => [...headers.valid.get(target)]))
|
|
55
56
|
for (const entry of allowedList) {
|
|
56
57
|
if (entry instanceof RegExp) allowedRegexes.push(entry)
|
|
57
58
|
else if (typeof entry == 'string') {
|
|
@@ -2,7 +2,8 @@ import apis from '../data/apis.js'
|
|
|
2
2
|
import headers from '../data/headers.js'
|
|
3
3
|
import regex from '../data/regex.js'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const defaultOptions = { target: 'all', allowedGrants: [], allowBroadConnect: false },
|
|
6
|
+
re = { connect: /^\s*\/\/\s*@connect\s+(\S+)/, grant: /^\s*\/\/\s*@grant\s+(\S+)/ }
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
meta: {
|
|
@@ -13,7 +14,7 @@ export default {
|
|
|
13
14
|
recommended: false,
|
|
14
15
|
security: true
|
|
15
16
|
},
|
|
16
|
-
defaultOptions: [
|
|
17
|
+
defaultOptions: [defaultOptions],
|
|
17
18
|
schema: [{
|
|
18
19
|
type: 'object',
|
|
19
20
|
properties: {
|
|
@@ -45,14 +46,11 @@ export default {
|
|
|
45
46
|
},
|
|
46
47
|
|
|
47
48
|
create(context) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
sourceCode = context.sourceCode ?? context.getSourceCode(),
|
|
54
|
-
managers = target == 'all' ? [...apis.managerSensitive.keys()]
|
|
55
|
-
: Array.isArray(target) ? target : [target]
|
|
49
|
+
const { target, allowedGrants, allowBroadConnect } = { ...defaultOptions, ...context.options[0] },
|
|
50
|
+
allowed = new Set(typeof allowedGrants == 'string' ? [allowedGrants] : allowedGrants),
|
|
51
|
+
sourceCode = context.sourceCode ?? context.getSourceCode(),
|
|
52
|
+
managers = target == 'all' ? [...apis.managerSensitive.keys()]
|
|
53
|
+
: Array.isArray(target) ? target : [target]
|
|
56
54
|
const isSensitive = grant => apis.sensitive.has(grant)
|
|
57
55
|
|| managers.some(mgr => apis.managerSensitive.get(mgr)?.has(grant))
|
|
58
56
|
const report = (line, messageId, data, col, len) => context.report({
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
|
|
3
|
+
const defaultOptions = { ignoreLanguages: [] }
|
|
4
|
+
|
|
3
5
|
export default {
|
|
4
6
|
meta: {
|
|
5
7
|
type: 'problem',
|
|
@@ -14,21 +16,20 @@ export default {
|
|
|
14
16
|
ignoreLanguages: {
|
|
15
17
|
anyOf: [
|
|
16
18
|
{ type: 'string' },
|
|
17
|
-
{ type: 'array', items: { type: 'string' },
|
|
19
|
+
{ type: 'array', items: { type: 'string' }, uniqueItems: true }
|
|
18
20
|
],
|
|
19
21
|
description: 'Language code(s) to ignore'
|
|
20
22
|
}
|
|
21
23
|
},
|
|
22
24
|
additionalProperties: false
|
|
23
25
|
}],
|
|
24
|
-
defaultOptions: [
|
|
26
|
+
defaultOptions: [defaultOptions],
|
|
25
27
|
messages: { missingPair: 'Missing matching @{{missing}} for @{{present}}.' }
|
|
26
28
|
},
|
|
27
29
|
|
|
28
30
|
create(context) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
ignoreLangs = new Set(Array.isArray(rawLangs) ? rawLangs : [rawLangs])
|
|
31
|
+
const { ignoreLanguages } = { ...defaultOptions, ...context.options[0] },
|
|
32
|
+
ignoreLangs = new Set(Array.isArray(ignoreLanguages) ? ignoreLanguages : [ignoreLanguages])
|
|
32
33
|
return {
|
|
33
34
|
Program() {
|
|
34
35
|
const metaEntries = metadata.extract(context.sourceCode),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
|
|
3
|
+
const defaultOptions = { fields: ['name', 'version'], requireMatchOrInclude: true }
|
|
4
|
+
|
|
3
5
|
export default {
|
|
4
6
|
meta: {
|
|
5
7
|
type: 'problem',
|
|
@@ -23,19 +25,17 @@ export default {
|
|
|
23
25
|
},
|
|
24
26
|
additionalProperties: false
|
|
25
27
|
}],
|
|
26
|
-
defaultOptions: [
|
|
28
|
+
defaultOptions: [defaultOptions],
|
|
27
29
|
messages: { missingField: 'Missing required metadata field: @{{field}}.' }
|
|
28
30
|
},
|
|
29
31
|
|
|
30
32
|
create(context) {
|
|
31
|
-
const
|
|
32
|
-
requiredFields = options.fields || ['name', 'version'],
|
|
33
|
-
requireMatchOrInclude = options.requireMatchOrInclude ?? true
|
|
33
|
+
const { fields, requireMatchOrInclude } = { ...defaultOptions, ...context.options[0] }
|
|
34
34
|
return {
|
|
35
35
|
Program(node) {
|
|
36
36
|
const metaEntries = metadata.extract(context.sourceCode),
|
|
37
37
|
keys = new Set(metaEntries.map(entry => entry.key))
|
|
38
|
-
for (const field of
|
|
38
|
+
for (const field of fields)
|
|
39
39
|
if (!keys.has(field))
|
|
40
40
|
context.report({ node, messageId: 'missingField', data: { field }})
|
|
41
41
|
if (requireMatchOrInclude && !['include', 'match'].some(field => keys.has(field)))
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
|
|
3
|
+
const defaultOptions = { algorithm: 'sha256' }
|
|
3
4
|
const re = {
|
|
4
|
-
hashEntry: /^(sha1|sha256|sha384|sha512|md5)[=-]([a-z0-9+/]+={0,2})$/i,
|
|
5
|
+
hashEntry: /^(sha1|sha256|sha384|sha512|md5)[=-]([a-z0-9+/]+={0,2})$/i,
|
|
6
|
+
hashFragment: /#([^#]+)$/,
|
|
7
|
+
hashSep: /[,;]/
|
|
8
|
+
}
|
|
5
9
|
|
|
6
10
|
export default {
|
|
7
11
|
meta: {
|
|
@@ -23,12 +27,12 @@ export default {
|
|
|
23
27
|
minItems: 1, uniqueItems: true
|
|
24
28
|
}
|
|
25
29
|
],
|
|
26
|
-
description: 'SRI hash algorithm(s) to accept, as a single string or array
|
|
30
|
+
description: 'SRI hash algorithm(s) to accept, as a single string or array'
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
additionalProperties: false
|
|
30
34
|
}],
|
|
31
|
-
defaultOptions: [
|
|
35
|
+
defaultOptions: [defaultOptions],
|
|
32
36
|
messages: {
|
|
33
37
|
missingHash: 'Missing SRI hash for @{{key}}.',
|
|
34
38
|
invalidHash: 'Invalid SRI hash for @{{key}}.',
|
|
@@ -37,9 +41,9 @@ export default {
|
|
|
37
41
|
},
|
|
38
42
|
|
|
39
43
|
create(context) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
const { algorithm } = { ...defaultOptions, ...context.options[0] },
|
|
45
|
+
algorithms = (Array.isArray(algorithm) ? algorithm : [algorithm]).map(a => a.toLowerCase()),
|
|
46
|
+
expected = algorithms.join(' or ')
|
|
43
47
|
return {
|
|
44
48
|
'Program:exit'() {
|
|
45
49
|
for (const entry of metadata.extract(context.sourceCode)) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
+
const defaultOptions = { versionType: 'semver', allowedGrants: [], allowFutureDate: false },
|
|
5
|
+
urlKeys = ['homepage', 'icon', 'icon64', 'require', 'source', 'website']
|
|
4
6
|
const re = { // oxlint-disable security/detect-unsafe-regex
|
|
5
7
|
semVer: /^\d+\.\d+\.\d+(?:-[\da-z.-]+)?(?:\+[\da-z.-]+)?$/i,
|
|
6
8
|
dateVer: /^\d{4}\.(?:0?[1-9]|1[0-2])\.(?:0?[1-9]|[12]\d|3[01])(?:\.\d+)?$/
|
|
7
9
|
} // oxlint-enable security/detect-unsafe-regex
|
|
8
10
|
|
|
9
|
-
const urlKeys = ['homepage', 'icon', 'icon64', 'require', 'source', 'website']
|
|
10
|
-
|
|
11
11
|
export default {
|
|
12
12
|
meta: {
|
|
13
13
|
type: 'problem',
|
|
@@ -22,7 +22,7 @@ export default {
|
|
|
22
22
|
versionType: {
|
|
23
23
|
type: 'string',
|
|
24
24
|
enum: ['semver', 'date'],
|
|
25
|
-
description: 'Version format to enforce
|
|
25
|
+
description: 'Version format to enforce'
|
|
26
26
|
},
|
|
27
27
|
allowedGrants: {
|
|
28
28
|
type: 'array',
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
},
|
|
32
32
|
allowFutureDate: {
|
|
33
33
|
type: 'boolean',
|
|
34
|
-
description: 'Allow @version dates later than the current date
|
|
34
|
+
description: 'Allow @version dates later than the current date'
|
|
35
35
|
},
|
|
36
36
|
timezone: {
|
|
37
37
|
type: 'string',
|
|
@@ -40,7 +40,7 @@ export default {
|
|
|
40
40
|
},
|
|
41
41
|
additionalProperties: false
|
|
42
42
|
}],
|
|
43
|
-
defaultOptions: [
|
|
43
|
+
defaultOptions: [defaultOptions],
|
|
44
44
|
messages: {
|
|
45
45
|
invalidVersion: 'Invalid @version: must be a valid {{versionType}} (expected format: {{example}}).',
|
|
46
46
|
futureVersion: 'Invalid @version: date cannot be in the future.',
|
|
@@ -50,11 +50,9 @@ export default {
|
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
create(context) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
allowFutureDate = options.allowFutureDate ?? false,
|
|
57
|
-
timezone = options.timezone,
|
|
53
|
+
const { versionType, allowedGrants, allowFutureDate, timezone }
|
|
54
|
+
= { ...defaultOptions, ...context.options[0] }
|
|
55
|
+
const allowedGrantsSet = new Set(allowedGrants),
|
|
58
56
|
example = versionType == 'semver' ? '1.2.3' : 'YYYY.M.D or YYYY.MM.DD'
|
|
59
57
|
const now = timezone
|
|
60
58
|
? Object.fromEntries(new Intl.DateTimeFormat('en', {
|
|
@@ -66,7 +64,7 @@ export default {
|
|
|
66
64
|
Program() {
|
|
67
65
|
const metaEntries = metadata.extract(context.sourceCode)
|
|
68
66
|
for (const entry of metaEntries) {
|
|
69
|
-
if (entry.key == 'grant' &&
|
|
67
|
+
if (entry.key == 'grant' && allowedGrantsSet.size && !allowedGrantsSet.has(entry.value))
|
|
70
68
|
context.report({ node: entry.comment, messageId: 'invalidGrant', data: { grant: entry.value }})
|
|
71
69
|
else if (entry.key == 'resource') {
|
|
72
70
|
const url = entry.value.trim().split(regex.whitespace.all)[1]
|
|
@@ -5,7 +5,8 @@ import license from '../utils/license.js'
|
|
|
5
5
|
|
|
6
6
|
import regex from '../data/regex.js'
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const defaultOptions = { allowedLicenses: [] },
|
|
9
|
+
re = { spdxExpr: /(?<=\s)(?:OR|AND)(?=\s)/i }
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
meta: {
|
|
@@ -15,7 +16,7 @@ export default {
|
|
|
15
16
|
category: 'Possible Errors',
|
|
16
17
|
recommended: true
|
|
17
18
|
},
|
|
18
|
-
defaultOptions: [
|
|
19
|
+
defaultOptions: [defaultOptions],
|
|
19
20
|
schema: [{
|
|
20
21
|
type: 'object',
|
|
21
22
|
properties: {
|
|
@@ -36,9 +37,9 @@ export default {
|
|
|
36
37
|
},
|
|
37
38
|
|
|
38
39
|
create(context) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const { allowedLicenses } = { ...defaultOptions, ...context.options[0] },
|
|
41
|
+
allowedLicensesArray = array.to(allowedLicenses),
|
|
42
|
+
sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
42
43
|
return {
|
|
43
44
|
Program() {
|
|
44
45
|
const text = sourceCode.text,
|
|
@@ -61,11 +62,11 @@ export default {
|
|
|
61
62
|
for (const part of parts)
|
|
62
63
|
if (!license.spdx.isValid(part))
|
|
63
64
|
return context.report({ loc, messageId: 'invalidSPDX', data: { id: part }})
|
|
64
|
-
if (
|
|
65
|
+
if (allowedLicensesArray.length) {
|
|
65
66
|
for (const part of parts)
|
|
66
|
-
if (!
|
|
67
|
+
if (!allowedLicensesArray.includes(part))
|
|
67
68
|
return context.report({ loc, messageId: 'disallowedSPDX',
|
|
68
|
-
data: { id: part, allowed:
|
|
69
|
+
data: { id: part, allowed: allowedLicensesArray.join(', ') }})
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
checkURLfields: true,
|
|
6
|
+
checkMatchPatterns: true,
|
|
7
|
+
allowedSchemes: ['http', 'https', 'file', 'ftp', 'http*'],
|
|
8
|
+
allowBroadPatterns: false
|
|
9
|
+
}
|
|
4
10
|
const re = {
|
|
5
11
|
matchPattern: /^(\*|http\*?|https?|file|ftp):\/\/([^/]*)(\/.*)$/,
|
|
6
12
|
regexForm: /^\/.+\/[a-z]*$/i
|
|
7
13
|
}
|
|
8
|
-
|
|
9
14
|
const urlKeys = new Set(['homepage', 'require', 'require-css', 'source', 'website']),
|
|
10
15
|
matchKeys = new Set(['match', 'exclude-match']),
|
|
11
16
|
regexOrMatchKeys = new Set(['include', 'exclude'])
|
|
@@ -18,12 +23,7 @@ export default {
|
|
|
18
23
|
category: 'Possible Errors',
|
|
19
24
|
recommended: true
|
|
20
25
|
},
|
|
21
|
-
defaultOptions: [
|
|
22
|
-
checkURLfields: true,
|
|
23
|
-
checkMatchPatterns: true,
|
|
24
|
-
allowedSchemes: ['http', 'https', 'file', 'ftp', 'http*'],
|
|
25
|
-
allowBroadPatterns: false
|
|
26
|
-
}],
|
|
26
|
+
defaultOptions: [defaultOptions],
|
|
27
27
|
schema: [{
|
|
28
28
|
type: 'object',
|
|
29
29
|
properties: {
|
|
@@ -57,11 +57,9 @@ export default {
|
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
create(context) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
allowedSchemes = new Set(opts.allowedSchemes ?? ['http', 'https', 'file', 'ftp', 'http*']),
|
|
64
|
-
allowBroadPatterns = opts.allowBroadPatterns === true
|
|
60
|
+
const { checkURLfields, checkMatchPatterns, allowedSchemes, allowBroadPatterns }
|
|
61
|
+
= { ...defaultOptions, ...context.options[0] }
|
|
62
|
+
const allowedSchemeSet = new Set(allowedSchemes)
|
|
65
63
|
if (!checkURLfields && !checkMatchPatterns) return {}
|
|
66
64
|
function validateMatchPattern(value, field, node) {
|
|
67
65
|
if (value == '<all_urls>') {
|
|
@@ -76,7 +74,7 @@ export default {
|
|
|
76
74
|
return
|
|
77
75
|
}
|
|
78
76
|
const [, scheme, host, path] = m
|
|
79
|
-
if (scheme != '*' && !
|
|
77
|
+
if (scheme != '*' && !allowedSchemeSet.has(scheme))
|
|
80
78
|
context.report({ node, messageId: 'invalidScheme', data: { field, value }})
|
|
81
79
|
else if (scheme == 'file' && host != '')
|
|
82
80
|
context.report({ node, messageId: 'invalidFileHost', data: { field, value }})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import metadata from '../utils/metadata.js'
|
|
2
2
|
|
|
3
|
+
const defaultOptions = { versionType: 'semver', allowFutureDate: false, timezone: '' },
|
|
4
|
+
examples = { date: 'YYYY.M.D or YYYY.M.D.N', semver: '1.2.3' }
|
|
3
5
|
const re = { // oxlint-disable security/detect-unsafe-regex
|
|
4
6
|
date: /^\d{4}\.(?:0?[1-9]|1[0-2])\.(?:0?[1-9]|[12]\d|3[01])(?:\.\d+)?$/,
|
|
5
7
|
semver: /^\d+\.\d+\.\d+(?:-[\da-z.-]+)?(?:\+[\da-z.-]+)?$/i
|
|
6
8
|
} // oxlint-enable security/detect-unsafe-regex
|
|
7
9
|
|
|
8
|
-
const examples = { date: 'YYYY.M.D or YYYY.M.D.N', semver: '1.2.3' }
|
|
9
|
-
|
|
10
10
|
export default {
|
|
11
11
|
meta: {
|
|
12
12
|
type: 'problem',
|
|
@@ -15,7 +15,7 @@ export default {
|
|
|
15
15
|
category: 'Possible Errors',
|
|
16
16
|
recommended: true
|
|
17
17
|
},
|
|
18
|
-
defaultOptions: [
|
|
18
|
+
defaultOptions: [defaultOptions],
|
|
19
19
|
schema: [{
|
|
20
20
|
type: 'object',
|
|
21
21
|
properties: {
|
|
@@ -44,11 +44,8 @@ export default {
|
|
|
44
44
|
},
|
|
45
45
|
|
|
46
46
|
create(context) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
allowFutureDate = opts.allowFutureDate ?? false,
|
|
50
|
-
timezone = opts.timezone ?? '',
|
|
51
|
-
example = examples[versionType] // oxlint-disable-line security/detect-object-injection
|
|
47
|
+
const { versionType, allowFutureDate, timezone } = { ...defaultOptions, ...context.options[0] },
|
|
48
|
+
example = examples[versionType] // oxlint-disable-line security/detect-object-injection
|
|
52
49
|
const now = timezone
|
|
53
50
|
? Object.fromEntries(new Intl.DateTimeFormat('en', {
|
|
54
51
|
timeZone: timezone, year: 'numeric', month: 'numeric', day: 'numeric'
|