eslint-plugin-chrome-extensions 2.4.2 → 2.6.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 +272 -115
- package/package.json +6 -8
- package/src/data/apis.js +166 -115
- package/src/data/deprecations.js +5 -7
- package/src/data/i18n.js +19 -69
- package/src/data/manifest.js +61 -57
- package/src/data/permissions.js +23 -30
- package/src/data/regex.js +10 -0
- package/src/index.js +7 -124
- package/src/rules/manifest-key-order.js +8 -8
- package/src/rules/manifest-spacing.js +5 -5
- package/src/rules/no-content-style-leak.js +6 -4
- package/src/rules/no-deprecated-apis.js +4 -4
- package/src/rules/no-duplicate-manifest-keys.js +3 -4
- package/src/rules/no-insecure-messaging.js +4 -4
- package/src/rules/no-invalid-locales.js +89 -0
- package/src/rules/no-invalid-permissions.js +10 -13
- package/src/rules/no-missing-default-locale.js +44 -21
- package/src/rules/no-missing-i18n-keys.js +24 -25
- package/src/rules/no-missing-permissions.js +7 -6
- package/src/rules/no-missing-web-accessible-resources.js +7 -6
- package/src/rules/no-remote-code.js +12 -12
- package/src/rules/no-sensitive-permissions.js +75 -0
- package/src/rules/no-unrestricted-host-permissions.js +35 -19
- package/src/rules/no-unused-i18n-keys.js +11 -10
- package/src/rules/no-unused-permissions.js +31 -23
- package/src/rules/require-license-file.js +4 -5
- package/src/rules/require-mv3.js +5 -6
- package/src/rules/require-short-name-when-useful.js +4 -3
- package/src/rules/valid-filenames.js +3 -10
- package/src/rules/valid-license-file.js +18 -11
- package/src/rules/valid-manifest-icons.js +35 -6
- package/src/rules/valid-mv3-schema.js +22 -22
- package/src/rules/valid-service-worker.js +34 -13
- package/src/rules/valid-url-fields.js +49 -41
- package/src/rules/valid-version-field.js +5 -9
- package/src/utils/extension.js +52 -33
- package/src/utils/i18n.js +58 -0
- package/src/utils/init.js +107 -0
- package/src/utils/license.js +56 -0
- package/src/utils/node.js +20 -19
- package/src/utils/pkg.js +12 -10
- package/src/utils/rules.js +6 -4
- package/src/utils/text.js +13 -0
- package/src/data/license.js +0 -28
- package/src/data/text-parser.js +0 -11
- package/src/utils/bundle.js +0 -10
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import css from '@eslint/css'
|
|
2
|
+
import html from '@html-eslint/eslint-plugin'
|
|
3
|
+
import json from '@eslint/json'
|
|
4
|
+
import rulesUtils from './rules.js'
|
|
5
|
+
import text from './text.js'
|
|
6
|
+
|
|
7
|
+
import manifestKeyOrder from '../rules/manifest-key-order.js'
|
|
8
|
+
import manifestSpacing from '../rules/manifest-spacing.js'
|
|
9
|
+
import noContentStyleLeak from '../rules/no-content-style-leak.js'
|
|
10
|
+
import noDeprecatedAPIs from '../rules/no-deprecated-apis.js'
|
|
11
|
+
import noDuplicateManifestKeys from '../rules/no-duplicate-manifest-keys.js'
|
|
12
|
+
import noInsecureFetch from '../rules/no-insecure-fetch.js'
|
|
13
|
+
import noInsecureMessaging from '../rules/no-insecure-messaging.js'
|
|
14
|
+
import noInvalidLocales from '../rules/no-invalid-locales.js'
|
|
15
|
+
import noInvalidPermissions from '../rules/no-invalid-permissions.js'
|
|
16
|
+
import noMissingDefaultLocale from '../rules/no-missing-default-locale.js'
|
|
17
|
+
import noMissingI18nKeys from '../rules/no-missing-i18n-keys.js'
|
|
18
|
+
import noMissingPermissions from '../rules/no-missing-permissions.js'
|
|
19
|
+
import noMissingWebAccessibleResources from '../rules/no-missing-web-accessible-resources.js'
|
|
20
|
+
import noRemoteCode from '../rules/no-remote-code.js'
|
|
21
|
+
import noSensitivePermissions from '../rules/no-sensitive-permissions.js'
|
|
22
|
+
import noUnrestrictedHostPermissions from '../rules/no-unrestricted-host-permissions.js'
|
|
23
|
+
import noUnusedI18nKeys from '../rules/no-unused-i18n-keys.js'
|
|
24
|
+
import noUnusedPermissions from '../rules/no-unused-permissions.js'
|
|
25
|
+
import requireLicenseFile from '../rules/require-license-file.js'
|
|
26
|
+
import requireMV3 from '../rules/require-mv3.js'
|
|
27
|
+
import requireShortNameWhenUseful from '../rules/require-short-name-when-useful.js'
|
|
28
|
+
import validFilenames from '../rules/valid-filenames.js'
|
|
29
|
+
import validLicenseFile from '../rules/valid-license-file.js'
|
|
30
|
+
import validManifestIcons from '../rules/valid-manifest-icons.js'
|
|
31
|
+
import validMV3schema from '../rules/valid-mv3-schema.js'
|
|
32
|
+
import validServiceWorker from '../rules/valid-service-worker.js'
|
|
33
|
+
import validURLfields from '../rules/valid-url-fields.js'
|
|
34
|
+
import validVersionField from '../rules/valid-version-field.js'
|
|
35
|
+
|
|
36
|
+
const namespace = 'chrome-extensions'
|
|
37
|
+
|
|
38
|
+
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
|
+
configs(plugin) {
|
|
46
|
+
const plugins = { [namespace]: plugin },
|
|
47
|
+
rules = plugin.rules,
|
|
48
|
+
createConfig = predicate => ({ plugins, rules: rulesUtils.bundle(rules, predicate, namespace) })
|
|
49
|
+
plugin.configs = {
|
|
50
|
+
recommended: createConfig(rule => rule.meta.docs.recommended),
|
|
51
|
+
strict: createConfig(() => true),
|
|
52
|
+
problem: createConfig(rule => rule.meta.type == 'problem'),
|
|
53
|
+
security: createConfig(rule => rule.meta.docs.security),
|
|
54
|
+
manifest: {
|
|
55
|
+
...createConfig(rule => rule.meta.docs.manifest), language: 'json/json', plugins: { ...plugins, json }},
|
|
56
|
+
license: {
|
|
57
|
+
...createConfig(rule => rule.meta.docs.license), languageOptions: { parser: text.parser }},
|
|
58
|
+
css: {
|
|
59
|
+
...createConfig(rule => rule.meta.docs.css), language: 'css/css', plugins: { ...plugins, css }},
|
|
60
|
+
html: {
|
|
61
|
+
...createConfig(rule => rule.meta.docs.html), language: 'html/html', plugins: { ...plugins, html }},
|
|
62
|
+
json: {
|
|
63
|
+
...createConfig(rule => rule.meta.docs.json), language: 'json/json', plugins: { ...plugins, json }},
|
|
64
|
+
json5: {
|
|
65
|
+
...createConfig(rule => rule.meta.docs.json), language: 'json/json5', plugins: { ...plugins, json }},
|
|
66
|
+
jsonc: {
|
|
67
|
+
...createConfig(rule => rule.meta.docs.json), language: 'json/jsonc', plugins: { ...plugins, json }}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
plugin() { return { rules: {}, configs: {} }},
|
|
72
|
+
|
|
73
|
+
rules(plugin) {
|
|
74
|
+
plugin.rules = {
|
|
75
|
+
'manifest-key-order': manifestKeyOrder,
|
|
76
|
+
'manifest-spacing': manifestSpacing,
|
|
77
|
+
'no-content-style-leak': noContentStyleLeak,
|
|
78
|
+
'no-deprecated-apis': noDeprecatedAPIs,
|
|
79
|
+
'no-duplicate-manifest-keys': noDuplicateManifestKeys,
|
|
80
|
+
'no-insecure-fetch': noInsecureFetch,
|
|
81
|
+
'no-insecure-messaging': noInsecureMessaging,
|
|
82
|
+
'no-invalid-locales': noInvalidLocales,
|
|
83
|
+
'no-invalid-permissions': noInvalidPermissions,
|
|
84
|
+
'no-missing-default-locale': noMissingDefaultLocale,
|
|
85
|
+
'no-missing-i18n-keys': noMissingI18nKeys,
|
|
86
|
+
'no-missing-permissions': noMissingPermissions,
|
|
87
|
+
'no-missing-web-accessible-resources': noMissingWebAccessibleResources,
|
|
88
|
+
'no-remote-code': noRemoteCode,
|
|
89
|
+
'no-sensitive-permissions': noSensitivePermissions,
|
|
90
|
+
'no-unrestricted-host-permissions': noUnrestrictedHostPermissions,
|
|
91
|
+
'no-unused-i18n-keys': noUnusedI18nKeys,
|
|
92
|
+
'no-unused-permissions': noUnusedPermissions,
|
|
93
|
+
'require-mv3': requireMV3,
|
|
94
|
+
'require-license-file': requireLicenseFile,
|
|
95
|
+
'require-short-name-when-useful': requireShortNameWhenUseful,
|
|
96
|
+
'valid-filenames': validFilenames,
|
|
97
|
+
'valid-license-file': validLicenseFile,
|
|
98
|
+
'valid-manifest-icons': validManifestIcons,
|
|
99
|
+
'valid-mv3-schema': validMV3schema,
|
|
100
|
+
'valid-service-worker': validServiceWorker,
|
|
101
|
+
'valid-url-fields': validURLfields,
|
|
102
|
+
'valid-version-field': validVersionField
|
|
103
|
+
}
|
|
104
|
+
for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
105
|
+
rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-${namespace}/#${ruleId}`
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable security/detect-non-literal-fs-filename -- lint-time test fixtures only */
|
|
2
|
+
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
import regex from '../data/regex.js'
|
|
8
|
+
|
|
9
|
+
const re = { spdxTag: /SPDX-License-Identifier:\s*([\w.-]+)/i },
|
|
10
|
+
require = createRequire(import.meta.url),
|
|
11
|
+
skipDirs = new Set(['node_modules']),
|
|
12
|
+
spdxIDs = new Set(require('spdx-license-ids')),
|
|
13
|
+
spdxNames = Object.entries(require('spdx-license-list'))
|
|
14
|
+
.map(([id, { name }]) => ({ id, name }))
|
|
15
|
+
.sort((a, b) => b.name.length - a.name.length)
|
|
16
|
+
|
|
17
|
+
function tryReaddir(dir) { try { return fs.readdirSync(dir, { withFileTypes: true }) } catch { return null }}
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
find(root) {
|
|
21
|
+
const queue = [root]
|
|
22
|
+
while (queue.length) {
|
|
23
|
+
const dir = queue.shift(), entries = tryReaddir(dir)
|
|
24
|
+
if (!entries) continue
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (entry.name.startsWith('.') || skipDirs.has(entry.name)) continue
|
|
27
|
+
if (entry.isFile() && regex.filename.license.test(entry.name)) return path.join(dir, entry.name)
|
|
28
|
+
if (entry.isDirectory()) queue.push(path.join(dir, entry.name))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
spdx: {
|
|
35
|
+
appendTag(fixer, text, id) {
|
|
36
|
+
const nl = text.includes('\r\n') ? '\r\n' : '\n',
|
|
37
|
+
trailer = `${text.endsWith(nl) ? '' : nl}${nl}SPDX-License-Identifier: ${id}${nl}`
|
|
38
|
+
return fixer.insertTextAfterRange([0, text.length], trailer)
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
find(text) {
|
|
42
|
+
const normalizedTxt = text.replace(/\s+/g, ' ')
|
|
43
|
+
let best = null
|
|
44
|
+
for (const { id, name } of spdxNames) {
|
|
45
|
+
const idx = normalizedTxt.indexOf(name)
|
|
46
|
+
if (idx == -1) continue
|
|
47
|
+
if (!best || idx < best.idx || (idx == best.idx && name.length > best.name.length))
|
|
48
|
+
best = { id, idx, name }
|
|
49
|
+
}
|
|
50
|
+
return best?.id ?? null
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
isValid(id) { return spdxIDs.has(id) },
|
|
54
|
+
parse(text) { return text.match(re.spdxTag)?.[1] ?? null }
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/utils/node.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
1
|
+
export default {
|
|
2
|
+
getMemberPath(node) {
|
|
3
|
+
const parts = [] ; let currentNode = node
|
|
4
|
+
while (currentNode?.type == 'MemberExpression') {
|
|
5
|
+
if (currentNode.computed || currentNode.property.type != 'Identifier') return null
|
|
6
|
+
parts.unshift(currentNode.property.name)
|
|
7
|
+
currentNode = currentNode.object
|
|
8
|
+
}
|
|
9
|
+
if (currentNode?.type != 'Identifier') return null
|
|
10
|
+
parts.unshift(currentNode.name)
|
|
11
|
+
return parts.join('.')
|
|
12
|
+
},
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
isInsideNestedFunction(node) {
|
|
15
|
+
let ancestor = node.parent
|
|
16
|
+
while (ancestor) {
|
|
17
|
+
if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(ancestor.type))
|
|
18
|
+
return true
|
|
19
|
+
ancestor = ancestor.parent
|
|
20
|
+
}
|
|
21
|
+
return false
|
|
20
22
|
}
|
|
21
|
-
return false
|
|
22
23
|
}
|
package/src/utils/pkg.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export default {
|
|
5
|
+
findRoot(dir) {
|
|
6
|
+
let current = path.resolve(dir)
|
|
7
|
+
while (true) {
|
|
8
|
+
if (current.split(path.sep).includes('node_modules')) {
|
|
9
|
+
const parent = path.dirname(current)
|
|
10
|
+
if (parent === current) return null
|
|
11
|
+
current = parent
|
|
12
|
+
continue
|
|
13
|
+
}
|
|
14
|
+
if ( // eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
15
|
+
fs.existsSync(path.join(current, 'package.json'))) return current
|
|
8
16
|
const parent = path.dirname(current)
|
|
9
17
|
if (parent === current) return null
|
|
10
18
|
current = parent
|
|
11
|
-
continue
|
|
12
19
|
}
|
|
13
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
14
|
-
if (fs.existsSync(path.join(current, 'package.json'))) return current
|
|
15
|
-
const parent = path.dirname(current)
|
|
16
|
-
if (parent === current) return null
|
|
17
|
-
current = parent
|
|
18
20
|
}
|
|
19
21
|
}
|
package/src/utils/rules.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
.
|
|
4
|
-
|
|
1
|
+
export default {
|
|
2
|
+
bundle(rules, predicate, pluginName) {
|
|
3
|
+
return Object.fromEntries(Object.entries(rules)
|
|
4
|
+
.filter(([, rule]) => predicate(rule))
|
|
5
|
+
.map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
|
|
6
|
+
}
|
|
5
7
|
}
|
package/src/data/license.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
export const re = {
|
|
5
|
-
licenseFilename: // e.g. LICENSE.md, LICENSE-MIT, MIT-LICENSE.md, COPYING.LESSER, LICENSE-2.0.txt
|
|
6
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
7
|
-
/^(?!.*\.(?:[cm]?js|[cm]?ts)x?$)(?:[\w.-]*-)?(?:LICEN[SC]E|COPYING)(?:[-.][\w.-]+)?$/i,
|
|
8
|
-
spdxTag: /SPDX-License-Identifier:\s*([\w.+-]+)/i
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const skipDirs = new Set(['node_modules'])
|
|
12
|
-
|
|
13
|
-
export function findLicenseFile(root) {
|
|
14
|
-
const queue = [root]
|
|
15
|
-
while (queue.length) {
|
|
16
|
-
const dir = queue.shift(), entries = tryReaddir(dir)
|
|
17
|
-
if (!entries) continue
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
if (entry.name.startsWith('.') || skipDirs.has(entry.name)) continue
|
|
20
|
-
if (entry.isFile() && re.licenseFilename.test(entry.name)) return path.join(dir, entry.name)
|
|
21
|
-
if (entry.isDirectory()) queue.push(path.join(dir, entry.name))
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return null
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function tryReaddir(dir) { // eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
28
|
-
try { return fs.readdirSync(dir, { withFileTypes: true }) } catch { return null }}
|
package/src/data/text-parser.js
DELETED
package/src/utils/bundle.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function initDeprecation(plugin, [ruleId, bundleNames], pluginName) {
|
|
2
|
-
for (const bundleName of bundleNames) {
|
|
3
|
-
/* eslint-disable security/detect-object-injection */
|
|
4
|
-
if (plugin.configs[bundleName])
|
|
5
|
-
plugin.configs[bundleName].rules[`${pluginName}/${ruleId}`] = 'error'
|
|
6
|
-
/* eslint-enable security/detect-object-injection */
|
|
7
|
-
if (process.env.DEBUG_MODE)
|
|
8
|
-
console.warn(`[${pluginName}] '${ruleId}' will be removed from '${bundleName}' in a future major release`)
|
|
9
|
-
}
|
|
10
|
-
}
|