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
package/README.md CHANGED
@@ -1008,6 +1008,76 @@ If you intentionally target forks w/ relaxed CSPs. For individual patterns you s
1008
1008
 
1009
1009
  </details>
1010
1010
 
1011
+ <h3 id="no-sensitive-permissions">
1012
+ <code>no-sensitive-permissions</code>
1013
+ <a title="Included in recommended config" href="#usage">
1014
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
1015
+ <a title="Included in security config" href="#usage">
1016
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
1017
+ <a title="Works in 'json/json' config block" href="#usage">
1018
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
1019
+ <a title="Accepts array w/ options" href="#usage">
1020
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1021
+ <a title="Copy rule URL" target="_blank"
1022
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-sensitive-permissions">
1023
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
1024
+ </h3>
1025
+
1026
+ <details>
1027
+
1028
+ <summary>Flags sensitive manifest permissions that warrant security review.</summary>
1029
+
1030
+ <h4 id="no-sensitive-permissions-options">Options</h4>
1031
+
1032
+ - `allowedPermissions` (default `[]`) – sensitive permission names to explicitly allow, as a single string or array
1033
+
1034
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
1035
+
1036
+ ```diff
1037
+ {
1038
+ "manifest_version": 3,
1039
+ ! "permissions": ["tabs", "cookies", "storage"]
1040
+ }
1041
+ ```
1042
+
1043
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
1044
+
1045
+ ```diff
1046
+ {
1047
+ "manifest_version": 3,
1048
+ - "permissions": ["tabs", "cookies", "storage"]
1049
+ + "permissions": ["storage"]
1050
+ }
1051
+ ```
1052
+
1053
+ #### When not to use it
1054
+
1055
+ If you legitimately require sensitive permissions, or prefer manual security review.
1056
+
1057
+ #### Introduced in
1058
+
1059
+ v2.5.0 (Unreleased)
1060
+
1061
+ #### See also
1062
+
1063
+ - [`no-invalid-permissions`](#no-invalid-permissions)
1064
+ <a title="Included in recommended config" href="#usage"><img height=14 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
1065
+ <a title="Works in 'json/json' config block" href="#usage"><img height=14 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-999999?style=flat-square" align="middle"></a>
1066
+ <a title="Accepts array w/ options" href="#no-invalid-permissions-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1067
+ - [`no-unused-permissions`](#no-unused-permissions)
1068
+ <a title="Only works in 'json/json' config block" href="#usage"><img height=14 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-999999?style=flat-square" align="middle"></a>
1069
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64">&ensp;<a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">Chrome for Developers — Declare permissions</a>
1070
+
1071
+ <div align="center">
1072
+
1073
+ [Back to index ↑][epce-rules]
1074
+
1075
+ </div>
1076
+
1077
+ ---
1078
+
1079
+ </details>
1080
+
1011
1081
  <h3 id="no-unrestricted-host-permissions">
1012
1082
  <code>no-unrestricted-host-permissions</code>
1013
1083
  <a title="Only works in 'json/json' config block" href="#usage">
@@ -1253,6 +1323,7 @@ If you intentionally omit a license, or it's injected at build time.
1253
1323
 
1254
1324
  - [`valid-license-file`](#valid-license-file)
1255
1325
  <a title="Only works in license config block" href="#usage"><img height=14 alt="[License only]" src="https://img.shields.io/badge/License_only-559bff?style=flat-square" align="middle"></a>
1326
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix"><img height=14 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
1256
1327
  <a title="Accepts array w/ options" href="#valid-license-file-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1257
1328
 
1258
1329
  <div align="center">
@@ -1427,7 +1498,7 @@ If you primarily target a browser that doesn't support `short_name`.
1427
1498
 
1428
1499
  ```diff
1429
1500
  // files
1430
- ! /extension/_metadata/app.json
1501
+ ! /extension/_metadata/extension.json
1431
1502
  ! /extension/content script.js
1432
1503
 
1433
1504
  // manifest.json
@@ -1440,8 +1511,8 @@ If you primarily target a browser that doesn't support `short_name`.
1440
1511
  #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
1441
1512
 
1442
1513
  ```diff
1443
- - /extension/_metadata/app.json
1444
- + /extension/metadata/app.json
1514
+ - /extension/_metadata/extension.json
1515
+ + /extension/metadata/extension.json
1445
1516
  - /extension/content script.js
1446
1517
  + /extension/content-script.js
1447
1518
 
@@ -1484,6 +1555,8 @@ If you don't use conventional filenames or paths, or they're generated at build
1484
1555
  <code>valid-license-file</code>
1485
1556
  <a title="Only works in license config block" href="#usage">
1486
1557
  <img height=18 alt="[License only]" src="https://img.shields.io/badge/License_only-559bff?style=flat-square" align="middle"></a>
1558
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
1559
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
1487
1560
  <a title="Accepts array w/ options" href="#usage">
1488
1561
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1489
1562
  <a title="Copy rule URL" target="_blank"
@@ -1524,11 +1597,11 @@ If you don't use conventional filenames or paths, or they're generated at build
1524
1597
 
1525
1598
  #### Benefits
1526
1599
 
1527
- Makes the license machine-readable by compliance scanners + SBOM generators + CI gates.
1600
+ Enables machine-readability for compliance scanners, SBOM generators & CI gates.
1528
1601
 
1529
1602
  #### When not to use it
1530
1603
 
1531
- If your license is injected at build time, or you use a license w/ no SPDX identifier.
1604
+ If your license is injected at build time, or you use one w/ no existing SPDX identifier.
1532
1605
 
1533
1606
  #### Introduced in
1534
1607
 
@@ -1694,6 +1767,8 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
1694
1767
  <code>valid-service-worker</code>
1695
1768
  <a title="Included in recommended config" href="#usage">
1696
1769
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
1770
+ <a title="Accepts array w/ options" href="#usage">
1771
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1697
1772
  <a title="Copy rule URL" target="_blank"
1698
1773
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-service-worker">
1699
1774
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -1703,6 +1778,12 @@ If you intentionally target MV2 for a browser that still supports it (e.g. Firef
1703
1778
 
1704
1779
  <summary>Enforces valid service worker lifecycle and isolation.</summary>
1705
1780
 
1781
+ <h4 id="mit-worker-options">Options</h4>
1782
+
1783
+ - `checkListenerTopLevel` (default `true`) – require `chrome.*.addListener()` calls at module top level
1784
+ - `checkKeepaliveTimer` (default `true`) – flag sub-30s timers used as a keepalive workaround
1785
+ - `checkDOMglobals` (default `true`) – flag DOM globals (`window`, `document`, `localStorage`) inside the service worker
1786
+
1706
1787
  #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
1707
1788
 
1708
1789
  ```diff
@@ -1919,7 +2000,7 @@ If you don't care about version format consistency, or your version is injected
1919
2000
  ## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/baseline-account-balance.svg?color=%232b2b2a"> License
1920
2001
 
1921
2002
  <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/src/branch/main/docs/LICENSE.md">
1922
- <img height=85 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
2003
+ <img height=80 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
1923
2004
  <br><br>
1924
2005
 
1925
2006
  <details>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-chrome-extensions",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "ESLint rules for Chrome extensions",
5
5
  "keywords": [
6
6
  "eslint",
@@ -62,7 +62,8 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "globals": "^17.13.0",
65
- "spdx-license-ids": "^3.0.24"
65
+ "spdx-license-ids": "^3.0.24",
66
+ "spdx-license-list": "^6.12.0"
66
67
  },
67
68
  "devDependencies": {
68
69
  "@e18e/eslint-plugin": "^0.8.1",
package/src/data/apis.js CHANGED
@@ -1,116 +1,167 @@
1
- export const apiPermissionMap = new Map([
2
- ['chrome.alarms', 'alarms'],
3
- ['chrome.bookmarks', 'bookmarks'],
4
- ['chrome.browsingData', 'browsingData'],
5
- ['chrome.contextMenus', 'contextMenus'],
6
- ['chrome.cookies', 'cookies'],
7
- ['chrome.debugger', 'debugger'],
8
- ['chrome.declarativeNetRequest', 'declarativeNetRequest'],
9
- ['chrome.downloads', 'downloads'],
10
- ['chrome.history', 'history'],
11
- ['chrome.idle', 'idle'],
12
- ['chrome.management', 'management'],
13
- ['chrome.menus', 'menus'],
14
- ['chrome.notifications', 'notifications'],
15
- ['chrome.pageCapture', 'pageCapture'],
16
- ['chrome.privacy', 'privacy'],
17
- ['chrome.proxy', 'proxy'],
18
- ['chrome.scripting', 'scripting'],
19
- ['chrome.sessions', 'sessions'],
20
- ['chrome.storage', 'storage'],
21
- ['chrome.tabCapture', 'tabCapture'],
22
- ['chrome.tabs.query', 'tabs'],
23
- ['chrome.topSites', 'topSites'],
24
- ['chrome.webRequest', 'webRequest']
25
- ])
26
-
27
- export const deprecatedAPIs = new Map([
28
- ['chrome.pageAction.show', 'chrome.action.enable'],
29
- ['chrome.pageAction.hide', 'chrome.action.disable'],
30
- ['chrome.pageAction.setIcon', 'chrome.action.setIcon'],
31
- ['chrome.pageAction.setTitle', 'chrome.action.setTitle'],
32
- ['chrome.pageAction.setPopup', 'chrome.action.setPopup'],
33
- ['chrome.pageAction.getTitle', 'chrome.action.getTitle'],
34
- ['chrome.pageAction.getPopup', 'chrome.action.getPopup'],
35
- ['chrome.browserAction', 'chrome.action'],
36
- ['chrome.pageAction', 'chrome.action'],
37
- ['chrome.extension.getBackgroundPage', 'chrome.runtime.getContexts'],
38
- ['chrome.extension.getURL', 'chrome.runtime.getURL'],
39
- ['chrome.extension.sendRequest', 'chrome.runtime.sendMessage'],
40
- ['chrome.extension.onRequest', 'chrome.runtime.onMessage'],
41
- ['chrome.extension.onRequestExternal', 'chrome.runtime.onMessageExternal'],
42
- ['chrome.extension.connect', 'chrome.runtime.connect'],
43
- ['chrome.extension.onConnect', 'chrome.runtime.onConnect'],
44
- ['chrome.extension.onConnectExternal', 'chrome.runtime.onConnectExternal'],
45
- ['chrome.tabs.executeScript', 'chrome.scripting.executeScript'],
46
- ['chrome.tabs.insertCSS', 'chrome.scripting.insertCSS'],
47
- ['chrome.tabs.removeCSS', 'chrome.scripting.removeCSS']
48
- ])
49
-
50
- export const externalMsgListeners = new Set(['chrome.runtime.onMessageExternal', 'chrome.runtime.onConnectExternal'])
51
-
52
- export const permlessAPIs = new Set([
53
- // https://developer.chrome.com/docs/extensions/reference/api/management
54
- 'chrome.management.getPermissionWarningsByManifest',
55
- 'chrome.management.getSelf',
56
- 'chrome.management.uninstallSelf',
57
- // https://developer.chrome.com/docs/extensions/reference/api/runtime
58
- 'chrome.runtime.connect',
59
- 'chrome.runtime.getManifest',
60
- 'chrome.runtime.getPlatformInfo',
61
- 'chrome.runtime.getURL',
62
- 'chrome.runtime.onBrowserUpdateAvailable', // eslint-disable-line no-secrets/no-secrets
63
- 'chrome.runtime.onConnect',
64
- 'chrome.runtime.onConnectExternal',
65
- 'chrome.runtime.onInstalled',
66
- 'chrome.runtime.onMessage',
67
- 'chrome.runtime.onMessageExternal',
68
- 'chrome.runtime.onStartup',
69
- 'chrome.runtime.onSuspend',
70
- 'chrome.runtime.onSuspendCanceled',
71
- 'chrome.runtime.onUpdateAvailable',
72
- 'chrome.runtime.openOptionsPage',
73
- 'chrome.runtime.reload',
74
- 'chrome.runtime.requestUpdateCheck',
75
- 'chrome.runtime.restart',
76
- 'chrome.runtime.restartAfterDelay',
77
- 'chrome.runtime.sendMessage',
78
- 'chrome.runtime.setUninstallURL',
79
- // https://developer.chrome.com/docs/extensions/reference/api/tabs
80
- 'chrome.tabs.captureVisibleTab',
81
- 'chrome.tabs.connect',
82
- 'chrome.tabs.create',
83
- 'chrome.tabs.detectLanguage',
84
- 'chrome.tabs.discard',
85
- 'chrome.tabs.duplicate',
86
- 'chrome.tabs.executeScript',
87
- 'chrome.tabs.get',
88
- 'chrome.tabs.getCurrent',
89
- 'chrome.tabs.goBack',
90
- 'chrome.tabs.goForward',
91
- 'chrome.tabs.highlight',
92
- 'chrome.tabs.insertCSS',
93
- 'chrome.tabs.move',
94
- 'chrome.tabs.reload',
95
- 'chrome.tabs.remove',
96
- 'chrome.tabs.removeCSS',
97
- 'chrome.tabs.sendMessage',
98
- 'chrome.tabs.update'
99
- ])
100
-
101
- export function findDeprecated(api) {
102
- for (const [deprecated, replacement] of deprecatedAPIs)
103
- if (api == deprecated || api.startsWith(`${deprecated}.`))
104
- return { deprecated, replacement }
105
- return null
1
+ export default {
2
+ deprecated: new Map([
3
+ ['chrome.browserAction', 'chrome.action'],
4
+ ['chrome.declarativeWebRequest', 'chrome.declarativeNetRequest'],
5
+ ['chrome.extension.connect', 'chrome.runtime.connect'],
6
+ ['chrome.extension.getBackgroundPage', 'chrome.runtime.getContexts'],
7
+ ['chrome.extension.getURL', 'chrome.runtime.getURL'],
8
+ ['chrome.extension.lastError', 'chrome.runtime.lastError'],
9
+ ['chrome.extension.onConnect', 'chrome.runtime.onConnect'],
10
+ ['chrome.extension.onConnectExternal', 'chrome.runtime.onConnectExternal'],
11
+ ['chrome.extension.onRequest', 'chrome.runtime.onMessage'],
12
+ ['chrome.extension.onRequestExternal', 'chrome.runtime.onMessageExternal'],
13
+ ['chrome.extension.sendRequest', 'chrome.runtime.sendMessage'],
14
+ ['chrome.fileBrowserHandler', 'File Handling API'],
15
+ ['chrome.pageAction.show', 'chrome.action.enable'],
16
+ ['chrome.pageAction.hide', 'chrome.action.disable'],
17
+ ['chrome.pageAction.setIcon', 'chrome.action.setIcon'],
18
+ ['chrome.pageAction.setTitle', 'chrome.action.setTitle'],
19
+ ['chrome.pageAction.setPopup', 'chrome.action.setPopup'],
20
+ ['chrome.pageAction.getTitle', 'chrome.action.getTitle'],
21
+ ['chrome.pageAction.getPopup', 'chrome.action.getPopup'],
22
+ ['chrome.pageAction', 'chrome.action'], // must stay after pageAction.* — prefix match
23
+ ['chrome.tabs.executeScript', 'chrome.scripting.executeScript'],
24
+ ['chrome.tabs.getAllInWindow', 'chrome.tabs.query({ currentWindow: true })'],
25
+ ['chrome.tabs.getSelected', 'chrome.tabs.query({ active: true })'],
26
+ ['chrome.tabs.insertCSS', 'chrome.scripting.insertCSS'],
27
+ ['chrome.tabs.onActiveChanged', 'chrome.tabs.onActivated'],
28
+ ['chrome.tabs.onSelectionChanged', 'chrome.tabs.onActivated'],
29
+ ['chrome.tabs.removeCSS', 'chrome.scripting.removeCSS'],
30
+ ['chrome.tabs.sendRequest', 'chrome.tabs.sendMessage']
31
+ ]),
32
+ externalMsgListeners: new Set(['chrome.runtime.onMessageExternal', 'chrome.runtime.onConnectExternal']),
33
+ permissionMap: new Map([
34
+ ['chrome.accessibilityFeatures.set', 'accessibilityFeatures.modify'],
35
+ ['chrome.accessibilityFeatures.clear', 'accessibilityFeatures.modify'],
36
+ ['chrome.accessibilityFeatures', 'accessibilityFeatures.read'],
37
+ ['chrome.alarms', 'alarms'],
38
+ ['chrome.audio', 'audio'],
39
+ ['chrome.bookmarks', 'bookmarks'],
40
+ ['chrome.browsingData', 'browsingData'],
41
+ ['chrome.certificateProvider', 'certificateProvider'],
42
+ ['chrome.contentSettings', 'contentSettings'],
43
+ ['chrome.contextMenus', 'contextMenus'],
44
+ ['chrome.cookies', 'cookies'],
45
+ ['chrome.debugger', 'debugger'],
46
+ ['chrome.declarativeContent', 'declarativeContent'],
47
+ ['chrome.declarativeNetRequest', 'declarativeNetRequest'],
48
+ ['chrome.dns', 'dns'],
49
+ ['chrome.desktopCapture', 'desktopCapture'],
50
+ ['chrome.documentScan', 'documentScan'],
51
+ ['chrome.downloads.open', 'downloads.open'],
52
+ ['chrome.downloads.setUiOptions', 'downloads.ui'],
53
+ ['chrome.downloads', 'downloads'],
54
+ ['chrome.enterprise.deviceAttributes', 'enterprise.deviceAttributes'],
55
+ ['chrome.enterprise.hardwarePlatform', 'enterprise.hardwarePlatform'],
56
+ ['chrome.enterprise.networkingAttributes','enterprise.networkingAttributes'],
57
+ ['chrome.enterprise.platformKeys', 'enterprise.platformKeys'],
58
+ ['chrome.favicon', 'favicon'],
59
+ ['chrome.fileBrowserHandler', 'fileBrowserHandler'],
60
+ ['chrome.fontSettings', 'fontSettings'],
61
+ ['chrome.gcm', 'gcm'],
62
+ ['chrome.instanceID', 'gcm'],
63
+ ['chrome.history', 'history'],
64
+ ['chrome.identity.getProfileUserInfo', 'identity.email'],
65
+ ['chrome.identity', 'identity'],
66
+ ['chrome.idle', 'idle'],
67
+ ['chrome.loginState', 'loginState'],
68
+ ['chrome.management', 'management'],
69
+ ['chrome.runtime.connectNative', 'nativeMessaging'],
70
+ ['chrome.runtime.sendNativeMessage', 'nativeMessaging'],
71
+ ['chrome.notifications', 'notifications'],
72
+ ['chrome.offscreen', 'offscreen'],
73
+ ['chrome.pageCapture', 'pageCapture'],
74
+ ['chrome.platformKeys', 'platformKeys'],
75
+ ['chrome.power', 'power'],
76
+ ['chrome.printerProvider', 'printerProvider'],
77
+ ['chrome.printingMetrics', 'printingMetrics'],
78
+ ['chrome.printing', 'printing'],
79
+ ['chrome.privacy', 'privacy'],
80
+ ['chrome.processes', 'processes'],
81
+ ['chrome.proxy', 'proxy'],
82
+ ['chrome.publicSuffix', 'publicSuffix'],
83
+ ['chrome.readingList', 'readingList'],
84
+ ['chrome.scripting', 'scripting'],
85
+ ['chrome.search', 'search'],
86
+ ['chrome.sessions', 'sessions'],
87
+ ['chrome.sidePanel', 'sidePanel'],
88
+ ['chrome.storage', 'storage'],
89
+ ['chrome.system.cpu', 'system.cpu'],
90
+ ['chrome.system.display', 'system.display'],
91
+ ['chrome.system.memory', 'system.memory'],
92
+ ['chrome.system.storage', 'system.storage'],
93
+ ['chrome.tabCapture', 'tabCapture'],
94
+ ['chrome.tabGroups', 'tabGroups'],
95
+ ['chrome.tabs.query', 'tabs'],
96
+ ['chrome.topSites', 'topSites'],
97
+ ['chrome.ttsEngine', 'ttsEngine'],
98
+ ['chrome.tts', 'tts'],
99
+ ['chrome.userScripts', 'userScripts'],
100
+ ['chrome.vpnProvider', 'vpnProvider'],
101
+ ['chrome.wallpaper', 'wallpaper'],
102
+ ['chrome.webAuthenticationProxy', 'webAuthenticationProxy'],
103
+ ['chrome.webNavigation', 'webNavigation'],
104
+ ['chrome.webRequest', 'webRequest']
105
+ ]),
106
+ permless: new Set([
107
+ 'chrome.i18n',
108
+ 'chrome.management.getPermissionWarningsByManifest',
109
+ 'chrome.management.getSelf',
110
+ 'chrome.management.uninstallSelf',
111
+ 'chrome.runtime.connect',
112
+ 'chrome.runtime.getManifest',
113
+ 'chrome.runtime.getPlatformInfo',
114
+ 'chrome.runtime.getURL',
115
+ 'chrome.runtime.onBrowserUpdateAvailable', // eslint-disable-line no-secrets/no-secrets
116
+ 'chrome.runtime.onConnect',
117
+ 'chrome.runtime.onConnectExternal',
118
+ 'chrome.runtime.onInstalled',
119
+ 'chrome.runtime.onMessage',
120
+ 'chrome.runtime.onMessageExternal',
121
+ 'chrome.runtime.onStartup',
122
+ 'chrome.runtime.onSuspend',
123
+ 'chrome.runtime.onSuspendCanceled',
124
+ 'chrome.runtime.onUpdateAvailable',
125
+ 'chrome.runtime.openOptionsPage',
126
+ 'chrome.runtime.reload',
127
+ 'chrome.runtime.requestUpdateCheck',
128
+ 'chrome.runtime.restart',
129
+ 'chrome.runtime.restartAfterDelay',
130
+ 'chrome.runtime.sendMessage',
131
+ 'chrome.runtime.setUninstallURL',
132
+ 'chrome.tabs.captureVisibleTab',
133
+ 'chrome.tabs.connect',
134
+ 'chrome.tabs.create',
135
+ 'chrome.tabs.detectLanguage',
136
+ 'chrome.tabs.discard',
137
+ 'chrome.tabs.duplicate',
138
+ 'chrome.tabs.executeScript',
139
+ 'chrome.tabs.get',
140
+ 'chrome.tabs.getCurrent',
141
+ 'chrome.tabs.goBack',
142
+ 'chrome.tabs.goForward',
143
+ 'chrome.tabs.highlight',
144
+ 'chrome.tabs.insertCSS',
145
+ 'chrome.tabs.move',
146
+ 'chrome.tabs.reload',
147
+ 'chrome.tabs.remove',
148
+ 'chrome.tabs.removeCSS',
149
+ 'chrome.tabs.sendMessage',
150
+ 'chrome.tabs.update',
151
+ 'chrome.windows'
152
+ ]),
153
+ findDeprecated(api) {
154
+ for (const [deprecated, replacement] of this.deprecated)
155
+ if (api == deprecated || api.startsWith(`${deprecated}.`))
156
+ return { deprecated, replacement }
157
+ return null
158
+ },
159
+ findPermission(api) {
160
+ if (this.permless.has(api)) return null
161
+ for (const [prefix, permission] of this.permissionMap)
162
+ if (api == prefix || api.startsWith(`${prefix}.`))
163
+ return permission
164
+ return null
165
+ },
166
+ isExternalMsgListener(api) { return this.externalMsgListeners.has(api) }
106
167
  }
107
-
108
- export function findPermission(api) {
109
- if (permlessAPIs.has(api)) return null
110
- for (const [prefix, permission] of apiPermissionMap)
111
- if (api == prefix || api.startsWith(`${prefix}.`))
112
- return permission
113
- return null
114
- }
115
-
116
- export function isExternalMessagingListener(api) { return externalMsgListeners.has(api) }
@@ -1,10 +1,10 @@
1
1
  // Delete a line to end support
2
2
 
3
- export const bundles = {
4
- 'no-invalid-permissions': ['recommended'], // 2026-10-5 announced future removal
5
- 'require-mv3': ['recommended'], // 2026-10-5 announced future removal
6
- 'valid-mv3-schema': ['recommended'], // 2026-10-5 announced future removal
7
- 'valid-version-field': ['recommended'] // 2026-10-5 announced future removal
3
+ export default {
4
+ bundles: {
5
+ 'no-invalid-permissions': ['recommended'], // 2026-10-5 announced future removal
6
+ 'require-mv3': ['recommended'], // 2026-10-5 announced future removal
7
+ 'valid-mv3-schema': ['recommended'], // 2026-10-5 announced future removal
8
+ 'valid-version-field': ['recommended'] // 2026-10-5 announced future removal
9
+ }
8
10
  }
9
-
10
- export default { bundles }
package/src/data/i18n.js CHANGED
@@ -1,71 +1,13 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename -- lint-time project inspection only */
2
-
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
-
6
- export const re = {
7
- getMsgLiteral: /\b(?:chrome|browser)\.i18n\.getMessage\(\s*['"]([^'"]+)['"]/g,
8
- getMsgTemplate: /\b(?:chrome|browser)\.i18n\.getMessage\(\s*`([^`]*)`/g,
9
- msgPlaceholder: /__MSG_([\w@]+)__/g,
10
- specialChars: /[.*+?^${}()|[\]\\]/g,
11
- templateExpr: /\$\{[^}]*\}/g
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
- }