eslint-plugin-chrome-extensions 2.6.0 → 2.8.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 +648 -33
- package/package.json +11 -9
- package/src/data/apis.js +74 -88
- package/src/data/manifest.js +3 -1
- package/src/data/regex.js +3 -1
- package/src/index.d.ts +42 -0
- package/src/index.js +0 -4
- package/src/rules/css-spacing.js +375 -0
- package/src/rules/manifest-key-order.js +1 -2
- package/src/rules/manifest-spacing.js +1 -2
- package/src/rules/no-content-style-leak.js +12 -11
- package/src/rules/no-deprecated-apis.js +2 -2
- package/src/rules/no-duplicate-manifest-keys.js +0 -1
- package/src/rules/no-global-keyframes.js +84 -0
- package/src/rules/no-host-page-selectors.js +105 -0
- package/src/rules/no-inline-event-handlers.js +116 -0
- package/src/rules/no-inline-scripts.js +65 -0
- package/src/rules/no-insecure-fetch.js +7 -6
- package/src/rules/no-insecure-messaging.js +4 -4
- package/src/rules/no-invalid-locales.js +3 -5
- package/src/rules/no-missing-default-locale.js +1 -1
- package/src/rules/no-missing-permissions.js +2 -3
- package/src/rules/no-missing-web-accessible-resources.js +2 -2
- package/src/rules/no-remote-code.js +77 -28
- package/src/rules/no-remote-objects.js +55 -0
- package/src/rules/no-remote-urls.js +102 -0
- package/src/rules/no-scrollbar-styling.js +71 -0
- package/src/rules/no-unrestricted-host-permissions.js +1 -1
- package/src/rules/no-unused-i18n-keys.js +0 -1
- package/src/rules/no-unused-permissions.js +5 -4
- package/src/rules/require-license-file.js +1 -2
- package/src/rules/require-mv3.js +3 -2
- package/src/rules/require-short-name-when-useful.js +0 -1
- package/src/rules/valid-filenames.js +3 -3
- package/src/rules/valid-license-file.js +2 -4
- package/src/rules/valid-manifest-icons.js +2 -2
- package/src/rules/valid-mv3-schema.js +5 -5
- package/src/rules/valid-url-fields.js +2 -2
- package/src/rules/valid-version-field.js +2 -2
- package/src/utils/api.js +20 -0
- package/src/utils/array.js +1 -0
- package/src/utils/extension.js +1 -3
- package/src/utils/html.js +29 -0
- package/src/utils/i18n.js +2 -2
- package/src/utils/init.js +21 -9
- package/src/utils/license.js +1 -1
- package/src/utils/node.js +3 -1
- package/src/utils/pkg.js +19 -2
- package/src/utils/rules.js +1 -1
- package/src/data/deprecations.js +0 -8
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import array from '../utils/array.js'
|
|
2
|
+
import node from '../utils/node.js'
|
|
3
|
+
|
|
4
|
+
const re = {
|
|
5
|
+
scrollbarPseudo: /^(?:-webkit-)?scrollbar(?:-[\w-]+)?$/i } // oxlint-disable-line security/detect-unsafe-regex
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow scrollbar pseudo-elements (e.g. `::-webkit-scrollbar`) '
|
|
12
|
+
+ 'on unscoped selectors in content-script CSS',
|
|
13
|
+
category: 'Possible Errors',
|
|
14
|
+
css: true
|
|
15
|
+
},
|
|
16
|
+
defaultOptions: [{ allowedSelectors: [] }],
|
|
17
|
+
schema: [{
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
allowedSelectors: {
|
|
21
|
+
oneOf: [
|
|
22
|
+
{ type: 'string', description: 'A single selector' },
|
|
23
|
+
{ type: 'array', items: { type: 'string' }, description: 'List of selectors' }
|
|
24
|
+
],
|
|
25
|
+
description: 'Selectors to explicitly allow (e.g. `body::-webkit-scrollbar`)'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false
|
|
29
|
+
}],
|
|
30
|
+
messages: {
|
|
31
|
+
noScrollbarStyling: 'Scrollbar pseudo-element on `{{selector}}` styles the host page\'s scrollbar. '
|
|
32
|
+
+ 'Scope it to a namespaced class (e.g. `.myext-panel::...`) or use `allowedSelectors`.'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
create(context) {
|
|
37
|
+
const opts = context.options[0] || {},
|
|
38
|
+
allowedSelectors = new Set(array.to(opts.allowedSelectors))
|
|
39
|
+
function checkSelector(selector) {
|
|
40
|
+
if (!selector) return
|
|
41
|
+
const text = getSelectorText(selector).trim() ; if (allowedSelectors.has(text)) return
|
|
42
|
+
let hasScrollbarPseudo = false,
|
|
43
|
+
hasQualifier = false,
|
|
44
|
+
pastFirstCompound = false
|
|
45
|
+
for (const simple of selector.children ?? []) {
|
|
46
|
+
const type = node.plainType(simple)
|
|
47
|
+
if (type == 'Combinator') { pastFirstCompound = true; continue }
|
|
48
|
+
if (type == 'PseudoElementSelector' && re.scrollbarPseudo.test(simple.name ?? '')) {
|
|
49
|
+
hasScrollbarPseudo = true
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
if (pastFirstCompound) continue
|
|
53
|
+
if (['AttributeSelector', 'ClassSelector', 'IdSelector'].includes(type)
|
|
54
|
+
|| (type == 'PseudoClassSelector' && (simple.name ?? '').toLowerCase().startsWith('host'))
|
|
55
|
+
) hasQualifier = true
|
|
56
|
+
}
|
|
57
|
+
if (hasScrollbarPseudo && !hasQualifier)
|
|
58
|
+
context.report({ node: selector, messageId: 'noScrollbarStyling', data: { selector: text }})
|
|
59
|
+
}
|
|
60
|
+
function getSelectorText(selector) {
|
|
61
|
+
const { range } = selector
|
|
62
|
+
if (range?.length == 2) return context.sourceCode.text.slice(range[0], range[1])
|
|
63
|
+
return context.sourceCode.getText(selector)
|
|
64
|
+
}
|
|
65
|
+
function handleRule(node) {
|
|
66
|
+
for (const selector of node.prelude?.children ?? [])
|
|
67
|
+
checkSelector(selector)
|
|
68
|
+
}
|
|
69
|
+
return { Rule: handleRule, CSSRule: handleRule }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -58,7 +58,7 @@ export default {
|
|
|
58
58
|
return {
|
|
59
59
|
'Program:exit'() {
|
|
60
60
|
for (const key of hostKeys) {
|
|
61
|
-
const patterns = manifest[key] //
|
|
61
|
+
const patterns = manifest[key] // oxlint-disable-line security/detect-object-injection
|
|
62
62
|
if (!Array.isArray(patterns)) continue
|
|
63
63
|
for (const pattern of patterns)
|
|
64
64
|
if (isBroad(pattern))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import apiUtils from '../utils/api.js'
|
|
1
2
|
import extension from '../utils/extension.js'
|
|
2
3
|
import fs from 'node:fs'
|
|
3
4
|
import path from 'node:path'
|
|
@@ -14,15 +15,15 @@ function collectUsedAPIs(manifest, root) {
|
|
|
14
15
|
if (manifest.action?.default_popup) files.add(manifest.action.default_popup)
|
|
15
16
|
if (manifest.options_ui?.page) files.add(manifest.options_ui.page)
|
|
16
17
|
if (manifest.devtools_page) files.add(manifest.devtools_page)
|
|
17
|
-
const re = { /*
|
|
18
|
+
const re = { /* oxlint-disable-next-line security/detect-unsafe-regex */
|
|
18
19
|
chromeAPI: /\bchrome\.\w+(?:\.\w+)*/g }
|
|
19
20
|
const used = new Set()
|
|
20
21
|
for (const file of files) {
|
|
21
22
|
try {
|
|
22
|
-
const src = //
|
|
23
|
+
const src = // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
23
24
|
fs.readFileSync(path.resolve(root, file), 'utf8')
|
|
24
25
|
for (const match of src.matchAll(re.chromeAPI)) used.add(match[0])
|
|
25
|
-
} catch {}
|
|
26
|
+
} catch {}
|
|
26
27
|
}
|
|
27
28
|
return used
|
|
28
29
|
}
|
|
@@ -54,7 +55,7 @@ export default {
|
|
|
54
55
|
if (perm == permission) { knownPerm = true ; break }
|
|
55
56
|
if (!knownPerm) return true
|
|
56
57
|
for (const api of usedAPIs)
|
|
57
|
-
if (
|
|
58
|
+
if (apiUtils.findPermission(api) == permission) return true
|
|
58
59
|
return false
|
|
59
60
|
}
|
|
60
61
|
if (!regex.filename.manifest.test(filename)) {
|
|
@@ -7,7 +7,6 @@ export default {
|
|
|
7
7
|
type: 'problem',
|
|
8
8
|
docs: {
|
|
9
9
|
description: 'Requires a license file in the extension',
|
|
10
|
-
recommended: false,
|
|
11
10
|
manifest: true
|
|
12
11
|
},
|
|
13
12
|
schema: [],
|
|
@@ -20,7 +19,7 @@ export default {
|
|
|
20
19
|
const { filename } = context
|
|
21
20
|
function check(node) {
|
|
22
21
|
const pkgRoot = pkg.findRoot(path.dirname(filename))
|
|
23
|
-
if (!pkgRoot || !license.find(pkgRoot)) //
|
|
22
|
+
if (!pkgRoot || !license.find(pkgRoot)) // oxlint-disable-line e18e/prefer-array-some
|
|
24
23
|
context.report({ node, messageId: 'missingFile' })
|
|
25
24
|
}
|
|
26
25
|
return { Program: check, Document: check }
|
package/src/rules/require-mv3.js
CHANGED
|
@@ -32,7 +32,8 @@ export default {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
const manifest = extension.manifest.read(context)
|
|
36
|
-
|
|
35
|
+
const manifest = extension.manifest.read(context)
|
|
36
|
+
if (!manifest || manifest.manifest_version == 3) return {}
|
|
37
|
+
return { Program(node) { context.report({ node, messageId: 'notMV3' })}}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* oxlint-disable security/detect-non-literal-fs-filename -- lint-time project inspection only */
|
|
2
2
|
|
|
3
3
|
import extension from '../utils/extension.js'
|
|
4
4
|
import fs from 'node:fs'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
|
|
7
|
-
const re = { invalidChars: /[^\w\-.]
|
|
7
|
+
const re = { invalidChars: /[^\w\-.]/ }
|
|
8
8
|
|
|
9
9
|
const refKeys = [
|
|
10
10
|
['action.default_popup', manifest => manifest.action?.default_popup],
|
|
@@ -112,7 +112,7 @@ export default {
|
|
|
112
112
|
segments = path.relative(extRoot, filename).split(path.sep)
|
|
113
113
|
if (noReservedPrefix)
|
|
114
114
|
for (const seg of segments)
|
|
115
|
-
if (
|
|
115
|
+
if (seg.startsWith('_') && seg != '_locales')
|
|
116
116
|
context.report({ loc: { line: 1, column: 1 }, messageId: 'reservedPrefix', data: { name: seg }})
|
|
117
117
|
if (noSpecialChars)
|
|
118
118
|
for (const seg of segments)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import array from '../utils/array.js'
|
|
1
2
|
import license from '../utils/license.js'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
|
|
4
5
|
import regex from '../data/regex.js'
|
|
5
6
|
|
|
6
|
-
function toArray(value) { return value == null ? [] : Array.isArray(value) ? value : [value] }
|
|
7
|
-
|
|
8
7
|
function validateContent(context, node, { requireSPDX, allowedLicenses }) {
|
|
9
8
|
const text = context.sourceCode.text,
|
|
10
9
|
id = license.spdx.parse(text)
|
|
@@ -28,7 +27,6 @@ export default {
|
|
|
28
27
|
fixable: 'code',
|
|
29
28
|
docs: {
|
|
30
29
|
description: 'Validates SPDX identifiers in license files',
|
|
31
|
-
recommended: false,
|
|
32
30
|
license: true
|
|
33
31
|
},
|
|
34
32
|
defaultOptions: [{ requireSPDX: false, allowedLicenses: [] }],
|
|
@@ -56,7 +54,7 @@ export default {
|
|
|
56
54
|
create(context) {
|
|
57
55
|
const opts = context.options[0],
|
|
58
56
|
{ requireSPDX } = opts,
|
|
59
|
-
allowedLicenses =
|
|
57
|
+
allowedLicenses = array.to(opts.allowedLicenses),
|
|
60
58
|
{ filename } = context,
|
|
61
59
|
basename = path.basename(filename)
|
|
62
60
|
if (!regex.filename.license.test(basename) || regex.filename.script.test(basename))
|
|
@@ -20,7 +20,7 @@ function validateIcons(context, icons, root, opts, loc) {
|
|
|
20
20
|
if (typeof file != 'string') continue
|
|
21
21
|
if (checkSVG && re.svg.test(file))
|
|
22
22
|
context.report({ loc, messageId: 'svgNotSupported' })
|
|
23
|
-
else if (checkFiles && root //
|
|
23
|
+
else if (checkFiles && root // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
24
24
|
&& !fs.existsSync(path.resolve(root, file))
|
|
25
25
|
) context.report({ loc, messageId: 'missingFile', data: { file }})
|
|
26
26
|
}
|
|
@@ -98,7 +98,7 @@ export default {
|
|
|
98
98
|
if (typeof file != 'string') continue
|
|
99
99
|
if (checkSVG && re.svg.test(file))
|
|
100
100
|
context.report({ node: fileNode, messageId: 'svgNotSupported' })
|
|
101
|
-
else if (checkFiles && root //
|
|
101
|
+
else if (checkFiles && root // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
102
102
|
&& !fs.existsSync(path.resolve(root, file))
|
|
103
103
|
) context.report({ node: fileNode, messageId: 'missingFile', data: { file }})
|
|
104
104
|
}
|
|
@@ -85,10 +85,10 @@ export default {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
const manifest = extension.manifest.read(context) ; if (
|
|
88
|
+
const manifest = extension.manifest.read(context) ; if (manifest?.manifest_version != 3) return {}
|
|
89
89
|
const isFF = manifest.browser_specific_settings != null
|
|
90
|
-
return {
|
|
91
|
-
Program(node) {
|
|
90
|
+
return { // oxlint-disable security/detect-object-injection
|
|
91
|
+
Program(node) {
|
|
92
92
|
for (const key of Object.keys(manifest))
|
|
93
93
|
if (manifestData.keys.mv2Only.has(key))
|
|
94
94
|
context.report({ node, messageId: 'mv2Key', data: { key, reason: 'MV2-only key' }})
|
|
@@ -117,7 +117,7 @@ export default {
|
|
|
117
117
|
if (!set.has(value))
|
|
118
118
|
context.report({ node, messageId: 'invalidDataCollectionValue', data: { value, kind }})
|
|
119
119
|
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
120
|
+
}
|
|
121
|
+
} // oxlint-enable security/detect-object-injection
|
|
122
122
|
}
|
|
123
123
|
}
|
|
@@ -6,7 +6,7 @@ import regex from '../data/regex.js'
|
|
|
6
6
|
const re = {
|
|
7
7
|
absoluteURL: /^[a-z][a-z0-9+.-]*:\/\/[^\s/]+/i,
|
|
8
8
|
match: /^(\*|https?|file|ftp):\/\/([^/]*)(\/.*)$/,
|
|
9
|
-
template: /\{[^}]+\}/g
|
|
9
|
+
template: /\{[^{}]+\}/g
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function collectStrings(obj, path = [], out = []) {
|
|
@@ -15,7 +15,7 @@ function collectStrings(obj, path = [], out = []) {
|
|
|
15
15
|
else if (Array.isArray(obj))
|
|
16
16
|
for (const elem of obj) collectStrings(elem, path, out)
|
|
17
17
|
else if (typeof obj == 'object')
|
|
18
|
-
for (const key of Object.keys(obj)) //
|
|
18
|
+
for (const key of Object.keys(obj)) // oxlint-disable-next-line security/detect-object-injection
|
|
19
19
|
collectStrings(obj[key], [...path, key], out)
|
|
20
20
|
return out
|
|
21
21
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import extension from '../utils/extension.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
-
const re = {
|
|
4
|
+
const re = { // oxlint-disable security/detect-unsafe-regex
|
|
5
5
|
chromeParts: /^\d+(?:\.\d+){0,3}$/,
|
|
6
6
|
date: /^\d{4}\.(?:0?[1-9]|1[0-2])\.(?:0?[1-9]|[12]\d|3[01])(?:\.\d+)?$/,
|
|
7
7
|
semver: /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/
|
|
8
|
-
}
|
|
8
|
+
} // oxlint-enable security/detect-unsafe-regex
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
meta: {
|
package/src/utils/api.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import apis from '../data/apis.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
findDeprecated(api) {
|
|
5
|
+
for (const [deprecated, replacement] of apis.deprecated)
|
|
6
|
+
if (api == deprecated || api.startsWith(`${deprecated}.`))
|
|
7
|
+
return { deprecated, replacement }
|
|
8
|
+
return null
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
findPermission(api) {
|
|
12
|
+
if (apis.permless.has(api)) return null
|
|
13
|
+
for (const [prefix, permission] of apis.permissionMap)
|
|
14
|
+
if (api == prefix || api.startsWith(`${prefix}.`))
|
|
15
|
+
return permission
|
|
16
|
+
return null
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
isExternalMsgListener(api) { return apis.externalMsgListeners.has(api) }
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default { to(val) { return Array.isArray(val) ? val : val == null ? [] : [val] }}
|
package/src/utils/extension.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// oxlint-disable security/detect-non-literal-fs-filename
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
@@ -40,8 +40,6 @@ const utils = {
|
|
|
40
40
|
|
|
41
41
|
manifest: {
|
|
42
42
|
permission: {
|
|
43
|
-
valid: permissions.valid,
|
|
44
|
-
sensitive: permissions.sensitive,
|
|
45
43
|
isValid(name, target = 'mv3') {
|
|
46
44
|
if (permissions.valid.has(name)) return true
|
|
47
45
|
if (target == 'mv3') return false
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import regex from '../data/regex.js'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
isHTML(text) { return text.trimStart().startsWith('<') },
|
|
5
|
+
|
|
6
|
+
isRemote(src, allowedPrefixes) {
|
|
7
|
+
if (!src || allowedPrefixes.some(prefix => src.startsWith(prefix))) return false
|
|
8
|
+
return regex.scheme.test(src)
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
offsetToLoc(text, offset) {
|
|
12
|
+
let line = 1, column = 1
|
|
13
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
14
|
+
if (text[i] == '\n') { line++ ; column = 1 } // oxlint-disable-line security/detect-object-injection
|
|
15
|
+
else column++
|
|
16
|
+
}
|
|
17
|
+
return { line, column }
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
pickAttrVal(match) { return match ? (match[1] ?? match[2] ?? match[3] ?? '') : null },
|
|
21
|
+
|
|
22
|
+
re: {
|
|
23
|
+
attr: {
|
|
24
|
+
data: /\sdata\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i,
|
|
25
|
+
src: /\ssrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i,
|
|
26
|
+
type: /\stype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/utils/i18n.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// oxlint-disable security/detect-non-literal-fs-filename
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
@@ -7,7 +7,7 @@ import i18n from '../data/i18n.js'
|
|
|
7
7
|
|
|
8
8
|
function templateToPattern(body) {
|
|
9
9
|
const parts = body.split(i18n.re.templateExpr).map(part => part.replace(i18n.re.specialChars, '\\$&'))
|
|
10
|
-
return new RegExp(`^${parts.join('.*')}$`) //
|
|
10
|
+
return new RegExp(`^${parts.join('.*')}$`) // oxlint-disable-line security/detect-non-literal-regexp
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export default {
|
package/src/utils/init.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import css from '@eslint/css'
|
|
2
2
|
import html from '@html-eslint/eslint-plugin'
|
|
3
3
|
import json from '@eslint/json'
|
|
4
|
+
import pkg from './pkg.js'
|
|
4
5
|
import rulesUtils from './rules.js'
|
|
5
6
|
import text from './text.js'
|
|
6
7
|
|
|
8
|
+
import cssSpacing from '../rules/css-spacing.js'
|
|
7
9
|
import manifestKeyOrder from '../rules/manifest-key-order.js'
|
|
8
10
|
import manifestSpacing from '../rules/manifest-spacing.js'
|
|
9
11
|
import noContentStyleLeak from '../rules/no-content-style-leak.js'
|
|
10
12
|
import noDeprecatedAPIs from '../rules/no-deprecated-apis.js'
|
|
11
13
|
import noDuplicateManifestKeys from '../rules/no-duplicate-manifest-keys.js'
|
|
14
|
+
import noGlobalKeyframes from '../rules/no-global-keyframes.js'
|
|
15
|
+
import noHostPageSelectors from '../rules/no-host-page-selectors.js'
|
|
16
|
+
import noInlineEventHandlers from '../rules/no-inline-event-handlers.js'
|
|
17
|
+
import noInlineScripts from '../rules/no-inline-scripts.js'
|
|
12
18
|
import noInsecureFetch from '../rules/no-insecure-fetch.js'
|
|
13
19
|
import noInsecureMessaging from '../rules/no-insecure-messaging.js'
|
|
14
20
|
import noInvalidLocales from '../rules/no-invalid-locales.js'
|
|
@@ -18,6 +24,9 @@ import noMissingI18nKeys from '../rules/no-missing-i18n-keys.js'
|
|
|
18
24
|
import noMissingPermissions from '../rules/no-missing-permissions.js'
|
|
19
25
|
import noMissingWebAccessibleResources from '../rules/no-missing-web-accessible-resources.js'
|
|
20
26
|
import noRemoteCode from '../rules/no-remote-code.js'
|
|
27
|
+
import noRemoteObjects from '../rules/no-remote-objects.js'
|
|
28
|
+
import noRemoteURLs from '../rules/no-remote-urls.js'
|
|
29
|
+
import noScrollbarStyling from '../rules/no-scrollbar-styling.js'
|
|
21
30
|
import noSensitivePermissions from '../rules/no-sensitive-permissions.js'
|
|
22
31
|
import noUnrestrictedHostPermissions from '../rules/no-unrestricted-host-permissions.js'
|
|
23
32
|
import noUnusedI18nKeys from '../rules/no-unused-i18n-keys.js'
|
|
@@ -33,15 +42,10 @@ import validServiceWorker from '../rules/valid-service-worker.js'
|
|
|
33
42
|
import validURLfields from '../rules/valid-url-fields.js'
|
|
34
43
|
import validVersionField from '../rules/valid-version-field.js'
|
|
35
44
|
|
|
36
|
-
const
|
|
45
|
+
const pkgName = pkg.getName(),
|
|
46
|
+
namespace = pkgName.replace(/^@[^/]+\//, '').replace(/^eslint-plugin-/, '')
|
|
37
47
|
|
|
38
48
|
export default {
|
|
39
|
-
bundleDeprecation(plugin, [ruleId, bundleNames], pluginName) { /* eslint-disable security/detect-object-injection */
|
|
40
|
-
for (const bundleName of bundleNames)
|
|
41
|
-
if (plugin.configs[bundleName])
|
|
42
|
-
plugin.configs[bundleName].rules[`${pluginName}/${ruleId}`] = 'error'
|
|
43
|
-
}, /* eslint-enable security/detect-object-injection */
|
|
44
|
-
|
|
45
49
|
configs(plugin) {
|
|
46
50
|
const plugins = { [namespace]: plugin },
|
|
47
51
|
rules = plugin.rules,
|
|
@@ -68,15 +72,20 @@ export default {
|
|
|
68
72
|
}
|
|
69
73
|
},
|
|
70
74
|
|
|
71
|
-
plugin() { return { rules: {}, configs: {} }},
|
|
75
|
+
plugin() { return { meta: { name: pkgName }, rules: {}, configs: {} }},
|
|
72
76
|
|
|
73
77
|
rules(plugin) {
|
|
74
78
|
plugin.rules = {
|
|
79
|
+
'css-spacing': cssSpacing,
|
|
75
80
|
'manifest-key-order': manifestKeyOrder,
|
|
76
81
|
'manifest-spacing': manifestSpacing,
|
|
77
82
|
'no-content-style-leak': noContentStyleLeak,
|
|
78
83
|
'no-deprecated-apis': noDeprecatedAPIs,
|
|
79
84
|
'no-duplicate-manifest-keys': noDuplicateManifestKeys,
|
|
85
|
+
'no-global-keyframes': noGlobalKeyframes,
|
|
86
|
+
'no-host-page-selectors': noHostPageSelectors,
|
|
87
|
+
'no-inline-event-handlers': noInlineEventHandlers,
|
|
88
|
+
'no-inline-scripts': noInlineScripts,
|
|
80
89
|
'no-insecure-fetch': noInsecureFetch,
|
|
81
90
|
'no-insecure-messaging': noInsecureMessaging,
|
|
82
91
|
'no-invalid-locales': noInvalidLocales,
|
|
@@ -86,6 +95,9 @@ export default {
|
|
|
86
95
|
'no-missing-permissions': noMissingPermissions,
|
|
87
96
|
'no-missing-web-accessible-resources': noMissingWebAccessibleResources,
|
|
88
97
|
'no-remote-code': noRemoteCode,
|
|
98
|
+
'no-remote-objects': noRemoteObjects,
|
|
99
|
+
'no-remote-urls': noRemoteURLs,
|
|
100
|
+
'no-scrollbar-styling': noScrollbarStyling,
|
|
89
101
|
'no-sensitive-permissions': noSensitivePermissions,
|
|
90
102
|
'no-unrestricted-host-permissions': noUnrestrictedHostPermissions,
|
|
91
103
|
'no-unused-i18n-keys': noUnusedI18nKeys,
|
|
@@ -102,6 +114,6 @@ export default {
|
|
|
102
114
|
'valid-version-field': validVersionField
|
|
103
115
|
}
|
|
104
116
|
for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
105
|
-
rule.meta.docs.url = `https://codeberg.org/adamlui
|
|
117
|
+
rule.meta.docs.url = `https://codeberg.org/adamlui/${pkgName}/#${ruleId}`
|
|
106
118
|
}
|
|
107
119
|
}
|
package/src/utils/license.js
CHANGED
package/src/utils/node.js
CHANGED
package/src/utils/pkg.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
|
|
@@ -11,11 +12,27 @@ export default {
|
|
|
11
12
|
current = parent
|
|
12
13
|
continue
|
|
13
14
|
}
|
|
14
|
-
if ( //
|
|
15
|
+
if ( // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
15
16
|
fs.existsSync(path.join(current, 'package.json'))) return current
|
|
16
17
|
const parent = path.dirname(current)
|
|
17
18
|
if (parent === current) return null
|
|
18
19
|
current = parent
|
|
19
20
|
}
|
|
20
|
-
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
getName() { // oxlint-disable security/detect-non-literal-fs-filename
|
|
24
|
+
let current = path.dirname(fileURLToPath(import.meta.url))
|
|
25
|
+
while (current) {
|
|
26
|
+
const pkgPath = path.join(current, 'package.json')
|
|
27
|
+
if (fs.existsSync(pkgPath))
|
|
28
|
+
try {
|
|
29
|
+
const { name } = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
30
|
+
if (name) return name
|
|
31
|
+
} catch { /* malformed, keep walking */ }
|
|
32
|
+
const parent = path.dirname(current)
|
|
33
|
+
if (parent === current) return null
|
|
34
|
+
current = parent
|
|
35
|
+
}
|
|
36
|
+
return null
|
|
37
|
+
} // oxlint-enable security/detect-non-literal-fs-filename
|
|
21
38
|
}
|
package/src/utils/rules.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
bundle(rules, predicate, pluginName) {
|
|
3
3
|
return Object.fromEntries(Object.entries(rules)
|
|
4
|
-
.filter(([, rule]) => predicate(rule))
|
|
4
|
+
.filter(([, rule]) => !rule.meta.deprecated && predicate(rule))
|
|
5
5
|
.map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
|
|
6
6
|
}
|
|
7
7
|
}
|