eslint-plugin-chrome-extensions 2.4.0 → 2.4.1
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 +20 -22
- package/package.json +4 -2
- package/src/data/manifest.js +17 -0
- package/src/rules/no-invalid-permissions.js +3 -3
- package/src/rules/no-unused-permissions.js +3 -3
- package/src/rules/valid-filenames.js +1 -0
- package/src/rules/valid-mv3-schema.js +3 -3
- package/src/rules/valid-service-worker.js +4 -3
- package/src/rules/valid-url-fields.js +2 -18
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ import chromeExtensions from 'eslint-plugin-chrome-extensions'
|
|
|
40
40
|
export default [
|
|
41
41
|
// other configs
|
|
42
42
|
{
|
|
43
|
-
files: ['
|
|
43
|
+
files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
|
|
44
44
|
plugins: { 'chrome-extensions': chromeExtensions },
|
|
45
45
|
rules: {
|
|
46
46
|
...chromeExtensions.configs.recommended.rules,
|
|
@@ -55,11 +55,11 @@ export default [
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
{ files: ['**/*LICENSE*'], ...chromeExtensions.configs.license },
|
|
58
|
-
{ files: ['
|
|
59
|
-
{ files: ['
|
|
60
|
-
{ files: ['
|
|
61
|
-
{ files: ['
|
|
62
|
-
{ files: ['
|
|
58
|
+
{ files: ['**/extension/**/*.css'], ...chromeExtensions.configs.css },
|
|
59
|
+
{ files: ['**/extension/**/*.html'], ...chromeExtensions.configs.html },
|
|
60
|
+
{ files: ['**/extension/**/*.json'], ...chromeExtensions.configs.json },
|
|
61
|
+
{ files: ['**/extension/**/*.json5'], ...chromeExtensions.configs.json5 },
|
|
62
|
+
{ files: ['**/extension/**/*.jsonc'], ...chromeExtensions.configs.jsonc }
|
|
63
63
|
]
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -488,7 +488,7 @@ If you intentionally target HTTP endpoints.
|
|
|
488
488
|
|
|
489
489
|
<details>
|
|
490
490
|
|
|
491
|
-
<summary>Requires sender/origin validation + filtered <code>tabs.query()</code> + explicit <code>window.postMessage()</code> target.</summary>
|
|
491
|
+
<summary>Requires sender/origin validation + filtered <code>chrome.tabs.query()</code> + explicit <code>window.postMessage()</code> target.</summary>
|
|
492
492
|
|
|
493
493
|
<h4 id="no-insecure-messaging-options">Options</h4>
|
|
494
494
|
|
|
@@ -728,9 +728,9 @@ If your `_locales/` is externally populated at build time.
|
|
|
728
728
|
<img height=18 alt="[HTML]" src="https://img.shields.io/badge/HTML-a8d8ea?style=flat-square" align="middle"></a>
|
|
729
729
|
<a title="Works in 'json/json' config block" href="#usage">
|
|
730
730
|
<img height=18 alt="[JSON]" src="https://img.shields.io/badge/JSON-a8e6cf?style=flat-square" align="middle"></a>
|
|
731
|
-
<a title="Works in 'json5' config block" href="#usage">
|
|
731
|
+
<a title="Works in 'json/json5' config block" href="#usage">
|
|
732
732
|
<img height=18 alt="[JSON5]" src="https://img.shields.io/badge/JSON5-c7e6b8?style=flat-square" align="middle"></a>
|
|
733
|
-
<a title="Works in 'jsonc' config block" href="#usage">
|
|
733
|
+
<a title="Works in 'json/jsonc' config block" href="#usage">
|
|
734
734
|
<img height=18 alt="[JSONC]" src="https://img.shields.io/badge/JSONC-e6e6a8?style=flat-square" align="middle"></a>
|
|
735
735
|
<a title="Accepts array w/ options" href="#usage">
|
|
736
736
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
@@ -760,7 +760,7 @@ If your `_locales/` is externally populated at build time.
|
|
|
760
760
|
}
|
|
761
761
|
|
|
762
762
|
// _locales/en/messages.json
|
|
763
|
-
{ "unrelated": { "message": "
|
|
763
|
+
{ "unrelated": { "message": "sup" }}
|
|
764
764
|
```
|
|
765
765
|
|
|
766
766
|
#### <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
|
|
@@ -776,7 +776,7 @@ If your `_locales/` is externally populated at build time.
|
|
|
776
776
|
}
|
|
777
777
|
|
|
778
778
|
// _locales/en/messages.json
|
|
779
|
-
- { "unrelated": { "message": "
|
|
779
|
+
- { "unrelated": { "message": "sup" }}
|
|
780
780
|
+ {
|
|
781
781
|
+ "greeting": { "message": "Hello" },
|
|
782
782
|
+ "extName": { "message": "My Extension" }
|
|
@@ -1135,8 +1135,8 @@ If your i18n keys are referenced dynamically.
|
|
|
1135
1135
|
<a title="Works in 'css/css' config block" href="#usage"><img height=14 alt="[CSS]" src="https://img.shields.io/badge/CSS-dcd6f7?style=flat-square" align="middle"></a>
|
|
1136
1136
|
<a title="Works in 'html/html' config block" href="#usage"><img height=14 alt="[HTML]" src="https://img.shields.io/badge/HTML-a8d8ea?style=flat-square" align="middle"></a>
|
|
1137
1137
|
<a title="Works in 'json/json' config block" href="#usage"><img height=14 alt="[JSON]" src="https://img.shields.io/badge/JSON-a8e6cf?style=flat-square" align="middle"></a>
|
|
1138
|
-
<a title="Works in 'json5' config block" href="#usage"><img height=14 alt="[JSON5]" src="https://img.shields.io/badge/JSON5-c7e6b8?style=flat-square" align="middle"></a>
|
|
1139
|
-
<a title="Works in 'jsonc' config block" href="#usage"><img height=14 alt="[JSONC]" src="https://img.shields.io/badge/JSONC-e6e6a8?style=flat-square" align="middle"></a>
|
|
1138
|
+
<a title="Works in 'json/json5' config block" href="#usage"><img height=14 alt="[JSON5]" src="https://img.shields.io/badge/JSON5-c7e6b8?style=flat-square" align="middle"></a>
|
|
1139
|
+
<a title="Works in 'json/jsonc' config block" href="#usage"><img height=14 alt="[JSONC]" src="https://img.shields.io/badge/JSONC-e6e6a8?style=flat-square" align="middle"></a>
|
|
1140
1140
|
<a title="Accepts array w/ options" href="#no-missing-i18n-keys-options"><img height=14 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
1141
1141
|
- <img height=12 src="https://www.google.com/s2/favicons?domain=developer.chrome.com&sz=64"> <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/i18n">Chrome for Developers — `i18n`</a>
|
|
1142
1142
|
|
|
@@ -1228,20 +1228,18 @@ If you access the granted API through an alias or computed property rather than
|
|
|
1228
1228
|
|
|
1229
1229
|
<summary>Requires a license file in the extension.</summary>
|
|
1230
1230
|
|
|
1231
|
-
#### <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
|
|
1231
|
+
#### <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 structure
|
|
1232
1232
|
|
|
1233
1233
|
```diff
|
|
1234
|
-
// files
|
|
1235
1234
|
/extension/manifest.json
|
|
1236
1235
|
!
|
|
1237
1236
|
```
|
|
1238
1237
|
|
|
1239
|
-
#### <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
|
|
1238
|
+
#### <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 structure
|
|
1240
1239
|
|
|
1241
1240
|
```diff
|
|
1242
|
-
// files
|
|
1243
1241
|
/extension/manifest.json
|
|
1244
|
-
+ /extension/LICENSE
|
|
1242
|
+
+ /extension/LICENSE.md
|
|
1245
1243
|
```
|
|
1246
1244
|
|
|
1247
1245
|
#### When not to use it
|
|
@@ -1403,9 +1401,9 @@ If you primarily target a browser that doesn't support `short_name`.
|
|
|
1403
1401
|
<img height=18 alt="[HTML]" src="https://img.shields.io/badge/HTML-a8d8ea?style=flat-square" align="middle"></a>
|
|
1404
1402
|
<a title="Works in 'json/json' config block" href="#usage">
|
|
1405
1403
|
<img height=18 alt="[JSON]" src="https://img.shields.io/badge/JSON-a8e6cf?style=flat-square" align="middle"></a>
|
|
1406
|
-
<a title="Works in 'json5' config block" href="#usage">
|
|
1404
|
+
<a title="Works in 'json/json5' config block" href="#usage">
|
|
1407
1405
|
<img height=18 alt="[JSON5]" src="https://img.shields.io/badge/JSON5-c7e6b8?style=flat-square" align="middle"></a>
|
|
1408
|
-
<a title="Works in 'jsonc' config block" href="#usage">
|
|
1406
|
+
<a title="Works in 'json/jsonc' config block" href="#usage">
|
|
1409
1407
|
<img height=18 alt="[JSONC]" src="https://img.shields.io/badge/JSONC-e6e6a8?style=flat-square" align="middle"></a>
|
|
1410
1408
|
<a title="Accepts array w/ options" href="#usage">
|
|
1411
1409
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
@@ -1776,7 +1774,7 @@ If your background context isn't a service worker.
|
|
|
1776
1774
|
<h4 id="valid-url-fields-options">Options</h4>
|
|
1777
1775
|
|
|
1778
1776
|
- `checkURLfields` (default `true`) – validate URL fields (<a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/chrome-settings-override">`chrome_settings_overrides`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/homepage-url">`homepage_url`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/oauth2">`oauth2.scopes`</a>, <a target="_blank" href="https://developer.chrome.com/docs/apps/autoupdate#update_url">`update_url`</a>)
|
|
1779
|
-
- `checkMatchPatterns` (default `true`) – validate match patterns (in <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/content-scripts">`content_scripts`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable">`externally_connectable`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions">`host_permissions`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">`optional_host_permissions`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources">`web_accessible_resources`</a>)
|
|
1777
|
+
- `checkMatchPatterns` (default `true`) – validate <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">match patterns</a> (in <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/content-scripts">`content_scripts`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable">`externally_connectable`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions">`host_permissions`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">`optional_host_permissions`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources">`web_accessible_resources`</a>)
|
|
1780
1778
|
- `allowedSchemes` (default `['https', 'file']`) – permitted URL schemes for <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">match patterns</a>
|
|
1781
1779
|
- `allowAllURLs` (default `false`) – allow unrestricted patterns (<a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">`<all_urls>`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">`https://*/*`</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">`*://*/*`</a>)
|
|
1782
1780
|
|
|
@@ -1810,7 +1808,7 @@ If your background context isn't a service worker.
|
|
|
1810
1808
|
|
|
1811
1809
|
#### When not to use it
|
|
1812
1810
|
|
|
1813
|
-
If your URLs or
|
|
1811
|
+
If your URLs or patterns are generated at build time.
|
|
1814
1812
|
|
|
1815
1813
|
#### Introduced in
|
|
1816
1814
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-chrome-extensions",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "ESLint rules for Chrome extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -66,12 +66,14 @@
|
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@e18e/eslint-plugin": "^0.8.1",
|
|
69
|
+
"@eslint/css": "^2.0.0",
|
|
69
70
|
"@eslint/js": "^10.0.1",
|
|
70
71
|
"@eslint/json": "^2.1.1",
|
|
71
72
|
"@eslint/markdown": "^7.5.1",
|
|
73
|
+
"@html-eslint/eslint-plugin": "^0.66.1",
|
|
72
74
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
73
75
|
"eslint": "^10.12.0",
|
|
74
|
-
"eslint-plugin-constructors": "^1.4.
|
|
76
|
+
"eslint-plugin-constructors": "^1.4.3",
|
|
75
77
|
"eslint-plugin-eslint-plugin": "^7.6.2",
|
|
76
78
|
"eslint-plugin-import-x": "^4.17.1",
|
|
77
79
|
"eslint-plugin-n": "^18.4.1",
|
package/src/data/manifest.js
CHANGED
|
@@ -36,9 +36,26 @@ export const manifestKeyOrder = [
|
|
|
36
36
|
'theme_experiment', 'tts_engine', 'update_url', 'version_name', 'web_accessible_resources'
|
|
37
37
|
]
|
|
38
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
|
+
|
|
39
44
|
export const mv2OnlyKeys = new Set(['browser_action', 'page_action'])
|
|
40
45
|
|
|
41
46
|
export const remoteCodePatterns = [
|
|
42
47
|
'eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']
|
|
43
48
|
|
|
44
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
|
+
])
|
|
@@ -56,9 +56,9 @@ export default {
|
|
|
56
56
|
const keyName = keyNode.value ?? keyNode.name
|
|
57
57
|
if (keyName != 'permissions' && keyName != 'optional_permissions') return
|
|
58
58
|
const arrNode = node.value ; if (arrNode?.type != 'Array') return
|
|
59
|
-
const
|
|
60
|
-
for (const
|
|
61
|
-
const valueNode =
|
|
59
|
+
const elems = arrNode.elements || arrNode.items || []
|
|
60
|
+
for (const elem of elems) {
|
|
61
|
+
const valueNode = elem.value || elem
|
|
62
62
|
checkPermission(valueNode, valueNode?.value)
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -64,9 +64,9 @@ export default {
|
|
|
64
64
|
const keyName = keyNode.value ?? keyNode.name
|
|
65
65
|
if (keyName != 'permissions' && keyName != 'optional_permissions') return
|
|
66
66
|
const arrNode = node.value ; if (arrNode?.type != 'Array') return
|
|
67
|
-
const
|
|
68
|
-
for (const
|
|
69
|
-
const valueNode =
|
|
67
|
+
const elems = arrNode.elements || arrNode.items || []
|
|
68
|
+
for (const elem of elems) {
|
|
69
|
+
const valueNode = elem.value || elem
|
|
70
70
|
const perm = valueNode?.value
|
|
71
71
|
if (typeof perm != 'string') continue
|
|
72
72
|
if (!permissionNodes.has(perm)) permissionNodes.set(perm, [])
|
|
@@ -64,12 +64,12 @@ export default {
|
|
|
64
64
|
data: { directive }})
|
|
65
65
|
if (inDataCollection && (keyName == 'required' || keyName == 'optional')
|
|
66
66
|
&& Array.isArray(node.value?.elements)
|
|
67
|
-
) for (const
|
|
68
|
-
const value = typeof
|
|
67
|
+
) for (const elem of node.value.elements) {
|
|
68
|
+
const value = typeof elem.value == 'string' ? elem.value : elem.value?.value
|
|
69
69
|
if (typeof value == 'string') {
|
|
70
70
|
const set = keyName == 'required' ? ffDataCollectionRequired : ffDataCollectionOptional
|
|
71
71
|
if (!set.has(value))
|
|
72
|
-
pendingReports.push({ node:
|
|
72
|
+
pendingReports.push({ node: elem, messageId: 'invalidDataCollectionValue',
|
|
73
73
|
data: { value, kind: keyName }})
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -35,9 +35,10 @@ export default {
|
|
|
35
35
|
) context.report({ node, messageId: 'keepaliveTimer', data: { fn: node.callee.name }})
|
|
36
36
|
},
|
|
37
37
|
Identifier(node) {
|
|
38
|
-
if (node.parent?.type == 'MemberExpression' && node.parent.property === node
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (node.parent?.type == 'MemberExpression' && node.parent.property === node
|
|
39
|
+
|| node.parent?.type == 'Property' && node.parent.key === node && !node.parent.computed
|
|
40
|
+
|| !domGlobals.has(node.name)
|
|
41
|
+
) return
|
|
41
42
|
for (let scope = context.sourceCode.getScope(node) ;
|
|
42
43
|
scope && scope.type != 'global' ;
|
|
43
44
|
scope = scope.upper
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchPaths, urlPaths } from '../data/manifest.js'
|
|
1
2
|
import { getFilename } from '../utils.js'
|
|
2
3
|
|
|
3
4
|
const re = {
|
|
@@ -5,28 +6,11 @@ const re = {
|
|
|
5
6
|
match: /^(\*|https?|file|ftp):\/\/([^/]*)(\/.*)$/, template: /\{[^}]+\}/g
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
const matchPaths = new Set([
|
|
9
|
-
'content_scripts.matches', 'externally_connectable.matches',
|
|
10
|
-
'host_permissions', 'optional_host_permissions', 'web_accessible_resources.matches'
|
|
11
|
-
])
|
|
12
|
-
|
|
13
|
-
const urlPaths = new Set([
|
|
14
|
-
'homepage_url', 'update_url', 'oauth2.scopes',
|
|
15
|
-
'chrome_settings_overrides.homepage',
|
|
16
|
-
'chrome_settings_overrides.search_provider.alternate_urls',
|
|
17
|
-
'chrome_settings_overrides.search_provider.favicon_url',
|
|
18
|
-
'chrome_settings_overrides.search_provider.image_url',
|
|
19
|
-
'chrome_settings_overrides.search_provider.instant_url',
|
|
20
|
-
'chrome_settings_overrides.search_provider.search_url',
|
|
21
|
-
'chrome_settings_overrides.search_provider.suggest_url',
|
|
22
|
-
'chrome_settings_overrides.startup_pages'
|
|
23
|
-
])
|
|
24
|
-
|
|
25
9
|
function collectStrings(obj, path = [], out = []) {
|
|
26
10
|
if (obj == null) return out
|
|
27
11
|
if (typeof obj == 'string') out.push({ path: path.join('.'), value: obj })
|
|
28
12
|
else if (Array.isArray(obj))
|
|
29
|
-
for (const
|
|
13
|
+
for (const elem of obj) collectStrings(elem, path, out)
|
|
30
14
|
else if (typeof obj == 'object')
|
|
31
15
|
for (const key of Object.keys(obj)) // eslint-disable-next-line security/detect-object-injection
|
|
32
16
|
collectStrings(obj[key], [...path, key], out)
|