eslint-plugin-chrome-extensions 2.4.1 → 2.4.2
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 +15 -18
- package/package.json +2 -2
- package/src/data/license.js +2 -1
- package/src/index.js +2 -1
- package/src/rules/manifest-key-order.js +2 -2
- package/src/rules/manifest-spacing.js +2 -2
- package/src/rules/no-content-style-leak.js +2 -2
- package/src/rules/no-deprecated-apis.js +1 -1
- package/src/rules/no-duplicate-manifest-keys.js +2 -2
- package/src/rules/no-insecure-messaging.js +1 -1
- package/src/rules/no-invalid-permissions.js +2 -2
- package/src/rules/no-missing-default-locale.js +1 -1
- package/src/rules/no-missing-i18n-keys.js +2 -1
- package/src/rules/no-missing-permissions.js +2 -1
- package/src/rules/no-missing-web-accessible-resources.js +3 -2
- package/src/rules/no-remote-code.js +1 -1
- package/src/rules/no-unrestricted-host-permissions.js +2 -2
- package/src/rules/no-unused-i18n-keys.js +1 -1
- package/src/rules/no-unused-permissions.js +2 -2
- package/src/rules/require-license-file.js +8 -6
- package/src/rules/require-mv3.js +2 -2
- package/src/rules/require-short-name-when-useful.js +2 -2
- package/src/rules/valid-manifest-icons.js +2 -2
- package/src/rules/valid-mv3-schema.js +2 -2
- package/src/rules/valid-service-worker.js +7 -5
- package/src/rules/valid-url-fields.js +2 -2
- package/src/rules/valid-version-field.js +2 -2
- package/src/utils/bundle.js +10 -0
- package/src/utils/extension.js +45 -0
- package/src/utils/node.js +22 -0
- package/src/utils/pkg.js +19 -0
- package/src/utils/rules.js +5 -0
- package/src/utils.js +0 -95
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<a target="_blank" href="https://www.npmjs.com/package/eslint-plugin-chrome-extensions">
|
|
6
6
|
<img alt="[Latest Build]" src="https://img.shields.io/npm/v/eslint-plugin-chrome-extensions?logo=icinga&style=for-the-badge&label=Latest%20Build&color=32fcee"></a>
|
|
7
|
-
<a href="
|
|
7
|
+
<a href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/src/branch/main/docs/LICENSE.md">
|
|
8
8
|
<img alt="[License: MIT]" src="https://img.shields.io/badge/License-MIT-f1f20a.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
|
|
9
9
|
<a target="_blank" href="https://eslint.org/">
|
|
10
10
|
<img alt="[Requires ESLint >=9.6.0]" src="https://img.shields.io/badge/Requires-ESLint_%3E%3D9.6.0-4B32C3?style=for-the-badge&logo=eslint"></a>
|
|
@@ -54,7 +54,7 @@ export default [
|
|
|
54
54
|
'chrome-extensions/manifest-spacing': ['warn', { spaces: 4 }] // selectively tweak rule
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
|
-
{ files: ['**/*LICENSE*'],
|
|
57
|
+
{ files: ['**/*LICENSE*'], ...chromeExtensions.configs.license },
|
|
58
58
|
{ files: ['**/extension/**/*.css'], ...chromeExtensions.configs.css },
|
|
59
59
|
{ files: ['**/extension/**/*.html'], ...chromeExtensions.configs.html },
|
|
60
60
|
{ files: ['**/extension/**/*.json'], ...chromeExtensions.configs.json },
|
|
@@ -386,9 +386,8 @@ If you don't want deprecation validation at all. For individual deprecated APIs
|
|
|
386
386
|
- "name": "Foo",
|
|
387
387
|
- "name": "Bar",
|
|
388
388
|
"name": "My Extension",
|
|
389
|
-
- "version": "1.0.0",
|
|
390
|
-
- "version": "2.0.0"
|
|
391
389
|
"version": "1.0.0"
|
|
390
|
+
- "version": "2.0.0"
|
|
392
391
|
}
|
|
393
392
|
```
|
|
394
393
|
|
|
@@ -1228,14 +1227,14 @@ If you access the granted API through an alias or computed property rather than
|
|
|
1228
1227
|
|
|
1229
1228
|
<summary>Requires a license file in the extension.</summary>
|
|
1230
1229
|
|
|
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
|
|
1230
|
+
#### <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 directory structure
|
|
1232
1231
|
|
|
1233
1232
|
```diff
|
|
1234
1233
|
/extension/manifest.json
|
|
1235
1234
|
!
|
|
1236
1235
|
```
|
|
1237
1236
|
|
|
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
|
|
1237
|
+
#### <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 directory structure
|
|
1239
1238
|
|
|
1240
1239
|
```diff
|
|
1241
1240
|
/extension/manifest.json
|
|
@@ -1253,7 +1252,7 @@ If you intentionally omit a license, or it's injected at build time.
|
|
|
1253
1252
|
#### See also
|
|
1254
1253
|
|
|
1255
1254
|
- [`valid-license-file`](#valid-license-file)
|
|
1256
|
-
<a title="Only works in
|
|
1255
|
+
<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>
|
|
1257
1256
|
<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>
|
|
1258
1257
|
|
|
1259
1258
|
<div align="center">
|
|
@@ -1483,7 +1482,7 @@ If you don't use conventional filenames or paths, or they're generated at build
|
|
|
1483
1482
|
|
|
1484
1483
|
<h3 id="valid-license-file">
|
|
1485
1484
|
<code>valid-license-file</code>
|
|
1486
|
-
<a title="Only works in
|
|
1485
|
+
<a title="Only works in license config block" href="#usage">
|
|
1487
1486
|
<img height=18 alt="[License only]" src="https://img.shields.io/badge/License_only-559bff?style=flat-square" align="middle"></a>
|
|
1488
1487
|
<a title="Accepts array w/ options" href="#usage">
|
|
1489
1488
|
<img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
|
|
@@ -1506,13 +1505,9 @@ If you don't use conventional filenames or paths, or they're generated at build
|
|
|
1506
1505
|
```diff
|
|
1507
1506
|
/* eslint chrome-extensions/valid-license-file: ['error', { requireSPDX: true, allowedLicenses: ['MIT'] }] */
|
|
1508
1507
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
MIT License
|
|
1512
|
-
|
|
1508
|
+
! MIT License
|
|
1509
|
+
-----------
|
|
1513
1510
|
Copyright © 2026 You
|
|
1514
|
-
|
|
1515
|
-
!
|
|
1516
1511
|
```
|
|
1517
1512
|
|
|
1518
1513
|
#### <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
|
|
@@ -1520,15 +1515,17 @@ If you don't use conventional filenames or paths, or they're generated at build
|
|
|
1520
1515
|
```diff
|
|
1521
1516
|
/* eslint chrome-extensions/valid-license-file: ['error', { requireSPDX: true, allowedLicenses: ['MIT'] }] */
|
|
1522
1517
|
|
|
1523
|
-
// LICENSE.md
|
|
1524
|
-
|
|
1525
1518
|
MIT License
|
|
1526
|
-
|
|
1519
|
+
-----------
|
|
1527
1520
|
Copyright © 2026 You
|
|
1528
|
-
|
|
1521
|
+
+
|
|
1529
1522
|
+ SPDX-License-Identifier: MIT
|
|
1530
1523
|
```
|
|
1531
1524
|
|
|
1525
|
+
#### Benefits
|
|
1526
|
+
|
|
1527
|
+
Makes the license machine-readable by compliance scanners + SBOM generators + CI gates.
|
|
1528
|
+
|
|
1532
1529
|
#### When not to use it
|
|
1533
1530
|
|
|
1534
1531
|
If your license is injected at build time, or you use a license w/ no SPDX identifier.
|
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.2",
|
|
4
4
|
"description": "ESLint rules for Chrome extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "
|
|
20
|
+
"url": "https://codeberg.org/adamlui/eslint-plugin-chrome-extensions.git"
|
|
21
21
|
},
|
|
22
22
|
"funding": [
|
|
23
23
|
{
|
package/src/data/license.js
CHANGED
|
@@ -3,7 +3,8 @@ import path from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
export const re = {
|
|
5
5
|
licenseFilename: // e.g. LICENSE.md, LICENSE-MIT, MIT-LICENSE.md, COPYING.LESSER, LICENSE-2.0.txt
|
|
6
|
-
|
|
6
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
7
|
+
/^(?!.*\.(?:[cm]?js|[cm]?ts)x?$)(?:[\w.-]*-)?(?:LICEN[SC]E|COPYING)(?:[-.][\w.-]+)?$/i,
|
|
7
8
|
spdxTag: /SPDX-License-Identifier:\s*([\w.+-]+)/i
|
|
8
9
|
}
|
|
9
10
|
|
package/src/index.js
CHANGED
|
@@ -31,7 +31,8 @@ import validServiceWorker from './rules/valid-service-worker.js'
|
|
|
31
31
|
import validURLfields from './rules/valid-url-fields.js'
|
|
32
32
|
import validVersionField from './rules/valid-version-field.js'
|
|
33
33
|
|
|
34
|
-
import { bundleRules
|
|
34
|
+
import { bundle as bundleRules } from './utils/rules.js'
|
|
35
|
+
import { initDeprecation as initBundleDeprecation } from './utils/bundle.js'
|
|
35
36
|
import { textParser } from './data/text-parser.js'
|
|
36
37
|
|
|
37
38
|
const plugin = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { manifestKeyOrder } from '../data/manifest.js'
|
|
2
|
-
import {
|
|
2
|
+
import { file } from '../utils/extension.js'
|
|
3
3
|
|
|
4
4
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
5
5
|
|
|
@@ -43,7 +43,7 @@ export default {
|
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
create(context) {
|
|
46
|
-
if (!re.manifestFilename.test(
|
|
46
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
47
47
|
const custom = context.options[0]?.order ?? [],
|
|
48
48
|
master = custom.length ? manifestKeyOrder.filter(key => !custom.includes(key))
|
|
49
49
|
: manifestKeyOrder
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { manifestFilename: /manifest\.json$/, leadingWhitespace: /^[ \t]+/ },
|
|
4
4
|
brackets = { open: new Set(['LBrace', 'LBracket']), close: new Set(['RBrace', 'RBracket']) }
|
|
@@ -57,7 +57,7 @@ export default {
|
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
create(context) {
|
|
60
|
-
if (!re.manifestFilename.test(
|
|
60
|
+
if (!re.manifestFilename.test(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 { file, readManifest } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { appendMethod: /^append(?:Child)?$/ }
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ export default {
|
|
|
17
17
|
},
|
|
18
18
|
|
|
19
19
|
create(context) {
|
|
20
|
-
const manifest = readManifest(context) ; if (!manifest || !
|
|
20
|
+
const manifest = readManifest(context) ; if (!manifest || !file.isContentScript(context, manifest)) return {}
|
|
21
21
|
const styleElems = new Set(), docAliases = new Set()
|
|
22
22
|
function isCreateStyle(node) {
|
|
23
23
|
return node?.type == 'CallExpression'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
},
|
|
19
19
|
|
|
20
20
|
create(context) {
|
|
21
|
-
if (!re.manifestFilename.test(
|
|
21
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
22
22
|
return {
|
|
23
23
|
Object(node) {
|
|
24
24
|
const members = node.members || node.properties || [],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
2
2
|
import { isValidPermission } from '../data/permissions.js'
|
|
3
3
|
|
|
4
4
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
if (allowed.has(permission) || isValidPermission(permission, target)) return
|
|
50
50
|
context.report({ node, messageId: 'invalidPermission', data: { permission, target }})
|
|
51
51
|
}
|
|
52
|
-
if (re.manifestFilename.test(
|
|
52
|
+
if (re.manifestFilename.test(file.getName(context))) // lint it directly
|
|
53
53
|
return {
|
|
54
54
|
'*'(node) {
|
|
55
55
|
const keyNode = node.name || node.key ; if (!keyNode) return
|
|
@@ -4,7 +4,7 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
6
6
|
import { findExtensionRoot, isPredefined } from '../data/i18n.js'
|
|
7
|
-
import { readManifest } from '../utils.js'
|
|
7
|
+
import { readManifest } from '../utils/extension.js'
|
|
8
8
|
|
|
9
9
|
const re = { manifestFile: /manifest\.json$/ }
|
|
10
10
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getMemberPath
|
|
1
|
+
import { getMemberPath } from '../utils/node.js'
|
|
2
|
+
import { readManifest } from '../utils/extension.js'
|
|
2
3
|
import { findExtensionRoot, isPredefined, readDefaultLocaleMessages, re as i18nRe } from '../data/i18n.js'
|
|
3
4
|
|
|
4
5
|
const re = { file: { js: /\.(cjs|js|jsx|mjs|ts|tsx)$/i, manifest: /manifest\.json$/, text: /\.(css|htm|html)$/i }}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { findPermission } from '../data/apis.js'
|
|
2
|
-
import { getMemberPath
|
|
2
|
+
import { getMemberPath } from '../utils/node.js'
|
|
3
|
+
import { readManifest } from '../utils/extension.js'
|
|
3
4
|
|
|
4
5
|
function queriesActiveTab(node) {
|
|
5
6
|
const arg = node.arguments[0] ; if (arg?.type != 'ObjectExpression') return false
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getMemberPath
|
|
1
|
+
import { getMemberPath } from '../utils/node.js'
|
|
2
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
2
3
|
|
|
3
4
|
const re = { leadingSlash: /^\//, regexSpecial: /[.+?^${}()|[\]\\]/g }
|
|
4
5
|
|
|
@@ -35,7 +36,7 @@ export default {
|
|
|
35
36
|
|
|
36
37
|
create(context) {
|
|
37
38
|
const manifest = readManifest(context) ; if (!manifest) return {}
|
|
38
|
-
if (!
|
|
39
|
+
if (!file.isContentScript(context, manifest)) return {}
|
|
39
40
|
const regexes = collectWARpatterns(manifest).map(patternToRegex)
|
|
40
41
|
return {
|
|
41
42
|
CallExpression(node) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { broadHost: /^(?:<all_urls>|\*:\/\/\*\/\*|\w+:\/\/\*\/\*)$/, manifestFilename: /manifest\.json$/ },
|
|
4
4
|
hostKeys = new Set(['host_permissions', 'optional_host_permissions', 'optional_permissions', 'permissions'])
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
},
|
|
32
32
|
|
|
33
33
|
create(context) {
|
|
34
|
-
if (!re.manifestFilename.test(
|
|
34
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
35
35
|
const allowed = new Set(context.options[0]?.allowedPatterns || [])
|
|
36
36
|
return {
|
|
37
37
|
'*'(node) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readManifest } from '../utils.js'
|
|
2
1
|
import { collectUsedKeys, defaultScanExtensions, findExtensionRoot, readDefaultLocaleMessages } from '../data/i18n.js'
|
|
2
|
+
import { readManifest } from '../utils/extension.js'
|
|
3
3
|
|
|
4
4
|
const re = { manifestFile: /manifest\.json$/ }
|
|
5
5
|
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { apiPermissionMap, findPermission } from '../data/apis.js'
|
|
5
|
-
import {
|
|
5
|
+
import { file, findRoot as findExtensionRoot, readManifest } from '../utils/extension.js'
|
|
6
6
|
|
|
7
7
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
8
8
|
|
|
@@ -44,7 +44,7 @@ export default {
|
|
|
44
44
|
},
|
|
45
45
|
|
|
46
46
|
create(context) {
|
|
47
|
-
if (!re.manifestFilename.test(
|
|
47
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
48
48
|
const manifest = readManifest(context) ; if (!manifest) return {}
|
|
49
49
|
const root = findExtensionRoot(context) ; if (!root) return {}
|
|
50
50
|
const usedAPIs = collectUsedAPIs(manifest, root)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
|
|
3
3
|
import { findLicenseFile } from '../data/license.js'
|
|
4
|
+
import { findRoot as findPkgRoot } from '../utils/pkg.js'
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
meta: {
|
|
@@ -13,15 +14,16 @@ export default {
|
|
|
13
14
|
schema: [],
|
|
14
15
|
messages: {
|
|
15
16
|
missingFile: 'No license file found. Add one of: LICENSE, LICENSE.md, LICENSE.txt, COPYING '
|
|
16
|
-
+ '(at
|
|
17
|
+
+ '(at the package root or in a subdir).'
|
|
17
18
|
}
|
|
18
19
|
},
|
|
19
20
|
create(context) {
|
|
20
|
-
const { filename } = context
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const { filename } = context
|
|
22
|
+
function check(node) {
|
|
23
|
+
const pkgRoot = findPkgRoot(path.dirname(filename))
|
|
24
|
+
if (!pkgRoot || !findLicenseFile(pkgRoot))
|
|
25
|
+
context.report({ node, messageId: 'missingFile' })
|
|
26
|
+
}
|
|
25
27
|
return { Program: check, Document: check }
|
|
26
28
|
}
|
|
27
29
|
}
|
package/src/rules/require-mv3.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
},
|
|
19
19
|
|
|
20
20
|
create(context) {
|
|
21
|
-
if (re.manifestFilename.test(
|
|
21
|
+
if (re.manifestFilename.test(file.getName(context))) { // lint it directly
|
|
22
22
|
let foundVersion = null, versionNode = null
|
|
23
23
|
return {
|
|
24
24
|
'*'(node) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const defaults = { maxNameLength: 12 },
|
|
4
4
|
re = { i18nPlaceholder: /^__MSG_.+__$/, manifestFilename: /manifest\.json$/ }
|
|
@@ -43,7 +43,7 @@ export default {
|
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
create(context) {
|
|
46
|
-
if (!re.manifestFilename.test(
|
|
46
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
47
47
|
const opts = context.options[0] || {},
|
|
48
48
|
maxNameLength = opts.maxNameLength ?? defaults.maxNameLength,
|
|
49
49
|
always = opts.always ?? false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { findExtensionRoot,
|
|
4
|
+
import { findRoot as findExtensionRoot, file } from '../utils/extension.js'
|
|
5
5
|
|
|
6
6
|
const re = { manifestFilename: /manifest\.json$/, svg: /\.svg$/i },
|
|
7
7
|
sizes = { recommended: ['16', '32', '48', '128'], required: '128' }
|
|
@@ -47,7 +47,7 @@ export default {
|
|
|
47
47
|
},
|
|
48
48
|
|
|
49
49
|
create(context) {
|
|
50
|
-
if (!re.manifestFilename.test(
|
|
50
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
51
51
|
const opts = context.options[0] || {},
|
|
52
52
|
requireRecommended = opts.requireRecommendedSizes !== false,
|
|
53
53
|
checkFiles = opts.checkIconExists !== false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mv2OnlyKeys, forbiddenCSPdirectives, ffMV3bgKeys, ffDataCollectionRequired, ffDataCollectionOptional }
|
|
2
2
|
from '../data/manifest.js'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
5
5
|
|
|
6
6
|
const re = { manifestFilename: /manifest\.json$/ }
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ export default {
|
|
|
24
24
|
},
|
|
25
25
|
|
|
26
26
|
create(context) {
|
|
27
|
-
if (re.manifestFilename.test(
|
|
27
|
+
if (re.manifestFilename.test(file.getName(context))) { // lint it directly
|
|
28
28
|
const ancestors = [], pendingReports = [], deferredMV2 = []
|
|
29
29
|
let isMV3 = false, isFF = false
|
|
30
30
|
function keyOf(node) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { domGlobals, serviceWorkerKeepaliveMinMS } from '../data/manifest.js'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { isInsideNestedFunction } from '../utils/node.js'
|
|
4
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
3
5
|
|
|
4
6
|
export default {
|
|
5
7
|
meta: {
|
|
@@ -19,7 +21,7 @@ export default {
|
|
|
19
21
|
},
|
|
20
22
|
|
|
21
23
|
create(context) {
|
|
22
|
-
const manifest = readManifest(context) ; if (!manifest || !
|
|
24
|
+
const manifest = readManifest(context) ; if (!manifest || !file.isServiceWorker(context, manifest)) return {}
|
|
23
25
|
return {
|
|
24
26
|
CallExpression(node) {
|
|
25
27
|
if (node.callee.type == 'MemberExpression'
|
|
@@ -39,9 +41,9 @@ export default {
|
|
|
39
41
|
|| node.parent?.type == 'Property' && node.parent.key === node && !node.parent.computed
|
|
40
42
|
|| !domGlobals.has(node.name)
|
|
41
43
|
) return
|
|
42
|
-
for (let scope = context.sourceCode.getScope(node)
|
|
43
|
-
scope && scope.type != 'global'
|
|
44
|
-
scope = scope.upper
|
|
44
|
+
for (let scope = context.sourceCode.getScope(node)
|
|
45
|
+
;scope && scope.type != 'global'
|
|
46
|
+
;scope = scope.upper
|
|
45
47
|
) if (scope.set.has(node.name)) return // shadowed by a local binding
|
|
46
48
|
context.report({ node, messageId: 'domInSW', data: { global: node.name }})
|
|
47
49
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { matchPaths, urlPaths } from '../data/manifest.js'
|
|
2
|
-
import {
|
|
2
|
+
import { file } from '../utils/extension.js'
|
|
3
3
|
|
|
4
4
|
const re = {
|
|
5
5
|
absoluteURL: /^[a-z][a-z0-9+.-]*:\/\/[^\s/]+/i, manifestFilename: /manifest\.json$/,
|
|
@@ -67,7 +67,7 @@ export default {
|
|
|
67
67
|
},
|
|
68
68
|
|
|
69
69
|
create(context) {
|
|
70
|
-
if (!re.manifestFilename.test(
|
|
70
|
+
if (!re.manifestFilename.test(file.getName(context))) return {}
|
|
71
71
|
const opts = context.options[0] || {},
|
|
72
72
|
checkURLfields = opts.checkURLfields ?? true,
|
|
73
73
|
checkMatchPatterns = opts.checkMatchPatterns ?? true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file, readManifest } from '../utils/extension.js'
|
|
2
2
|
|
|
3
3
|
const re = { /* eslint-disable security/detect-unsafe-regex */
|
|
4
4
|
chromeParts: /^\d+(?:\.\d+){0,3}$/,
|
|
@@ -79,7 +79,7 @@ export default {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
if (re.manifestFilename.test(
|
|
82
|
+
if (re.manifestFilename.test(file.getName(context))) // lint it directly
|
|
83
83
|
return {
|
|
84
84
|
'*'(node) {
|
|
85
85
|
const keyNode = node.name || node.key ; if (!keyNode) return
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function initDeprecation(plugin, [ruleId, bundleNames], pluginName) {
|
|
2
|
+
for (const bundleName of bundleNames) {
|
|
3
|
+
/* eslint-disable security/detect-object-injection */
|
|
4
|
+
if (plugin.configs[bundleName])
|
|
5
|
+
plugin.configs[bundleName].rules[`${pluginName}/${ruleId}`] = 'error'
|
|
6
|
+
/* eslint-enable security/detect-object-injection */
|
|
7
|
+
if (process.env.DEBUG_MODE)
|
|
8
|
+
console.warn(`[${pluginName}] '${ruleId}' will be removed from '${bundleName}' in a future major release`)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
const manifestCache = new Map()
|
|
5
|
+
|
|
6
|
+
export function findRoot(context) {
|
|
7
|
+
let dir = path.dirname(file.getName(context))
|
|
8
|
+
while (dir) {
|
|
9
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
10
|
+
if (fs.existsSync(path.join(dir, 'manifest.json'))) return dir
|
|
11
|
+
const parent = path.dirname(dir)
|
|
12
|
+
if (parent == dir) break // reached fs root
|
|
13
|
+
dir = parent
|
|
14
|
+
}
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const file = {
|
|
19
|
+
getName(context) { return context.filename || context.getFilename() },
|
|
20
|
+
|
|
21
|
+
isContentScript(context, manifest) {
|
|
22
|
+
const scripts = (manifest?.content_scripts || []).flatMap(cs => cs.js || []) ; if (!scripts.length) return false
|
|
23
|
+
const root = findRoot(context) ; if (!root) return false
|
|
24
|
+
const filename = path.resolve(file.getName(context))
|
|
25
|
+
return scripts.some(script => filename == path.resolve(root, script))
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
isServiceWorker(context, manifest) {
|
|
29
|
+
const worker = manifest?.background?.service_worker ; if (!worker) return false
|
|
30
|
+
const root = findRoot(context) ; if (!root) return false
|
|
31
|
+
return path.resolve(file.getName(context)) == path.resolve(root, worker)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function readManifest(context) {
|
|
36
|
+
const root = findRoot(context)
|
|
37
|
+
if (!root) return null
|
|
38
|
+
if (manifestCache.has(root)) return manifestCache.get(root)
|
|
39
|
+
let parsed
|
|
40
|
+
try { // eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
41
|
+
parsed = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'))
|
|
42
|
+
} catch { parsed = null }
|
|
43
|
+
manifestCache.set(root, parsed)
|
|
44
|
+
return parsed
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function getMemberPath(node) {
|
|
2
|
+
const parts = []
|
|
3
|
+
let cur = node
|
|
4
|
+
while (cur?.type == 'MemberExpression') {
|
|
5
|
+
if (cur.computed || cur.property.type != 'Identifier') return null
|
|
6
|
+
parts.unshift(cur.property.name)
|
|
7
|
+
cur = cur.object
|
|
8
|
+
}
|
|
9
|
+
if (cur?.type != 'Identifier') return null
|
|
10
|
+
parts.unshift(cur.name)
|
|
11
|
+
return parts.join('.')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isInsideNestedFunction(node) {
|
|
15
|
+
let current = node.parent
|
|
16
|
+
while (current) {
|
|
17
|
+
if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(current.type))
|
|
18
|
+
return true
|
|
19
|
+
current = current.parent
|
|
20
|
+
}
|
|
21
|
+
return false
|
|
22
|
+
}
|
package/src/utils/pkg.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export function findRoot(dir) {
|
|
5
|
+
let current = path.resolve(dir)
|
|
6
|
+
while (true) {
|
|
7
|
+
if (current.split(path.sep).includes('node_modules')) {
|
|
8
|
+
const parent = path.dirname(current)
|
|
9
|
+
if (parent === current) return null
|
|
10
|
+
current = parent
|
|
11
|
+
continue
|
|
12
|
+
}
|
|
13
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
14
|
+
if (fs.existsSync(path.join(current, 'package.json'))) return current
|
|
15
|
+
const parent = path.dirname(current)
|
|
16
|
+
if (parent === current) return null
|
|
17
|
+
current = parent
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/utils.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
const re = { chromeAPI: /\bchrome(?:\.\w+)+/g }, // eslint-disable-line security/detect-unsafe-regex
|
|
5
|
-
manifestCache = new Map()
|
|
6
|
-
|
|
7
|
-
export function bundleRules(rules, predicate, pluginName) {
|
|
8
|
-
return Object.fromEntries(Object.entries(rules)
|
|
9
|
-
.filter(([, rule]) => predicate(rule))
|
|
10
|
-
.map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function findExtensionRoot(context) {
|
|
14
|
-
let dir = path.dirname(getFilename(context))
|
|
15
|
-
while (dir) {
|
|
16
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
17
|
-
if (fs.existsSync(path.join(dir, 'manifest.json'))) return dir
|
|
18
|
-
const parent = path.dirname(dir)
|
|
19
|
-
if (parent == dir) break // reached filesystem root or a stuck relative "."
|
|
20
|
-
dir = parent
|
|
21
|
-
}
|
|
22
|
-
return null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getChromeAPIcalls(sourceCode) {
|
|
26
|
-
const calls = new Set()
|
|
27
|
-
for (const match of sourceCode.getText().matchAll(re.chromeAPI))
|
|
28
|
-
calls.add(match[0])
|
|
29
|
-
return calls
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function getFilename(context) { return context.filename || context.getFilename() }
|
|
33
|
-
|
|
34
|
-
export function getMemberPath(node) {
|
|
35
|
-
const parts = []
|
|
36
|
-
let cur = node
|
|
37
|
-
while (cur?.type == 'MemberExpression') {
|
|
38
|
-
if (cur.computed || cur.property.type != 'Identifier') return null
|
|
39
|
-
parts.unshift(cur.property.name)
|
|
40
|
-
cur = cur.object
|
|
41
|
-
}
|
|
42
|
-
if (cur?.type != 'Identifier') return null
|
|
43
|
-
parts.unshift(cur.name)
|
|
44
|
-
return parts.join('.')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function initBundleDeprecation(plugin, [ruleId, bundleNames], pluginName) {
|
|
48
|
-
for (const bundleName of bundleNames) {
|
|
49
|
-
/* eslint-disable security/detect-object-injection */
|
|
50
|
-
if (plugin.configs[bundleName])
|
|
51
|
-
plugin.configs[bundleName].rules[`${pluginName}/${ruleId}`] = 'error'
|
|
52
|
-
/* eslint-enable security/detect-object-injection */
|
|
53
|
-
if (process.env.DEBUG_MODE)
|
|
54
|
-
console.warn(`[${pluginName}] '${ruleId}' will be removed from '${bundleName}' in a future major release`)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function isContentScriptFile(context, manifest) {
|
|
59
|
-
const scripts = (manifest?.content_scripts || []).flatMap(cs => cs.js || [])
|
|
60
|
-
if (!scripts.length) return false
|
|
61
|
-
const root = findExtensionRoot(context)
|
|
62
|
-
if (!root) return false
|
|
63
|
-
const filename = path.resolve(getFilename(context))
|
|
64
|
-
return scripts.some(s => filename == path.resolve(root, s))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function isInsideNestedFunction(node) {
|
|
68
|
-
let cur = node.parent
|
|
69
|
-
while (cur) {
|
|
70
|
-
if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(cur.type))
|
|
71
|
-
return true
|
|
72
|
-
cur = cur.parent
|
|
73
|
-
}
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function isServiceWorkerFile(context, manifest) {
|
|
78
|
-
const sw = manifest?.background?.service_worker
|
|
79
|
-
if (!sw) return false
|
|
80
|
-
const root = findExtensionRoot(context)
|
|
81
|
-
if (!root) return false
|
|
82
|
-
return path.resolve(getFilename(context)) == path.resolve(root, sw)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function readManifest(context) {
|
|
86
|
-
const root = findExtensionRoot(context)
|
|
87
|
-
if (!root) return null
|
|
88
|
-
if (manifestCache.has(root)) return manifestCache.get(root)
|
|
89
|
-
let parsed
|
|
90
|
-
try { // eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
91
|
-
parsed = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'))
|
|
92
|
-
} catch { parsed = null }
|
|
93
|
-
manifestCache.set(root, parsed)
|
|
94
|
-
return parsed
|
|
95
|
-
}
|