eslint-plugin-chrome-extensions 2.8.1 → 2.9.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 +76 -83
- package/docs/LICENSE.md +1 -1
- package/package.json +14 -10
- package/src/rules/css-spacing.js +9 -14
- package/src/rules/manifest-key-order.js +6 -5
- package/src/rules/manifest-spacing.js +5 -8
- package/src/rules/no-content-style-leak.js +4 -3
- package/src/rules/no-deprecated-apis.js +6 -6
- package/src/rules/no-global-keyframes.js +5 -8
- package/src/rules/no-host-page-selectors.js +12 -16
- package/src/rules/no-inline-event-handlers.js +7 -5
- package/src/rules/no-inline-scripts.js +10 -6
- package/src/rules/no-insecure-fetch.js +10 -6
- package/src/rules/no-insecure-messaging.js +8 -10
- package/src/rules/no-invalid-locales.js +7 -6
- package/src/rules/no-invalid-permissions.js +5 -5
- package/src/rules/no-missing-default-locale.js +5 -4
- package/src/rules/no-missing-i18n-keys.js +7 -5
- package/src/rules/no-missing-permissions.js +1 -1
- package/src/rules/no-missing-web-accessible-resources.js +10 -10
- package/src/rules/no-remote-code.js +30 -33
- package/src/rules/no-remote-objects.js +7 -6
- package/src/rules/no-remote-urls.js +11 -12
- package/src/rules/no-scrollbar-styling.js +6 -6
- package/src/rules/no-sensitive-permissions.js +6 -4
- package/src/rules/no-unrestricted-host-permissions.js +6 -7
- package/src/rules/no-unused-i18n-keys.js +5 -4
- package/src/rules/require-short-name-when-useful.js +3 -8
- package/src/rules/valid-filenames.js +9 -11
- package/src/rules/valid-license-file.js +7 -6
- package/src/rules/valid-manifest-icons.js +14 -20
- package/src/rules/valid-service-worker.js +8 -10
- package/src/rules/valid-url-fields.js +8 -13
- package/src/rules/valid-version-field.js +5 -6
- package/src/utils/extension.js +7 -3
- package/src/utils/init.js +5 -4
- package/src/utils/node.js +21 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import extension from '../utils/extension.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const defaultOptions = { maxNameLength: 12, always: false, checkRedundant: false },
|
|
5
5
|
re = { i18nPlaceholder: /^__MSG_.+__$/ }
|
|
6
6
|
|
|
7
7
|
function isI18n(value) { return typeof value == 'string' && re.i18nPlaceholder.test(value) }
|
|
@@ -32,9 +32,7 @@ export default {
|
|
|
32
32
|
},
|
|
33
33
|
additionalProperties: false
|
|
34
34
|
}],
|
|
35
|
-
defaultOptions: [
|
|
36
|
-
maxNameLength: defaults.maxNameLength, always: false, checkRedundant: false
|
|
37
|
-
}],
|
|
35
|
+
defaultOptions: [defaultOptions],
|
|
38
36
|
messages: {
|
|
39
37
|
missingShortName: '`name` exceeds {{max}} chars. Declare `short_name` to control truncation.',
|
|
40
38
|
shortNameTooLong: '`short_name` exceeds the recommended {{max}}-character limit (found {{length}}).',
|
|
@@ -44,10 +42,7 @@ export default {
|
|
|
44
42
|
|
|
45
43
|
create(context) {
|
|
46
44
|
if (!regex.filename.manifest.test(extension.file.getName(context))) return {}
|
|
47
|
-
const
|
|
48
|
-
maxNameLength = opts.maxNameLength ?? defaults.maxNameLength,
|
|
49
|
-
always = opts.always ?? false,
|
|
50
|
-
checkRedundant = opts.checkRedundant ?? false
|
|
45
|
+
const { maxNameLength, always, checkRedundant } = { ...defaultOptions, ...context.options[0] }
|
|
51
46
|
let manifest
|
|
52
47
|
try { manifest = JSON.parse(context.sourceCode.text) } catch { return {} }
|
|
53
48
|
if (!manifest || typeof manifest != 'object') return {}
|
|
@@ -4,6 +4,11 @@ import extension from '../utils/extension.js'
|
|
|
4
4
|
import fs from 'node:fs'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
|
|
7
|
+
const defaultOptions = {
|
|
8
|
+
noReservedPrefix: true, noSpecialChars: true, checkFileExists: true, enforceCaseSensitivity: true,
|
|
9
|
+
maxFullPathLength: 260
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
const re = { invalidChars: /[^\w\-.]/ }
|
|
8
13
|
|
|
9
14
|
const refKeys = [
|
|
@@ -85,10 +90,7 @@ export default {
|
|
|
85
90
|
},
|
|
86
91
|
additionalProperties: false
|
|
87
92
|
}],
|
|
88
|
-
defaultOptions: [
|
|
89
|
-
noReservedPrefix: true, noSpecialChars: true, checkFileExists: true, enforceCaseSensitivity: true,
|
|
90
|
-
maxFullPathLength: 260
|
|
91
|
-
}],
|
|
93
|
+
defaultOptions: [defaultOptions],
|
|
92
94
|
messages: {
|
|
93
95
|
reservedPrefix: '`{{name}}` starts with reserved prefix `_` (only `_locales` is allowed).',
|
|
94
96
|
specialChars: '`{{name}}` contains characters outside `a-z`, `0-9`, `-`, `_`, and `.`.',
|
|
@@ -99,13 +101,9 @@ export default {
|
|
|
99
101
|
},
|
|
100
102
|
|
|
101
103
|
create(context) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
checkFileExists = opts.checkFileExists ?? true,
|
|
106
|
-
enforceCaseSensitivity = opts.enforceCaseSensitivity ?? true,
|
|
107
|
-
maxFullPathLength = opts.maxFullPathLength ?? 260,
|
|
108
|
-
filename = context.filename
|
|
104
|
+
const { noReservedPrefix, noSpecialChars, checkFileExists, enforceCaseSensitivity, maxFullPathLength }
|
|
105
|
+
= { ...defaultOptions, ...context.options[0] }
|
|
106
|
+
const { filename } = context
|
|
109
107
|
if (!filename || filename == '<input>') return {}
|
|
110
108
|
const basename = path.basename(filename),
|
|
111
109
|
extRoot = extension.findRoot(context) || path.dirname(filename),
|
|
@@ -4,6 +4,8 @@ import path from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
import regex from '../data/regex.js'
|
|
6
6
|
|
|
7
|
+
const defaultOptions = { requireSPDX: false, allowedLicenses: [] }
|
|
8
|
+
|
|
7
9
|
function validateContent(context, node, { requireSPDX, allowedLicenses }) {
|
|
8
10
|
const text = context.sourceCode.text,
|
|
9
11
|
id = license.spdx.parse(text)
|
|
@@ -29,7 +31,7 @@ export default {
|
|
|
29
31
|
description: 'Validates SPDX identifiers in license files',
|
|
30
32
|
license: true
|
|
31
33
|
},
|
|
32
|
-
defaultOptions: [
|
|
34
|
+
defaultOptions: [defaultOptions],
|
|
33
35
|
schema: [{
|
|
34
36
|
type: 'object',
|
|
35
37
|
properties: {
|
|
@@ -52,11 +54,10 @@ export default {
|
|
|
52
54
|
},
|
|
53
55
|
|
|
54
56
|
create(context) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
basename = path.basename(filename)
|
|
57
|
+
const { requireSPDX, allowedLicenses: rawAllowedLicenses } = { ...defaultOptions, ...context.options[0] },
|
|
58
|
+
allowedLicenses = array.to(rawAllowedLicenses),
|
|
59
|
+
{ filename } = context,
|
|
60
|
+
basename = path.basename(filename)
|
|
60
61
|
if (!regex.filename.license.test(basename) || regex.filename.script.test(basename))
|
|
61
62
|
return {}
|
|
62
63
|
return { Program: node => validateContent(context, node, { requireSPDX, allowedLicenses })}
|
|
@@ -4,13 +4,14 @@ import path from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
import regex from '../data/regex.js'
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const defaultOptions = { requireRecommendedSizes: true, checkIconExists: true, noSVG: true },
|
|
8
|
+
re = { svg: /\.svg$/i },
|
|
8
9
|
sizes = { recommended: ['16', '32', '48', '128'], required: '128' }
|
|
9
10
|
|
|
10
|
-
function validateIcons(context, icons, root,
|
|
11
|
-
const {
|
|
11
|
+
function validateIcons(context, icons, root, options, loc) {
|
|
12
|
+
const { requireRecommendedSizes, checkIconExists, noSVG } = options,
|
|
12
13
|
declaredSizes = new Set(Object.keys(icons))
|
|
13
|
-
if (
|
|
14
|
+
if (requireRecommendedSizes) {
|
|
14
15
|
for (const size of sizes.recommended)
|
|
15
16
|
if (!declaredSizes.has(size))
|
|
16
17
|
context.report({ loc, messageId: 'missingSize', data: { size }})
|
|
@@ -18,9 +19,9 @@ function validateIcons(context, icons, root, opts, loc) {
|
|
|
18
19
|
context.report({ loc, messageId: 'missingRequiredSize' })
|
|
19
20
|
for (const file of Object.values(icons)) {
|
|
20
21
|
if (typeof file != 'string') continue
|
|
21
|
-
if (
|
|
22
|
+
if (noSVG && re.svg.test(file))
|
|
22
23
|
context.report({ loc, messageId: 'svgNotSupported' })
|
|
23
|
-
else if (
|
|
24
|
+
else if (checkIconExists && root // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
24
25
|
&& !fs.existsSync(path.resolve(root, file))
|
|
25
26
|
) context.report({ loc, messageId: 'missingFile', data: { file }})
|
|
26
27
|
}
|
|
@@ -35,11 +36,7 @@ export default {
|
|
|
35
36
|
recommended: true,
|
|
36
37
|
manifest: true
|
|
37
38
|
},
|
|
38
|
-
defaultOptions: [
|
|
39
|
-
requireRecommendedSizes: true,
|
|
40
|
-
checkIconExists: true,
|
|
41
|
-
noSVG: true
|
|
42
|
-
}],
|
|
39
|
+
defaultOptions: [defaultOptions],
|
|
43
40
|
schema: [{
|
|
44
41
|
type: 'object',
|
|
45
42
|
properties: {
|
|
@@ -69,12 +66,9 @@ export default {
|
|
|
69
66
|
create(context) {
|
|
70
67
|
const filename = extension.file.getName(context)
|
|
71
68
|
if (!regex.filename.manifest.test(filename) && !regex.filename.script.test(filename)) return {}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
checkSVG = opts.noSVG !== false,
|
|
76
|
-
root = checkFiles ? extension.findRoot(context) : null,
|
|
77
|
-
resolved = { requireRecommended, checkFiles, checkSVG }
|
|
69
|
+
const { requireRecommendedSizes, checkIconExists, noSVG } = { ...defaultOptions, ...context.options[0] },
|
|
70
|
+
root = checkIconExists ? extension.findRoot(context) : null,
|
|
71
|
+
resolved = { requireRecommendedSizes, checkIconExists, noSVG }
|
|
78
72
|
if (!regex.filename.manifest.test(filename)) {
|
|
79
73
|
const manifest = extension.manifest.read(context) ; if (!manifest) return {}
|
|
80
74
|
const icons = manifest.icons
|
|
@@ -96,13 +90,13 @@ export default {
|
|
|
96
90
|
file = fileNode?.value
|
|
97
91
|
if (typeof size == 'string') declaredSizes.add(size)
|
|
98
92
|
if (typeof file != 'string') continue
|
|
99
|
-
if (
|
|
93
|
+
if (noSVG && re.svg.test(file))
|
|
100
94
|
context.report({ node: fileNode, messageId: 'svgNotSupported' })
|
|
101
|
-
else if (
|
|
95
|
+
else if (checkIconExists && root // oxlint-disable-next-line security/detect-non-literal-fs-filename
|
|
102
96
|
&& !fs.existsSync(path.resolve(root, file))
|
|
103
97
|
) context.report({ node: fileNode, messageId: 'missingFile', data: { file }})
|
|
104
98
|
}
|
|
105
|
-
if (
|
|
99
|
+
if (requireRecommendedSizes) {
|
|
106
100
|
for (const size of sizes.recommended)
|
|
107
101
|
if (!declaredSizes.has(size))
|
|
108
102
|
context.report({ node: iconsObj, messageId: 'missingSize', data: { size }})
|
|
@@ -3,6 +3,8 @@ import nodeUtils from '../utils/node.js'
|
|
|
3
3
|
|
|
4
4
|
import manifestData from '../data/manifest.js'
|
|
5
5
|
|
|
6
|
+
const defaultOptions = { checkListenerTopLevel: true, checkKeepaliveTimer: true, checkDOMglobals: true }
|
|
7
|
+
|
|
6
8
|
export default {
|
|
7
9
|
meta: {
|
|
8
10
|
type: 'problem',
|
|
@@ -23,11 +25,7 @@ export default {
|
|
|
23
25
|
},
|
|
24
26
|
additionalProperties: false
|
|
25
27
|
}],
|
|
26
|
-
defaultOptions: [
|
|
27
|
-
checkListenerTopLevel: true,
|
|
28
|
-
checkKeepaliveTimer: true,
|
|
29
|
-
checkDOMglobals: true
|
|
30
|
-
}],
|
|
28
|
+
defaultOptions: [defaultOptions],
|
|
31
29
|
messages: {
|
|
32
30
|
listenerNotTopLevel: '`chrome.*.addListener()` must be registered at module top level'
|
|
33
31
|
+ ' so it attaches on service worker wake-up.',
|
|
@@ -37,8 +35,9 @@ export default {
|
|
|
37
35
|
},
|
|
38
36
|
|
|
39
37
|
create(context) {
|
|
40
|
-
const { checkListenerTopLevel, checkKeepaliveTimer, checkDOMglobals }
|
|
41
|
-
|
|
38
|
+
const { checkListenerTopLevel, checkKeepaliveTimer, checkDOMglobals }
|
|
39
|
+
= { ...defaultOptions, ...context.options[0] }
|
|
40
|
+
const manifest = extension.manifest.read(context)
|
|
42
41
|
if (!manifest || !extension.file.isServiceWorker(context, manifest)) return {}
|
|
43
42
|
return {
|
|
44
43
|
CallExpression(node) {
|
|
@@ -50,9 +49,8 @@ export default {
|
|
|
50
49
|
if (checkKeepaliveTimer
|
|
51
50
|
&& node.callee.type == 'Identifier'
|
|
52
51
|
&& ['setTimeout', 'setInterval'].includes(node.callee.name)
|
|
53
|
-
&& node.arguments[1]
|
|
54
|
-
&&
|
|
55
|
-
&& node.arguments[1].value < 30000
|
|
52
|
+
&& nodeUtils.getNumberLiteral(node.arguments[1]) !== null
|
|
53
|
+
&& nodeUtils.getNumberLiteral(node.arguments[1]) < 30000
|
|
56
54
|
&& !nodeUtils.isInsideNestedFunction(node)
|
|
57
55
|
) context.report({ node, messageId: 'keepaliveTimer', data: { fn: node.callee.name }})
|
|
58
56
|
},
|
|
@@ -3,6 +3,9 @@ import extension from '../utils/extension.js'
|
|
|
3
3
|
import manifestData from '../data/manifest.js'
|
|
4
4
|
import regex from '../data/regex.js'
|
|
5
5
|
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
checkURLfields: true, checkMatchPatterns: true, allowedSchemes: ['https', 'file'], allowAllURLs: false }
|
|
8
|
+
|
|
6
9
|
const re = {
|
|
7
10
|
absoluteURL: /^[a-z][a-z0-9+.-]*:\/\/[^\s/]+/i,
|
|
8
11
|
match: /^(\*|https?|file|ftp):\/\/([^/]*)(\/.*)$/,
|
|
@@ -86,10 +89,7 @@ export default {
|
|
|
86
89
|
},
|
|
87
90
|
additionalProperties: false
|
|
88
91
|
}],
|
|
89
|
-
defaultOptions: [
|
|
90
|
-
checkURLfields: true, checkMatchPatterns: true,
|
|
91
|
-
allowedSchemes: ['https', 'file'], allowAllURLs: false
|
|
92
|
-
}],
|
|
92
|
+
defaultOptions: [defaultOptions],
|
|
93
93
|
messages: {
|
|
94
94
|
invalidURL: '`{{field}}` has an invalid URL: "{{value}}".',
|
|
95
95
|
invalidMatchPattern: '`{{field}}` has an invalid match pattern: "{{value}}".',
|
|
@@ -105,23 +105,18 @@ export default {
|
|
|
105
105
|
create(context) {
|
|
106
106
|
const filename = extension.file.getName(context)
|
|
107
107
|
if (!regex.filename.manifest.test(filename) && !regex.filename.script.test(filename)) return {}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
checkMatchPatterns = opts.checkMatchPatterns ?? true,
|
|
111
|
-
allowedSchemes = opts.allowedSchemes ?? ['https', 'file'],
|
|
112
|
-
allowAllURLs = opts.allowAllURLs ?? false
|
|
113
|
-
if (!checkURLfields && !checkMatchPatterns) return {}
|
|
114
|
-
const resolved = { checkURLfields, checkMatchPatterns, allowedSchemes, allowAllURLs }
|
|
108
|
+
const options = { ...defaultOptions, ...context.options[0] }
|
|
109
|
+
if (!options.checkURLfields && !options.checkMatchPatterns) return {}
|
|
115
110
|
if (!regex.filename.manifest.test(filename)) {
|
|
116
111
|
const manifest = extension.manifest.read(context) ; if (!manifest) return {}
|
|
117
|
-
validateManifest(context, manifest,
|
|
112
|
+
validateManifest(context, manifest, options)
|
|
118
113
|
return {}
|
|
119
114
|
}
|
|
120
115
|
return {
|
|
121
116
|
'Document:exit'() {
|
|
122
117
|
let manifest
|
|
123
118
|
try { manifest = JSON.parse(context.sourceCode.text) } catch { return }
|
|
124
|
-
validateManifest(context, manifest,
|
|
119
|
+
validateManifest(context, manifest, options)
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
122
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import extension from '../utils/extension.js'
|
|
2
2
|
import regex from '../data/regex.js'
|
|
3
3
|
|
|
4
|
+
const defaultOptions = { versionType: 'chrome', allowFutureDate: false }
|
|
5
|
+
|
|
4
6
|
const re = { // oxlint-disable security/detect-unsafe-regex
|
|
5
7
|
chromeParts: /^\d+(?:\.\d+){0,3}$/,
|
|
6
8
|
date: /^\d{4}\.(?:0?[1-9]|1[0-2])\.(?:0?[1-9]|[12]\d|3[01])(?:\.\d+)?$/,
|
|
@@ -36,7 +38,7 @@ export default {
|
|
|
36
38
|
},
|
|
37
39
|
additionalProperties: false
|
|
38
40
|
}],
|
|
39
|
-
defaultOptions: [
|
|
41
|
+
defaultOptions: [defaultOptions],
|
|
40
42
|
messages: {
|
|
41
43
|
invalidVersion: 'Invalid manifest `version`: must be a valid {{versionType}} version '
|
|
42
44
|
+ '(expected format: {{example}}).',
|
|
@@ -45,11 +47,8 @@ export default {
|
|
|
45
47
|
},
|
|
46
48
|
|
|
47
49
|
create(context) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
allowFutureDate = options.allowFutureDate ?? false,
|
|
51
|
-
timezone = options.timezone,
|
|
52
|
-
example = versionType == 'semver' ? '1.2.3'
|
|
50
|
+
const { versionType, allowFutureDate, timezone } = { ...defaultOptions, ...context.options[0] }
|
|
51
|
+
const example = versionType == 'semver' ? '1.2.3'
|
|
53
52
|
: versionType == 'date' ? 'YYYY.M.D or YYYY.M.D.N'
|
|
54
53
|
: '1.2.3 or 1.2.3.4'
|
|
55
54
|
const now = timezone
|
package/src/utils/extension.js
CHANGED
|
@@ -8,6 +8,9 @@ import regex from '../data/regex.js'
|
|
|
8
8
|
|
|
9
9
|
const manifestCache = new Map()
|
|
10
10
|
|
|
11
|
+
function stripScriptExt(filePath) { return filePath.replace(regex.filename.script, '') }
|
|
12
|
+
function sameScriptPath(a, b) { return a === b || stripScriptExt(a) === stripScriptExt(b) }
|
|
13
|
+
|
|
11
14
|
const utils = {
|
|
12
15
|
file: {
|
|
13
16
|
getName(context) { return context.filename || context.getFilename() },
|
|
@@ -17,13 +20,14 @@ const utils = {
|
|
|
17
20
|
if (!scripts.length) return false
|
|
18
21
|
const root = utils.findRoot(context) ; if (!root) return false
|
|
19
22
|
const filename = path.resolve(utils.file.getName(context))
|
|
20
|
-
return scripts.some(script => filename
|
|
23
|
+
return scripts.some(script => sameScriptPath(filename, path.resolve(root, script)))
|
|
21
24
|
},
|
|
22
25
|
|
|
23
26
|
isServiceWorker(context, manifest) {
|
|
24
|
-
const worker = manifest?.background?.service_worker
|
|
27
|
+
const worker = manifest?.background?.service_worker
|
|
28
|
+
if (!worker) return false
|
|
25
29
|
const root = utils.findRoot(context) ; if (!root) return false
|
|
26
|
-
return path.resolve(utils.file.getName(context))
|
|
30
|
+
return sameScriptPath(path.resolve(utils.file.getName(context)), path.resolve(root, worker))
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
|
package/src/utils/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import json from '@eslint/json'
|
|
|
4
4
|
import pkg from './pkg.js'
|
|
5
5
|
import rulesUtils from './rules.js'
|
|
6
6
|
import text from './text.js'
|
|
7
|
+
import tsParser from '@typescript-eslint/parser'
|
|
7
8
|
|
|
8
9
|
import cssSpacing from '../rules/css-spacing.js'
|
|
9
10
|
import manifestKeyOrder from '../rules/manifest-key-order.js'
|
|
@@ -51,10 +52,10 @@ export default {
|
|
|
51
52
|
rules = plugin.rules,
|
|
52
53
|
createConfig = predicate => ({ plugins, rules: rulesUtils.bundle(rules, predicate, namespace) })
|
|
53
54
|
plugin.configs = {
|
|
54
|
-
recommended: createConfig(rule => rule.meta.docs.recommended),
|
|
55
|
-
strict: createConfig(() => true),
|
|
56
|
-
problem: createConfig(rule => rule.meta.type == 'problem'),
|
|
57
|
-
security: createConfig(rule => rule.meta.docs.security),
|
|
55
|
+
recommended: { ...createConfig(rule => rule.meta.docs.recommended), languageOptions: { parser: tsParser }},
|
|
56
|
+
strict: { ...createConfig(() => true), languageOptions: { parser: tsParser }},
|
|
57
|
+
problem: { ...createConfig(rule => rule.meta.type == 'problem'), languageOptions: { parser: tsParser }},
|
|
58
|
+
security: { ...createConfig(rule => rule.meta.docs.security), languageOptions: { parser: tsParser }},
|
|
58
59
|
manifest: {
|
|
59
60
|
...createConfig(rule => rule.meta.docs.manifest), language: 'json/json', plugins: { ...plugins, json }},
|
|
60
61
|
license: {
|
package/src/utils/node.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
getLiteral(node) {
|
|
3
|
+
const inner = this.unwrapTS(node)
|
|
4
|
+
return inner?.type == 'Literal' ? inner : null
|
|
5
|
+
},
|
|
6
|
+
|
|
2
7
|
getMemberPath(node) {
|
|
3
8
|
const parts = [] ; let currentNode = node
|
|
4
9
|
while (currentNode?.type == 'MemberExpression') {
|
|
@@ -11,6 +16,16 @@ export default {
|
|
|
11
16
|
return parts.join('.')
|
|
12
17
|
},
|
|
13
18
|
|
|
19
|
+
getNumberLiteral(node) {
|
|
20
|
+
const literal = this.getLiteral(node)
|
|
21
|
+
return typeof literal?.value == 'number' ? literal.value : null
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
getStringLiteral(node) {
|
|
25
|
+
const literal = this.getLiteral(node)
|
|
26
|
+
return typeof literal?.value == 'string' ? literal.value : null
|
|
27
|
+
},
|
|
28
|
+
|
|
14
29
|
isInsideNestedFunction(node) {
|
|
15
30
|
let ancestor = node.parent
|
|
16
31
|
while (ancestor) {
|
|
@@ -21,5 +36,10 @@ export default {
|
|
|
21
36
|
return false
|
|
22
37
|
},
|
|
23
38
|
|
|
24
|
-
plainType(node, prefix) { return node?.type?.replace(prefix, '') ?? '' }
|
|
39
|
+
plainType(node, prefix) { return node?.type?.replace(prefix, '') ?? '' },
|
|
40
|
+
|
|
41
|
+
unwrapTS(node) {
|
|
42
|
+
while (node?.type?.startsWith('TS') && node.expression) node = node.expression
|
|
43
|
+
return node
|
|
44
|
+
}
|
|
25
45
|
}
|