eslint-plugin-chrome-extensions 2.4.1 → 2.5.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 +101 -23
- package/package.json +4 -3
- package/src/data/apis.js +166 -115
- package/src/data/deprecations.js +7 -7
- package/src/data/i18n.js +11 -69
- package/src/data/manifest.js +61 -57
- package/src/data/permissions.js +29 -29
- package/src/data/regex.js +10 -0
- package/src/index.js +7 -123
- 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-permissions.js +10 -11
- package/src/rules/no-missing-default-locale.js +7 -9
- package/src/rules/no-missing-i18n-keys.js +22 -20
- package/src/rules/no-missing-permissions.js +7 -5
- package/src/rules/no-missing-web-accessible-resources.js +7 -5
- 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 +8 -7
- package/src/rules/no-unused-i18n-keys.js +11 -10
- package/src/rules/no-unused-permissions.js +22 -23
- package/src/rules/require-license-file.js +9 -8
- package/src/rules/require-mv3.js +4 -5
- 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 +6 -5
- package/src/rules/valid-mv3-schema.js +21 -21
- package/src/rules/valid-service-worker.js +36 -13
- package/src/rules/valid-url-fields.js +10 -7
- package/src/rules/valid-version-field.js +4 -8
- package/src/utils/extension.js +51 -0
- package/src/utils/i18n.js +55 -0
- package/src/utils/init.js +105 -0
- package/src/utils/license.js +55 -0
- package/src/utils/node.js +23 -0
- package/src/utils/pkg.js +21 -0
- package/src/utils/rules.js +7 -0
- package/src/utils/text.js +13 -0
- package/src/data/license.js +0 -27
- package/src/data/text-parser.js +0 -11
- package/src/utils.js +0 -95
package/src/data/i18n.js
CHANGED
|
@@ -1,71 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function collectUsedKeys(root, scanExtensions) {
|
|
15
|
-
const used = new Set(),
|
|
16
|
-
patterns = [],
|
|
17
|
-
exts = new Set(scanExtensions),
|
|
18
|
-
stack = [root]
|
|
19
|
-
while (stack.length) {
|
|
20
|
-
const dir = stack.pop()
|
|
21
|
-
let entries
|
|
22
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { continue }
|
|
23
|
-
for (const entry of entries) {
|
|
24
|
-
if (entry.name.startsWith('.') || entry.name == 'node_modules') continue
|
|
25
|
-
const full = path.join(dir, entry.name)
|
|
26
|
-
if (entry.isDirectory()) { stack.push(full) ; continue }
|
|
27
|
-
const ext = path.extname(entry.name).slice(1).toLowerCase()
|
|
28
|
-
if (!exts.has(ext)) continue
|
|
29
|
-
let text
|
|
30
|
-
try { text = fs.readFileSync(full, 'utf8') } catch { continue }
|
|
31
|
-
for (const match of text.matchAll(re.msgPlaceholder))
|
|
32
|
-
if (!isPredefined(match[1])) used.add(match[1])
|
|
33
|
-
for (const match of text.matchAll(re.getMsgLiteral))
|
|
34
|
-
used.add(match[1])
|
|
35
|
-
for (const match of text.matchAll(re.getMsgTemplate)) {
|
|
36
|
-
const body = match[1]
|
|
37
|
-
if (body.includes('${')) patterns.push(templateToPattern(body))
|
|
38
|
-
else used.add(body)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return { used, patterns }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const defaultScanExtensions = [
|
|
46
|
-
'cjs', 'css', 'htm', 'html', 'js', 'json', 'jsx', 'md', 'mjs', 'svg', 'ts', 'tsx', 'txt']
|
|
47
|
-
|
|
48
|
-
export function findExtensionRoot(startPath) {
|
|
49
|
-
let dir = path.dirname(startPath)
|
|
50
|
-
while (dir != path.dirname(dir)) {
|
|
51
|
-
if (fs.existsSync(path.join(dir, 'manifest.json'))) return dir
|
|
52
|
-
dir = path.dirname(dir)
|
|
1
|
+
export default {
|
|
2
|
+
defaultScanExtensions: ['cjs', 'css', 'htm', 'html', 'js', 'json', 'jsx', 'md', 'mjs', 'svg', 'ts', 'tsx', 'txt'],
|
|
3
|
+
re: {
|
|
4
|
+
getMsg: {
|
|
5
|
+
literal: /\b(?:browser|chrome)\.i18n\.getMessage\(\s*['"]([^'"]+)['"]/g,
|
|
6
|
+
path: /^(?:browser|chrome)\.i18n\.getMessage$/,
|
|
7
|
+
template: /\b(?:browser|chrome)\.i18n\.getMessage\(\s*`([^`]*)`/g
|
|
8
|
+
},
|
|
9
|
+
msgPlaceholder: /__MSG_([\w@]+)__/g,
|
|
10
|
+
specialChars: /[.*+?^${}()|[\]\\]/g,
|
|
11
|
+
templateExpr: /\$\{[^}]*\}/g
|
|
53
12
|
}
|
|
54
13
|
}
|
|
55
|
-
|
|
56
|
-
export function isPredefined(key) { return key.startsWith('@@') }
|
|
57
|
-
|
|
58
|
-
export function readDefaultLocaleMessages(manifest, root) {
|
|
59
|
-
const locale = manifest?.default_locale
|
|
60
|
-
if (typeof locale != 'string') return null
|
|
61
|
-
const filePath = path.join(root, '_locales', locale, 'messages.json')
|
|
62
|
-
let messages
|
|
63
|
-
try { messages = JSON.parse(fs.readFileSync(filePath, 'utf8')) } catch { return null }
|
|
64
|
-
if (!messages || typeof messages != 'object' || Array.isArray(messages)) return null
|
|
65
|
-
return { locale, messages }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function templateToPattern(body) {
|
|
69
|
-
const parts = body.split(re.templateExpr).map(part => part.replace(re.specialChars, '\\$&'))
|
|
70
|
-
return new RegExp(`^${parts.join('.*')}$`) // eslint-disable-line security/detect-non-literal-regexp
|
|
71
|
-
}
|
package/src/data/manifest.js
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
1
|
import globals from 'globals'
|
|
2
2
|
|
|
3
3
|
const serviceWorkerGlobals = new Set(Object.keys(globals.serviceworker || {}))
|
|
4
|
-
export const domGlobals = new Set(Object.keys(globals.browser).filter(name => !serviceWorkerGlobals.has(name)))
|
|
5
4
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
5
|
+
export default {
|
|
6
|
+
csp: {
|
|
7
|
+
forbiddenDirectives: [
|
|
8
|
+
`'unsafe-eval'`, `'unsafe-inline'`, `'unsafe-hashes'`, `'wasm-unsafe-eval'`, 'http://', 'https://']
|
|
9
|
+
},
|
|
10
|
+
globals: {
|
|
11
|
+
dom: new Set(Object.keys(globals.browser).filter(name => !serviceWorkerGlobals.has(name))) },
|
|
12
|
+
keys: {
|
|
13
|
+
ff: {
|
|
14
|
+
dataCollectionOptional: new Set([
|
|
15
|
+
'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo',
|
|
16
|
+
'healthInfo', 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo',
|
|
17
|
+
'searchTerms', 'technicalAndInteraction', 'websiteActivity', 'websiteContent'
|
|
18
|
+
]),
|
|
19
|
+
dataCollectionRequired: new Set([
|
|
20
|
+
'none', 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo',
|
|
21
|
+
'healthInfo', 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms',
|
|
22
|
+
'websiteActivity', 'websiteContent'
|
|
23
|
+
]),
|
|
24
|
+
mv3BG: new Set(['scripts', 'page'])
|
|
25
|
+
},
|
|
26
|
+
mv2Only: new Set(['browser_action', 'page_action']),
|
|
27
|
+
order: [
|
|
28
|
+
// platform-required
|
|
29
|
+
'manifest_version', 'name', 'version',
|
|
30
|
+
// CWS-required
|
|
31
|
+
'description', 'icons',
|
|
32
|
+
// optional
|
|
33
|
+
'action', 'author', 'background', 'browser_specific_settings', 'chrome_settings_overrides',
|
|
34
|
+
'chrome_url_overrides', 'commands', 'content_scripts', 'content_security_policy',
|
|
35
|
+
'cross_origin_embedder_policy', 'cross_origin_opener_policy', 'dark_theme', 'declarative_net_request',
|
|
36
|
+
'default_locale', 'developer', 'devtools_page', 'dictionaries', 'export', 'externally_connectable',
|
|
37
|
+
'homepage_url', 'host_permissions', 'import', 'incognito', 'key', 'mime_types_handler',
|
|
38
|
+
'minimum_chrome_version', 'oauth2', 'omnibox', 'optional_host_permissions', 'optional_permissions',
|
|
39
|
+
'options_page', 'options_ui', 'permissions', 'protocol_handlers', 'requirements', 'sandbox',
|
|
40
|
+
'short_name', 'sidebar_action', 'side_panel', 'storage', 'theme', 'theme_experiment', 'tts_engine',
|
|
41
|
+
'update_url', 'version_name', 'web_accessible_resources'
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
paths: {
|
|
45
|
+
match: new Set([
|
|
46
|
+
'content_scripts.matches', 'externally_connectable.matches', 'host_permissions',
|
|
47
|
+
'optional_host_permissions', 'web_accessible_resources.matches'
|
|
48
|
+
]),
|
|
49
|
+
url: new Set([
|
|
50
|
+
'homepage_url', 'update_url', 'oauth2.scopes',
|
|
51
|
+
'chrome_settings_overrides.homepage',
|
|
52
|
+
'chrome_settings_overrides.search_provider.alternate_urls',
|
|
53
|
+
'chrome_settings_overrides.search_provider.favicon_url',
|
|
54
|
+
'chrome_settings_overrides.search_provider.image_url',
|
|
55
|
+
'chrome_settings_overrides.search_provider.instant_url',
|
|
56
|
+
'chrome_settings_overrides.search_provider.search_url',
|
|
57
|
+
'chrome_settings_overrides.search_provider.suggest_url',
|
|
58
|
+
'chrome_settings_overrides.startup_pages'
|
|
59
|
+
])
|
|
60
|
+
},
|
|
61
|
+
patterns: {
|
|
62
|
+
remoteCode: [
|
|
63
|
+
'eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/data/permissions.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import regex from './regex.js'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
3
|
+
export default {
|
|
4
|
+
sensitive: new Set([
|
|
5
|
+
'accessibilityFeatures.modify', 'accessibilityFeatures.read', 'bookmarks', 'browsingData', 'clipboardRead',
|
|
6
|
+
'contentSettings', 'cookies', 'debugger', 'declarativeNetRequest', 'downloads', 'downloads.open',
|
|
7
|
+
'downloads.ui', 'geolocation', 'history', 'management', 'nativeMessaging', 'pageCapture', 'privacy',
|
|
8
|
+
'proxy', 'readingList', 'sessions', 'system.storage', 'tabCapture', 'tabGroups', 'tabs', 'topSites',
|
|
9
|
+
'ttsEngine', 'webAuthenticationProxy', 'webNavigation', 'webRequest', 'webRequestBlocking'
|
|
10
|
+
]),
|
|
11
|
+
valid: new Set([
|
|
12
|
+
'accessibilityFeatures.modify', 'accessibilityFeatures.read', 'activeTab', 'alarms', 'audio', 'background',
|
|
13
|
+
'bookmarks', 'browsingData', 'certificateProvider', 'clipboardRead', 'clipboardWrite', 'contentSettings',
|
|
14
|
+
'contextMenus', 'cookies', 'debugger', 'declarativeContent', 'declarativeNetRequest',
|
|
15
|
+
'declarativeNetRequestFeedback', 'declarativeNetRequestWithHostAccess', 'desktopCapture', 'dns',
|
|
16
|
+
'documentScan', 'downloads', 'downloads.open', 'downloads.ui', 'enterprise.deviceAttributes',
|
|
17
|
+
'enterprise.hardwarePlatform', 'enterprise.networkingAttributes', 'enterprise.platformKeys', 'favicon',
|
|
18
|
+
'fileBrowserHandler', 'fileSystemProvider', 'fontSettings', 'gcm', 'geolocation', 'history', 'identity',
|
|
19
|
+
'identity.email', 'idle', 'loginState', 'management', 'menus', 'nativeMessaging', 'notifications',
|
|
20
|
+
'offscreen', 'pageCapture', 'platformKeys', 'power', 'printerProvider', 'printing', 'printingMetrics',
|
|
21
|
+
'privacy', 'processes', 'proxy', 'publicSuffix', 'readingList', 'runtime', 'scripting', 'search',
|
|
22
|
+
'sessions', 'sidePanel', 'storage', 'system.cpu', 'system.display', 'system.memory', 'system.storage',
|
|
23
|
+
'tabCapture', 'tabGroups', 'tabs', 'topSites', 'tts', 'ttsEngine', 'unlimitedStorage', 'userScripts',
|
|
24
|
+
'vpnProvider', 'wallpaper', 'webAuthenticationProxy', 'webNavigation', 'webRequest', 'webRequestBlocking'
|
|
25
|
+
]),
|
|
26
|
+
isValid(name, target = 'mv3') {
|
|
27
|
+
if (this.valid.has(name)) return true
|
|
28
|
+
if (target == 'mv3') return false
|
|
29
|
+
return regex.url.test(name) || name.startsWith('<') || name == '*://*/*'
|
|
30
|
+
}
|
|
31
31
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
filename: {
|
|
3
|
+
cssHTML: /\.(css|htm|html)$/i,
|
|
4
|
+
license: // eslint-disable-next-line security/detect-unsafe-regex
|
|
5
|
+
/^(?:[\w.-]*-)?(?:LICEN[SC]E|COPYING)(?:[-.][\w.-]+)?$/i,
|
|
6
|
+
manifest: /manifest\.json$/,
|
|
7
|
+
script: /\.[cm]?[jt]sx?$/i
|
|
8
|
+
},
|
|
9
|
+
url: /^https?:\/\//
|
|
10
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,128 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import deprecations from './data/deprecations.js'
|
|
2
|
+
import init from './utils/init.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
import html from '@html-eslint/eslint-plugin'
|
|
5
|
-
import json from '@eslint/json'
|
|
4
|
+
const plugin = init.plugin()
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
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 noInvalidPermissions from './rules/no-invalid-permissions.js'
|
|
15
|
-
import noMissingDefaultLocale from './rules/no-missing-default-locale.js'
|
|
16
|
-
import noMissingI18nKeys from './rules/no-missing-i18n-keys.js'
|
|
17
|
-
import noMissingPermissions from './rules/no-missing-permissions.js'
|
|
18
|
-
import noMissingWebAccessibleResources from './rules/no-missing-web-accessible-resources.js'
|
|
19
|
-
import noRemoteCode from './rules/no-remote-code.js'
|
|
20
|
-
import noUnrestrictedHostPermissions from './rules/no-unrestricted-host-permissions.js'
|
|
21
|
-
import noUnusedI18nKeys from './rules/no-unused-i18n-keys.js'
|
|
22
|
-
import noUnusedPermissions from './rules/no-unused-permissions.js'
|
|
23
|
-
import requireLicenseFile from './rules/require-license-file.js'
|
|
24
|
-
import requireMV3 from './rules/require-mv3.js'
|
|
25
|
-
import requireShortNameWhenUseful from './rules/require-short-name-when-useful.js'
|
|
26
|
-
import validFilenames from './rules/valid-filenames.js'
|
|
27
|
-
import validLicenseFile from './rules/valid-license-file.js'
|
|
28
|
-
import validManifestIcons from './rules/valid-manifest-icons.js'
|
|
29
|
-
import validMV3schema from './rules/valid-mv3-schema.js'
|
|
30
|
-
import validServiceWorker from './rules/valid-service-worker.js'
|
|
31
|
-
import validURLfields from './rules/valid-url-fields.js'
|
|
32
|
-
import validVersionField from './rules/valid-version-field.js'
|
|
6
|
+
init.rules(plugin)
|
|
7
|
+
init.configs(plugin)
|
|
33
8
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const plugin = {
|
|
38
|
-
rules: {
|
|
39
|
-
'manifest-key-order': manifestKeyOrder,
|
|
40
|
-
'manifest-spacing': manifestSpacing,
|
|
41
|
-
'no-content-style-leak': noContentStyleLeak,
|
|
42
|
-
'no-deprecated-apis': noDeprecatedAPIs,
|
|
43
|
-
'no-duplicate-manifest-keys': noDuplicateManifestKeys,
|
|
44
|
-
'no-insecure-fetch': noInsecureFetch,
|
|
45
|
-
'no-insecure-messaging': noInsecureMessaging,
|
|
46
|
-
'no-invalid-permissions': noInvalidPermissions,
|
|
47
|
-
'no-missing-default-locale': noMissingDefaultLocale,
|
|
48
|
-
'no-missing-i18n-keys': noMissingI18nKeys,
|
|
49
|
-
'no-missing-permissions': noMissingPermissions,
|
|
50
|
-
'no-missing-web-accessible-resources': noMissingWebAccessibleResources,
|
|
51
|
-
'no-remote-code': noRemoteCode,
|
|
52
|
-
'no-unrestricted-host-permissions': noUnrestrictedHostPermissions,
|
|
53
|
-
'no-unused-i18n-keys': noUnusedI18nKeys,
|
|
54
|
-
'no-unused-permissions': noUnusedPermissions,
|
|
55
|
-
'require-mv3': requireMV3,
|
|
56
|
-
'require-license-file': requireLicenseFile,
|
|
57
|
-
'require-short-name-when-useful': requireShortNameWhenUseful,
|
|
58
|
-
'valid-filenames': validFilenames,
|
|
59
|
-
'valid-license-file': validLicenseFile,
|
|
60
|
-
'valid-manifest-icons': validManifestIcons,
|
|
61
|
-
'valid-mv3-schema': validMV3schema,
|
|
62
|
-
'valid-service-worker': validServiceWorker,
|
|
63
|
-
'valid-url-fields': validURLfields,
|
|
64
|
-
'valid-version-field': validVersionField
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
69
|
-
rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#${ruleId}`
|
|
70
|
-
|
|
71
|
-
plugin.configs = {
|
|
72
|
-
recommended: {
|
|
73
|
-
plugins: { 'chrome-extensions': plugin },
|
|
74
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'chrome-extensions')
|
|
75
|
-
},
|
|
76
|
-
strict: {
|
|
77
|
-
plugins: { 'chrome-extensions': plugin },
|
|
78
|
-
rules: bundleRules(plugin.rules, () => true, 'chrome-extensions')
|
|
79
|
-
},
|
|
80
|
-
problem: {
|
|
81
|
-
plugins: { 'chrome-extensions': plugin },
|
|
82
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.type == 'problem', 'chrome-extensions')
|
|
83
|
-
},
|
|
84
|
-
security: {
|
|
85
|
-
plugins: { 'chrome-extensions': plugin },
|
|
86
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.security, 'chrome-extensions')
|
|
87
|
-
},
|
|
88
|
-
manifest: {
|
|
89
|
-
language: 'json/json',
|
|
90
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
91
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.manifest, 'chrome-extensions')
|
|
92
|
-
},
|
|
93
|
-
license: {
|
|
94
|
-
languageOptions: { parser: textParser },
|
|
95
|
-
plugins: { 'chrome-extensions': plugin },
|
|
96
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.license, 'chrome-extensions')
|
|
97
|
-
},
|
|
98
|
-
css: {
|
|
99
|
-
language: 'css/css',
|
|
100
|
-
plugins: { 'chrome-extensions': plugin, css },
|
|
101
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.css, 'chrome-extensions')
|
|
102
|
-
},
|
|
103
|
-
html: {
|
|
104
|
-
language: 'html/html',
|
|
105
|
-
plugins: { 'chrome-extensions': plugin, html },
|
|
106
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.html, 'chrome-extensions')
|
|
107
|
-
},
|
|
108
|
-
json: {
|
|
109
|
-
language: 'json/json',
|
|
110
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
111
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
112
|
-
},
|
|
113
|
-
json5: {
|
|
114
|
-
language: 'json/json5',
|
|
115
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
116
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
117
|
-
},
|
|
118
|
-
jsonc: {
|
|
119
|
-
language: 'json/jsonc',
|
|
120
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
121
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
for (const entry of Object.entries(oldBundles))
|
|
126
|
-
initBundleDeprecation(plugin, entry, 'chrome-extensions')
|
|
9
|
+
for (const entry of Object.entries(deprecations.bundles))
|
|
10
|
+
init.bundleDeprecation(plugin, entry, 'chrome-extensions')
|
|
127
11
|
|
|
128
12
|
export { plugin as default, plugin as chromeExtensions }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getFilename } from '../utils.js'
|
|
1
|
+
import extension from '../utils/extension.js'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import manifest from '../data/manifest.js'
|
|
4
|
+
import regex from '../data/regex.js'
|
|
5
5
|
|
|
6
6
|
function keyOf(member) {
|
|
7
7
|
const keyNode = member.name || member.key
|
|
@@ -43,15 +43,15 @@ export default {
|
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
create(context) {
|
|
46
|
-
if (!
|
|
46
|
+
if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
|
|
47
47
|
const custom = context.options[0]?.order ?? [],
|
|
48
|
-
master = custom.length ?
|
|
49
|
-
:
|
|
48
|
+
master = custom.length ? manifest.keys.order.filter(key => !custom.includes(key))
|
|
49
|
+
: manifest.keys.order
|
|
50
50
|
return {
|
|
51
51
|
'Document:exit'(document) {
|
|
52
52
|
const root = document.body ; if (root?.type != 'Object') return
|
|
53
|
-
const members = root.members || root.properties || []
|
|
54
|
-
|
|
53
|
+
const members = root.members || root.properties || [],
|
|
54
|
+
violation = findViolation(members, master) ?? (custom.length && findViolation(members, custom))
|
|
55
55
|
if (!violation) return
|
|
56
56
|
const [first, second] = violation
|
|
57
57
|
context.report({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import extension from '../utils/extension.js'
|
|
2
|
+
import regex from '../data/regex.js'
|
|
2
3
|
|
|
3
|
-
const re = {
|
|
4
|
+
const re = { leadingWhitespace: /^[ \t]+/ },
|
|
4
5
|
brackets = { open: new Set(['LBrace', 'LBracket']), close: new Set(['RBrace', 'RBracket']) }
|
|
5
6
|
|
|
6
7
|
export default {
|
|
@@ -39,8 +40,7 @@ export default {
|
|
|
39
40
|
},
|
|
40
41
|
additionalProperties: false
|
|
41
42
|
}],
|
|
42
|
-
defaultOptions: [{ spaces: 2, checkColons: true, checkCommas: true,
|
|
43
|
-
checkBraces: true, checkBrackets: true }],
|
|
43
|
+
defaultOptions: [{ spaces: 2, checkColons: true, checkCommas: true, checkBraces: true, checkBrackets: true }],
|
|
44
44
|
messages: {
|
|
45
45
|
wrongIndent: 'Expected {{expected}} spaces of indentation (found {{found}}).',
|
|
46
46
|
tabFound: 'Expected spaces for indentation, found tabs.',
|
|
@@ -57,7 +57,7 @@ export default {
|
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
create(context) {
|
|
60
|
-
if (!
|
|
60
|
+
if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
|
|
61
61
|
const opts = context.options[0] || {},
|
|
62
62
|
spaces = opts.spaces ?? 2,
|
|
63
63
|
checkColons = opts.checkColons ?? true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import extension from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { appendMethod: /^append(?:Child)?$/ }
|
|
4
4
|
|
|
@@ -17,7 +17,8 @@ export default {
|
|
|
17
17
|
},
|
|
18
18
|
|
|
19
19
|
create(context) {
|
|
20
|
-
const manifest =
|
|
20
|
+
const manifest = extension.manifest.read(context)
|
|
21
|
+
if (!manifest || !extension.file.isContentScript(context, manifest)) return {}
|
|
21
22
|
const styleElems = new Set(), docAliases = new Set()
|
|
22
23
|
function isCreateStyle(node) {
|
|
23
24
|
return node?.type == 'CallExpression'
|
|
@@ -57,8 +58,9 @@ export default {
|
|
|
57
58
|
CallExpression(node) {
|
|
58
59
|
if (node.callee.type != 'MemberExpression') return
|
|
59
60
|
const method = node.callee.property.name
|
|
60
|
-
if (!re.appendMethod.test(method)
|
|
61
|
-
|
|
61
|
+
if (!re.appendMethod.test(method)
|
|
62
|
+
|| !isDocumentMember(node.callee.object)
|
|
63
|
+
) return
|
|
62
64
|
for (const arg of node.arguments)
|
|
63
65
|
if (isStyleTarget(arg))
|
|
64
66
|
context.report({ node: arg, messageId: 'styleLeak' })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import nodeUtils from '../utils/node.js'
|
|
2
|
+
import apis from '../data/apis.js'
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
meta: {
|
|
@@ -50,8 +50,8 @@ export default {
|
|
|
50
50
|
return {
|
|
51
51
|
MemberExpression(node) {
|
|
52
52
|
if (node.parent.type == 'MemberExpression' && node.parent.object === node) return
|
|
53
|
-
const api = getMemberPath(node) ; if (!api?.startsWith('chrome.') || isAllowed(api)) return
|
|
54
|
-
const found = findDeprecated(api) ; if (!found) return
|
|
53
|
+
const api = nodeUtils.getMemberPath(node) ; if (!api?.startsWith('chrome.') || isAllowed(api)) return
|
|
54
|
+
const found = apis.findDeprecated(api) ; if (!found) return
|
|
55
55
|
context.report({ node, messageId: 'deprecatedAPI',
|
|
56
56
|
data: { api: found.deprecated, replacement: found.replacement, target }})
|
|
57
57
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
const re = { manifestFilename: /manifest\.json$/ }
|
|
1
|
+
import extension from '../utils/extension.js'
|
|
2
|
+
import regex from '../data/regex.js'
|
|
4
3
|
|
|
5
4
|
export default {
|
|
6
5
|
meta: {
|
|
@@ -18,7 +17,7 @@ export default {
|
|
|
18
17
|
},
|
|
19
18
|
|
|
20
19
|
create(context) {
|
|
21
|
-
if (!
|
|
20
|
+
if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
|
|
22
21
|
return {
|
|
23
22
|
Object(node) {
|
|
24
23
|
const members = node.members || node.properties || [],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import nodeUtils from '../utils/node.js'
|
|
2
|
+
import apis from '../data/apis.js'
|
|
3
3
|
|
|
4
4
|
const re = { postmsgValidation: /\b\w+\.(?:origin|source)\b/, senderValidation: /sender\.(?:id|origin)/ }
|
|
5
5
|
|
|
@@ -44,9 +44,9 @@ export default {
|
|
|
44
44
|
return {
|
|
45
45
|
CallExpression(node) {
|
|
46
46
|
if (node.callee.type != 'MemberExpression') return
|
|
47
|
-
const path = getMemberPath(node.callee) ; if (!path) return
|
|
47
|
+
const path = nodeUtils.getMemberPath(node.callee) ; if (!path) return
|
|
48
48
|
const listenerRoot = path.endsWith('.addListener') ? path.slice(0, -'.addListener'.length) : null
|
|
49
|
-
if (listenerRoot &&
|
|
49
|
+
if (listenerRoot && apis.isExternalMsgListener(listenerRoot)) {
|
|
50
50
|
const handler = node.arguments[0] ; if (!handler) return
|
|
51
51
|
const body = context.sourceCode.getText(handler)
|
|
52
52
|
if (!re.senderValidation.test(body))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isValidPermission } from '../data/permissions.js'
|
|
1
|
+
import extension from '../utils/extension.js'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import permissions from '../data/permissions.js'
|
|
4
|
+
import regex from '../data/regex.js'
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
meta: {
|
|
@@ -46,29 +46,28 @@ export default {
|
|
|
46
46
|
: opts.allowedPermissions ? [opts.allowedPermissions] : [])
|
|
47
47
|
function checkPermission(node, permission) {
|
|
48
48
|
if (typeof permission != 'string') return
|
|
49
|
-
if (allowed.has(permission) ||
|
|
49
|
+
if (allowed.has(permission) || permissions.isValid(permission, target)) return
|
|
50
50
|
context.report({ node, messageId: 'invalidPermission', data: { permission, target }})
|
|
51
51
|
}
|
|
52
|
-
if (
|
|
52
|
+
if (regex.filename.manifest.test(extension.file.getName(context))) // lint it directly
|
|
53
53
|
return {
|
|
54
54
|
'*'(node) {
|
|
55
55
|
const keyNode = node.name || node.key ; if (!keyNode) return
|
|
56
|
-
|
|
57
|
-
if (keyName != 'permissions' && keyName != 'optional_permissions') return
|
|
56
|
+
if (!['optional_permissions', 'permissions'].includes(keyNode.value ?? keyNode.name)) return
|
|
58
57
|
const arrNode = node.value ; if (arrNode?.type != 'Array') return
|
|
59
|
-
const
|
|
60
|
-
for (const elem of
|
|
58
|
+
const nodeElems = arrNode.elements || arrNode.items || []
|
|
59
|
+
for (const elem of nodeElems) {
|
|
61
60
|
const valueNode = elem.value || elem
|
|
62
61
|
checkPermission(valueNode, valueNode?.value)
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
const manifest =
|
|
65
|
+
const manifest = extension.manifest.read(context) ; if (!manifest) return {}
|
|
67
66
|
const allPermissions = [...(manifest.permissions || []), ...(manifest.optional_permissions || [])]
|
|
68
67
|
return {
|
|
69
68
|
'Program:exit'() {
|
|
70
69
|
for (const permission of allPermissions) {
|
|
71
|
-
if (allowed.has(permission) ||
|
|
70
|
+
if (allowed.has(permission) || permissions.isValid(permission, target)) continue
|
|
72
71
|
context.report({ loc: { line: 1, column: 0 },
|
|
73
72
|
messageId: 'invalidPermission', data: { permission, target }})
|
|
74
73
|
}
|