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.
Files changed (46) hide show
  1. package/README.md +87 -6
  2. package/package.json +3 -2
  3. package/src/data/apis.js +166 -115
  4. package/src/data/deprecations.js +7 -7
  5. package/src/data/i18n.js +11 -69
  6. package/src/data/manifest.js +61 -57
  7. package/src/data/permissions.js +29 -29
  8. package/src/data/regex.js +10 -0
  9. package/src/index.js +7 -124
  10. package/src/rules/manifest-key-order.js +8 -8
  11. package/src/rules/manifest-spacing.js +5 -5
  12. package/src/rules/no-content-style-leak.js +6 -4
  13. package/src/rules/no-deprecated-apis.js +4 -4
  14. package/src/rules/no-duplicate-manifest-keys.js +3 -4
  15. package/src/rules/no-insecure-messaging.js +4 -4
  16. package/src/rules/no-invalid-permissions.js +10 -11
  17. package/src/rules/no-missing-default-locale.js +7 -9
  18. package/src/rules/no-missing-i18n-keys.js +22 -21
  19. package/src/rules/no-missing-permissions.js +7 -6
  20. package/src/rules/no-missing-web-accessible-resources.js +7 -6
  21. package/src/rules/no-remote-code.js +12 -12
  22. package/src/rules/no-sensitive-permissions.js +75 -0
  23. package/src/rules/no-unrestricted-host-permissions.js +8 -7
  24. package/src/rules/no-unused-i18n-keys.js +11 -10
  25. package/src/rules/no-unused-permissions.js +22 -23
  26. package/src/rules/require-license-file.js +4 -5
  27. package/src/rules/require-mv3.js +4 -5
  28. package/src/rules/require-short-name-when-useful.js +4 -3
  29. package/src/rules/valid-filenames.js +3 -10
  30. package/src/rules/valid-license-file.js +18 -11
  31. package/src/rules/valid-manifest-icons.js +6 -5
  32. package/src/rules/valid-mv3-schema.js +21 -21
  33. package/src/rules/valid-service-worker.js +34 -13
  34. package/src/rules/valid-url-fields.js +10 -7
  35. package/src/rules/valid-version-field.js +4 -8
  36. package/src/utils/extension.js +39 -33
  37. package/src/utils/i18n.js +55 -0
  38. package/src/utils/init.js +105 -0
  39. package/src/utils/license.js +55 -0
  40. package/src/utils/node.js +20 -19
  41. package/src/utils/pkg.js +12 -10
  42. package/src/utils/rules.js +6 -4
  43. package/src/utils/text.js +13 -0
  44. package/src/data/license.js +0 -28
  45. package/src/data/text-parser.js +0 -11
  46. package/src/utils/bundle.js +0 -10
@@ -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 const ffDataCollectionRequired = new Set([
7
- 'none', 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo',
8
- 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'websiteActivity',
9
- 'websiteContent'
10
- ])
11
-
12
- export const ffDataCollectionOptional = new Set([
13
- 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo', 'locationInfo',
14
- 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'technicalAndInteraction', 'websiteActivity',
15
- 'websiteContent'
16
- ])
17
-
18
- export const ffMV3bgKeys = new Set(['scripts', 'page'])
19
-
20
- export const forbiddenCSPdirectives = [
21
- `'unsafe-eval'`, `'unsafe-inline'`, `'unsafe-hashes'`, `'wasm-unsafe-eval'`, 'http://', 'https://']
22
-
23
- export const manifestKeyOrder = [
24
- // platform-required
25
- 'manifest_version', 'name', 'version',
26
- // CWS-required
27
- 'description', 'icons',
28
- // optional
29
- 'action', 'author', 'background', 'browser_specific_settings', 'chrome_settings_overrides', 'chrome_url_overrides',
30
- 'commands', 'content_scripts', 'content_security_policy', 'cross_origin_embedder_policy',
31
- 'cross_origin_opener_policy', 'dark_theme', 'declarative_net_request', 'default_locale', 'developer',
32
- 'devtools_page', 'dictionaries', 'export', 'externally_connectable', 'homepage_url', 'host_permissions', 'import',
33
- 'incognito', 'key', 'mime_types_handler', 'minimum_chrome_version', 'oauth2', 'omnibox',
34
- 'optional_host_permissions', 'optional_permissions', 'options_page', 'options_ui', 'permissions',
35
- 'protocol_handlers', 'requirements', 'sandbox', 'short_name', 'sidebar_action', 'side_panel', 'storage', 'theme',
36
- 'theme_experiment', 'tts_engine', 'update_url', 'version_name', 'web_accessible_resources'
37
- ]
38
-
39
- export const matchPaths = new Set([
40
- 'content_scripts.matches', 'externally_connectable.matches',
41
- 'host_permissions', 'optional_host_permissions', 'web_accessible_resources.matches'
42
- ])
43
-
44
- export const mv2OnlyKeys = new Set(['browser_action', 'page_action'])
45
-
46
- export const remoteCodePatterns = [
47
- 'eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']
48
-
49
- export const serviceWorkerKeepaliveMinMS = 30000
50
-
51
- export const urlPaths = new Set([
52
- 'homepage_url', 'update_url', 'oauth2.scopes',
53
- 'chrome_settings_overrides.homepage',
54
- 'chrome_settings_overrides.search_provider.alternate_urls',
55
- 'chrome_settings_overrides.search_provider.favicon_url',
56
- 'chrome_settings_overrides.search_provider.image_url',
57
- 'chrome_settings_overrides.search_provider.instant_url',
58
- 'chrome_settings_overrides.search_provider.search_url',
59
- 'chrome_settings_overrides.search_provider.suggest_url',
60
- 'chrome_settings_overrides.startup_pages'
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
+ }
@@ -1,31 +1,31 @@
1
- const re = { url: /^https?:\/\// }
1
+ import regex from './regex.js'
2
2
 
3
- export const sensitivePermissions = new Set([
4
- 'accessibilityFeatures.modify', 'accessibilityFeatures.read', 'bookmarks', 'browsingData', 'clipboardRead',
5
- 'contentSettings', 'cookies', 'debugger', 'declarativeNetRequest', 'downloads', 'downloads.open', 'downloads.ui',
6
- 'geolocation', 'history', 'management', 'nativeMessaging', 'pageCapture', 'privacy', 'proxy', 'readingList',
7
- 'sessions', 'system.storage', 'tabCapture', 'tabGroups', 'tabs', 'topSites', 'ttsEngine',
8
- 'webAuthenticationProxy', 'webNavigation', 'webRequest', 'webRequestBlocking'
9
- ])
10
-
11
- export const validPermissions = 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', 'documentScan',
16
- 'downloads', 'downloads.open', 'downloads.ui', 'enterprise.deviceAttributes', 'enterprise.hardwarePlatform',
17
- 'enterprise.networkingAttributes', 'enterprise.platformKeys', 'favicon', 'fileBrowserHandler',
18
- 'fileSystemProvider', 'fontSettings', 'gcm', 'geolocation', 'history', 'identity', 'identity.email', 'idle',
19
- 'loginState', 'management', 'menus', 'nativeMessaging', 'notifications', 'offscreen', 'pageCapture', 'platformKeys',
20
- 'power', 'printerProvider', 'printing', 'printingMetrics', 'privacy', 'processes', 'proxy', 'publicSuffix',
21
- 'readingList', 'runtime', 'scripting', 'search', 'sessions', 'sidePanel', 'storage', 'system.cpu',
22
- 'system.display', 'system.memory', 'system.storage', 'tabCapture', 'tabGroups', 'tabs', 'topSites', 'tts',
23
- 'ttsEngine', 'unlimitedStorage', 'userScripts', 'vpnProvider', 'wallpaper', 'webAuthenticationProxy',
24
- 'webNavigation', 'webRequest', 'webRequestBlocking'
25
- ])
26
-
27
- export function isValidPermission(name, target = 'mv3') {
28
- if (validPermissions.has(name)) return true
29
- if (target == 'mv3') return false
30
- return re.url.test(name) || name.startsWith('<') || name == '*://*/*'
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 { bundles as oldBundles } from './data/deprecations.js'
1
+ import deprecations from './data/deprecations.js'
2
+ import init from './utils/init.js'
2
3
 
3
- import css from '@eslint/css'
4
- import html from '@html-eslint/eslint-plugin'
5
- import json from '@eslint/json'
4
+ const plugin = init.plugin()
6
5
 
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 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
- import { bundle as bundleRules } from './utils/rules.js'
35
- import { initDeprecation as initBundleDeprecation } from './utils/bundle.js'
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 { manifestKeyOrder } from '../data/manifest.js'
2
- import { file } from '../utils/extension.js'
1
+ import extension from '../utils/extension.js'
3
2
 
4
- const re = { manifestFilename: /manifest\.json$/ }
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 (!re.manifestFilename.test(file.getName(context))) return {}
46
+ if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
47
47
  const custom = context.options[0]?.order ?? [],
48
- master = custom.length ? manifestKeyOrder.filter(key => !custom.includes(key))
49
- : manifestKeyOrder
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
- const violation = findViolation(members, master) ?? (custom.length && findViolation(members, custom))
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 { file } from '../utils/extension.js'
1
+ import extension from '../utils/extension.js'
2
+ import regex from '../data/regex.js'
2
3
 
3
- const re = { manifestFilename: /manifest\.json$/, leadingWhitespace: /^[ \t]+/ },
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 (!re.manifestFilename.test(file.getName(context))) return {}
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 { file, readManifest } from '../utils/extension.js'
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 = readManifest(context) ; if (!manifest || !file.isContentScript(context, manifest)) return {}
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)) return
61
- if (!isDocumentMember(node.callee.object)) return
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 { findDeprecated } from '../data/apis.js'
2
- import { getMemberPath } from '../utils/node.js'
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 { file } from '../utils/extension.js'
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 (!re.manifestFilename.test(file.getName(context))) return {}
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 { getMemberPath } from '../utils/node.js'
2
- import { isExternalMessagingListener } from '../data/apis.js'
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 && isExternalMessagingListener(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 { file, readManifest } from '../utils/extension.js'
2
- import { isValidPermission } from '../data/permissions.js'
1
+ import extension from '../utils/extension.js'
3
2
 
4
- const re = { manifestFilename: /manifest\.json$/ }
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) || isValidPermission(permission, target)) return
49
+ if (allowed.has(permission) || permissions.isValid(permission, target)) return
50
50
  context.report({ node, messageId: 'invalidPermission', data: { permission, target }})
51
51
  }
52
- if (re.manifestFilename.test(file.getName(context))) // lint it directly
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
- const keyName = keyNode.value ?? keyNode.name
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 elems = arrNode.elements || arrNode.items || []
60
- for (const elem of elems) {
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 = readManifest(context) ; if (!manifest) return {}
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) || isValidPermission(permission, target)) continue
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 { findExtensionRoot, isPredefined } from '../data/i18n.js'
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
- if (!filename || !re.manifestFile.test(filename)) return {}
58
- const manifest = readManifest(context) ; if (!manifest) return {}
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
  }