eslint-plugin-chrome-extensions 2.4.2 → 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 +87 -6
- package/package.json +3 -2
- 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 -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-permissions.js +10 -11
- package/src/rules/no-missing-default-locale.js +7 -9
- package/src/rules/no-missing-i18n-keys.js +22 -21
- 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 +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 +4 -5
- 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 +34 -13
- package/src/rules/valid-url-fields.js +10 -7
- package/src/rules/valid-version-field.js +4 -8
- package/src/utils/extension.js +39 -33
- 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 +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
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,129 +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
|
-
import { textParser } from './data/text-parser.js'
|
|
37
|
-
|
|
38
|
-
const plugin = {
|
|
39
|
-
rules: {
|
|
40
|
-
'manifest-key-order': manifestKeyOrder,
|
|
41
|
-
'manifest-spacing': manifestSpacing,
|
|
42
|
-
'no-content-style-leak': noContentStyleLeak,
|
|
43
|
-
'no-deprecated-apis': noDeprecatedAPIs,
|
|
44
|
-
'no-duplicate-manifest-keys': noDuplicateManifestKeys,
|
|
45
|
-
'no-insecure-fetch': noInsecureFetch,
|
|
46
|
-
'no-insecure-messaging': noInsecureMessaging,
|
|
47
|
-
'no-invalid-permissions': noInvalidPermissions,
|
|
48
|
-
'no-missing-default-locale': noMissingDefaultLocale,
|
|
49
|
-
'no-missing-i18n-keys': noMissingI18nKeys,
|
|
50
|
-
'no-missing-permissions': noMissingPermissions,
|
|
51
|
-
'no-missing-web-accessible-resources': noMissingWebAccessibleResources,
|
|
52
|
-
'no-remote-code': noRemoteCode,
|
|
53
|
-
'no-unrestricted-host-permissions': noUnrestrictedHostPermissions,
|
|
54
|
-
'no-unused-i18n-keys': noUnusedI18nKeys,
|
|
55
|
-
'no-unused-permissions': noUnusedPermissions,
|
|
56
|
-
'require-mv3': requireMV3,
|
|
57
|
-
'require-license-file': requireLicenseFile,
|
|
58
|
-
'require-short-name-when-useful': requireShortNameWhenUseful,
|
|
59
|
-
'valid-filenames': validFilenames,
|
|
60
|
-
'valid-license-file': validLicenseFile,
|
|
61
|
-
'valid-manifest-icons': validManifestIcons,
|
|
62
|
-
'valid-mv3-schema': validMV3schema,
|
|
63
|
-
'valid-service-worker': validServiceWorker,
|
|
64
|
-
'valid-url-fields': validURLfields,
|
|
65
|
-
'valid-version-field': validVersionField
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const [ruleId, rule] of Object.entries(plugin.rules))
|
|
70
|
-
rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#${ruleId}`
|
|
71
|
-
|
|
72
|
-
plugin.configs = {
|
|
73
|
-
recommended: {
|
|
74
|
-
plugins: { 'chrome-extensions': plugin },
|
|
75
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'chrome-extensions')
|
|
76
|
-
},
|
|
77
|
-
strict: {
|
|
78
|
-
plugins: { 'chrome-extensions': plugin },
|
|
79
|
-
rules: bundleRules(plugin.rules, () => true, 'chrome-extensions')
|
|
80
|
-
},
|
|
81
|
-
problem: {
|
|
82
|
-
plugins: { 'chrome-extensions': plugin },
|
|
83
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.type == 'problem', 'chrome-extensions')
|
|
84
|
-
},
|
|
85
|
-
security: {
|
|
86
|
-
plugins: { 'chrome-extensions': plugin },
|
|
87
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.security, 'chrome-extensions')
|
|
88
|
-
},
|
|
89
|
-
manifest: {
|
|
90
|
-
language: 'json/json',
|
|
91
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
92
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.manifest, 'chrome-extensions')
|
|
93
|
-
},
|
|
94
|
-
license: {
|
|
95
|
-
languageOptions: { parser: textParser },
|
|
96
|
-
plugins: { 'chrome-extensions': plugin },
|
|
97
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.license, 'chrome-extensions')
|
|
98
|
-
},
|
|
99
|
-
css: {
|
|
100
|
-
language: 'css/css',
|
|
101
|
-
plugins: { 'chrome-extensions': plugin, css },
|
|
102
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.css, 'chrome-extensions')
|
|
103
|
-
},
|
|
104
|
-
html: {
|
|
105
|
-
language: 'html/html',
|
|
106
|
-
plugins: { 'chrome-extensions': plugin, html },
|
|
107
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.html, 'chrome-extensions')
|
|
108
|
-
},
|
|
109
|
-
json: {
|
|
110
|
-
language: 'json/json',
|
|
111
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
112
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
113
|
-
},
|
|
114
|
-
json5: {
|
|
115
|
-
language: 'json/json5',
|
|
116
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
117
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
118
|
-
},
|
|
119
|
-
jsonc: {
|
|
120
|
-
language: 'json/jsonc',
|
|
121
|
-
plugins: { 'chrome-extensions': plugin, json },
|
|
122
|
-
rules: bundleRules(plugin.rules, rule => rule.meta.docs.json, 'chrome-extensions')
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
for (const entry of Object.entries(oldBundles))
|
|
127
|
-
initBundleDeprecation(plugin, entry, 'chrome-extensions')
|
|
9
|
+
for (const entry of Object.entries(deprecations.bundles))
|
|
10
|
+
init.bundleDeprecation(plugin, entry, 'chrome-extensions')
|
|
128
11
|
|
|
129
12
|
export { plugin as default, plugin as chromeExtensions }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { file } from '../utils/extension.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
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/* eslint-disable security/detect-non-literal-fs-filename -- lint-time project inspection only */
|
|
2
2
|
|
|
3
|
+
import extension from '../utils/extension.js'
|
|
3
4
|
import fs from 'node:fs'
|
|
5
|
+
import i18n from '../utils/i18n.js'
|
|
4
6
|
import path from 'node:path'
|
|
5
7
|
|
|
6
|
-
import
|
|
7
|
-
import { readManifest } from '../utils/extension.js'
|
|
8
|
-
|
|
9
|
-
const re = { manifestFile: /manifest\.json$/ }
|
|
8
|
+
import regex from '../data/regex.js'
|
|
10
9
|
|
|
11
10
|
function readMessages(filePath) {
|
|
12
11
|
try {
|
|
@@ -53,10 +52,9 @@ export default {
|
|
|
53
52
|
},
|
|
54
53
|
|
|
55
54
|
create(context) {
|
|
56
|
-
const filename = context.filename
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const root = findExtensionRoot(filename) ; if (!root) return {}
|
|
55
|
+
const filename = context.filename ; if (!filename || !regex.filename.manifest.test(filename)) return {}
|
|
56
|
+
const manifest = extension.manifest.read(context) ; if (!manifest) return {}
|
|
57
|
+
const root = extension.findRoot(context) ; if (!root) return {}
|
|
60
58
|
const localesDir = path.join(root, '_locales'),
|
|
61
59
|
hasLocales = fs.existsSync(localesDir),
|
|
62
60
|
defaultLocale = manifest.default_locale,
|
|
@@ -81,7 +79,7 @@ export default {
|
|
|
81
79
|
if (locale == defaultLocale) continue
|
|
82
80
|
const msgs = readMessages(path.join(localesDir, locale, 'messages.json')) ; if (!msgs) continue
|
|
83
81
|
for (const key of Object.keys(msgs)) {
|
|
84
|
-
if (isPredefined(key) || ignored.has(key)) continue
|
|
82
|
+
if (i18n.isPredefined(key) || ignored.has(key)) continue
|
|
85
83
|
if (!(key in defaultMsgs))
|
|
86
84
|
context.report({ loc, messageId: 'missingKeyInDefault', data: { key, locale, defaultLocale }})
|
|
87
85
|
}
|