@jameswyse/oxc-config 0.4.0 → 0.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.
- package/REFERENCE.md +3 -3
- package/dist/rules/import-boundaries.js +1 -12
- package/dist/rules/no-ambient-clock.js +88 -27
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +56 -13
- package/package.json +1 -1
package/REFERENCE.md
CHANGED
|
@@ -72,10 +72,10 @@ Native rules: `prefer-const`, `no-param-reassign` and `oxc/no-accumulating-sprea
|
|
|
72
72
|
|
|
73
73
|
These rules run only once the project names its owners through the [project boundary options](docs/configuration.md#enforce-project-boundaries).
|
|
74
74
|
|
|
75
|
-
- `wyse/no-env-access` (`env`): reading `process.env` or `import.meta.env` outside the owners, config files and scripts. That includes destructuring `env`, `globalThis.process.env`, `global.process.env`, `process["env"]` and `Reflect.get(process, "env")`, including with `"env" as const`, importing `env` from `node:process` or `process`, reading `env` from those modules' default, namespace
|
|
76
|
-
- `wyse/import-boundaries` (`boundaries`): a value import, re-export, literal dynamic import, or `require` call, including a loader made with `createRequire` from `node:module`, that matches a `deny` glob. Type-only imports and `import("…")` types pass unless the boundary sets `types: true`. It doesn't check computed imports.
|
|
75
|
+
- `wyse/no-env-access` (`env`): reading `process.env` or `import.meta.env` outside the owners, config files and scripts. That includes destructuring `env`, `globalThis.process.env`, `global.process.env`, `process["env"]` and `Reflect.get(process, "env")`, including with `"env" as const`, importing `env` from `node:process` or `process`, reading `env` from those modules' default, namespace, literal dynamic import or `require`, including through a loader made with `createRequire` from `node:module` or `module` or a `const` copy of a loader, and re-exporting `env`, the default or the whole module from them with `export { ... } from` or `export * from`. Other `node:process` imports and re-exports, such as `argv`, type-only re-exports, a `require` function the file defines itself, a loader variable that's reassigned, and a `process` property on `window` or `self` pass. It doesn't check that the owner validates what it reads.
|
|
76
|
+
- `wyse/import-boundaries` (`boundaries`): a value import, re-export, literal dynamic import, or `require` call, including a loader made with `createRequire` from `node:module` or `module`, whether stored, copied to another `const` or called directly as `createRequire(import.meta.url)("…")`, that matches a `deny` glob. A loader variable that's reassigned doesn't count. Type-only imports and `import("…")` types pass unless the boundary sets `types: true`. It doesn't check computed imports.
|
|
77
77
|
- `wyse/server-action-guard` (`serverActionGuards`): a `"use server"` action that doesn't start by awaiting a guard, and indirect or default exports from server-action modules. Non-exported helpers and test files pass.
|
|
78
|
-
- `wyse/no-ambient-clock` (`clock`): `Date.now()`, `Date()` and argument-less `new Date()`, including through `globalThis` or `window`. It also reports argument-less `new TZDate()` and `new TZDateMini()` and one-argument `TZDate.tz(zone)` from `@date-fns/tz`, `@date-fns/tz/date` or `@date-fns/tz/date/mini`, and
|
|
78
|
+
- `wyse/no-ambient-clock` (`clock`): `Date.now()`, `Date()` and argument-less `new Date()`, including through `globalThis` or `window`. It also reports argument-less `new TZDate()` and `new TZDateMini()` and one-argument `TZDate.tz(zone)` from `@date-fns/tz`, `@date-fns/tz/date` or `@date-fns/tz/date/mini`, and the date-fns helpers that read the current time: `constructNow`, the `startOf` and `endOf` helpers for today, tomorrow and yesterday, `isToday`, `isTomorrow`, `isYesterday`, `isPast`, `isFuture`, the `isThis*` helpers, `formatDistanceToNow`, `formatDistanceToNowStrict`, `isMatch`, and `isMatch` and `isMatchWithOptions` from `date-fns/fp`. A helper reports where it's called, where it's passed or stored uncalled, such as `dates.filter(isPast)` or `{ check: isToday }`, and where it's exported by local name, such as `export { isPast }`. A `date-fns/fp` helper reports once at the end of its call chain, so the partial application in `values.filter(isMatch("yyyy"))` reports too. A `const` alias reports where it's used. Helpers count whether they're imported by name, through a namespace, from a `date-fns/<name>` subpath, by literal dynamic import or by `require`. Re-exporting one of these helpers with `export { ... } from`, or a module that contains one, such as `date-fns` or `date-fns/fp`, with `export * from`, also reports, because calls through the re-export no longer resolve to date-fns. Helpers that compare with a given date, such as `isSameDay`, type positions such as `typeof isPast`, type-only imports and exports, and re-exports of `TZDate`, `TZDateMini`, other date-fns helpers or types pass. Parameter defaults, such as `check = isPast`, uncalled `Date.now`, provider functions that only return the read, including through `.call`, `.apply` or `.bind`, write-only uses of a helper's name on a module object, such as `d.isPast = check`, `[d.isPast] = values`, `for (d.isPast of values)`, `delete d.isPast` and `delete d?.isPast`, and test files pass. Parentheses, `as`, `satisfies` and `!` don't change the result, so `(Date.now as typeof Date.now)()` reports and `{ now: () => startOfToday() as Date }` passes.
|
|
79
79
|
- `wyse/no-ambient-random` (`random`): `Math.random()`, `crypto.randomUUID()`, and `randomUUID` or `randomInt` from `node:crypto`. The same exceptions and wrapper handling apply. `randomBytes` and `getRandomValues` pass because they generate secrets.
|
|
80
80
|
|
|
81
81
|
## Tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, resolve as resolvePath } from "node:path";
|
|
2
2
|
import { defineRule } from "@oxlint/plugins";
|
|
3
|
-
import {
|
|
3
|
+
import { isRequire, matchesAny, projectPath } from "../shared/index.js";
|
|
4
4
|
function isStringList(value) {
|
|
5
5
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6
6
|
}
|
|
@@ -29,17 +29,6 @@ function parseSettings(options) {
|
|
|
29
29
|
}
|
|
30
30
|
return { root: option.root, boundaries: option.boundaries.map(parseBoundary) };
|
|
31
31
|
}
|
|
32
|
-
const nodeModuleSources = new Set(["module", "node:module"]);
|
|
33
|
-
function isRequire(sourceCode, callee) {
|
|
34
|
-
if (isGlobal(sourceCode, callee, "require")) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
const definition = variableFor(sourceCode, callee)?.defs[0];
|
|
38
|
-
const init = definition?.node.type === "VariableDeclarator" ? definition.node.init : null;
|
|
39
|
-
const factory = init?.type === "CallExpression" ? init.callee : null;
|
|
40
|
-
const binding = factory === null ? null : importedBinding(sourceCode, factory);
|
|
41
|
-
return (binding !== null && nodeModuleSources.has(binding.source) && binding.name === "createRequire");
|
|
42
|
-
}
|
|
43
32
|
export const importBoundaries = defineRule({
|
|
44
33
|
meta: {
|
|
45
34
|
type: "problem",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineRule } from "@oxlint/plugins";
|
|
2
|
-
import { importedBinding, isGlobalValue, isInjectionSeam, moduleExportName, outermostExpression, staticPropertyName, unwrapExpression, } from "../shared/index.js";
|
|
2
|
+
import { importedBinding, isGlobalValue, isInjectedValue, isInjectionSeam, moduleExportName, outermostExpression, staticPropertyName, unwrapExpression, variableFor, } from "../shared/index.js";
|
|
3
3
|
const timeZoneModules = new Set(["@date-fns/tz", "@date-fns/tz/date", "@date-fns/tz/date/mini"]);
|
|
4
4
|
const timeZoneDates = new Set(["TZDate", "TZDateMini"]);
|
|
5
5
|
const clockHelpers = new Set([
|
|
@@ -26,34 +26,88 @@ const clockHelpers = new Set([
|
|
|
26
26
|
"date-fns/formatDistanceToNow",
|
|
27
27
|
"date-fns/formatDistanceToNowStrict",
|
|
28
28
|
"date-fns/isMatch",
|
|
29
|
+
"date-fns/fp/isMatch",
|
|
30
|
+
"date-fns/fp/isMatchWithOptions",
|
|
29
31
|
]);
|
|
30
|
-
const curriedClockHelpers = new Map([
|
|
31
|
-
["date-fns/fp/isMatch", 2],
|
|
32
|
-
["date-fns/fp/isMatchWithOptions", 3],
|
|
33
|
-
]);
|
|
34
|
-
const clockExports = new Set([...clockHelpers, ...curriedClockHelpers.keys()]);
|
|
35
32
|
function helperPath(source, name) {
|
|
36
33
|
return name === "default" || source.endsWith(`/${name}`) ? source : `${source}/${name}`;
|
|
37
34
|
}
|
|
38
|
-
function
|
|
35
|
+
function isClockHelper(sourceCode, node) {
|
|
39
36
|
const binding = importedBinding(sourceCode, node);
|
|
40
|
-
return binding
|
|
37
|
+
return binding !== null && clockHelpers.has(helperPath(binding.source, binding.name));
|
|
41
38
|
}
|
|
42
39
|
function exposesClockHelper(source) {
|
|
43
|
-
return [...
|
|
40
|
+
return [...clockHelpers].some((helper) => helperPath(source, helper.slice(helper.lastIndexOf("/") + 1)) === helper);
|
|
41
|
+
}
|
|
42
|
+
function isTypeImport(definition) {
|
|
43
|
+
const declaration = definition.parent;
|
|
44
|
+
const specifier = definition.node;
|
|
45
|
+
return (declaration?.type === "ImportDeclaration" &&
|
|
46
|
+
(declaration.importKind === "type" ||
|
|
47
|
+
(specifier.type === "ImportSpecifier" && specifier.importKind === "type")));
|
|
48
|
+
}
|
|
49
|
+
function isTypePosition(node) {
|
|
50
|
+
const parent = node.parent;
|
|
51
|
+
const declaration = parent?.parent;
|
|
52
|
+
return (parent?.type === "TSTypeQuery" ||
|
|
53
|
+
parent?.type === "TSQualifiedName" ||
|
|
54
|
+
(parent?.type === "ExportSpecifier" &&
|
|
55
|
+
(parent.exportKind === "type" ||
|
|
56
|
+
(declaration?.type === "ExportNamedDeclaration" && declaration.exportKind === "type"))));
|
|
44
57
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
function isValueReference(sourceCode, node) {
|
|
59
|
+
const variable = variableFor(sourceCode, node);
|
|
60
|
+
const definition = variable?.defs[0];
|
|
61
|
+
return (variable !== null &&
|
|
62
|
+
definition !== undefined &&
|
|
63
|
+
!isTypeImport(definition) &&
|
|
64
|
+
!isTypePosition(node) &&
|
|
65
|
+
variable.references.some((reference) => reference.identifier === node && reference.isRead()));
|
|
66
|
+
}
|
|
67
|
+
const functionMethods = new Set(["call", "apply", "bind"]);
|
|
68
|
+
function helperUse(node) {
|
|
69
|
+
const value = outermostExpression(node);
|
|
70
|
+
const parent = value.parent;
|
|
71
|
+
if (parent?.type === "CallExpression" && parent.callee === value) {
|
|
72
|
+
return helperUse(parent);
|
|
48
73
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
if (parent?.type === "MemberExpression" &&
|
|
75
|
+
parent.object === value &&
|
|
76
|
+
functionMethods.has(staticPropertyName(parent) ?? "")) {
|
|
77
|
+
const use = helperUse(parent);
|
|
78
|
+
return use === parent ? node : use;
|
|
52
79
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
function isWriteTarget(node) {
|
|
83
|
+
const expression = outermostExpression(node);
|
|
84
|
+
const target = expression.parent?.type === "ChainExpression"
|
|
85
|
+
? outermostExpression(expression.parent)
|
|
86
|
+
: expression;
|
|
87
|
+
const parent = target.parent;
|
|
88
|
+
return ((parent?.type === "AssignmentExpression" &&
|
|
89
|
+
parent.left === target &&
|
|
90
|
+
parent.operator === "=") ||
|
|
91
|
+
((parent?.type === "ForOfStatement" || parent?.type === "ForInStatement") &&
|
|
92
|
+
parent.left === target) ||
|
|
93
|
+
(parent?.type === "UnaryExpression" && parent.operator === "delete") ||
|
|
94
|
+
(parent?.type === "AssignmentPattern" && parent.left === target) ||
|
|
95
|
+
(parent?.type === "Property" &&
|
|
96
|
+
parent.value === target &&
|
|
97
|
+
parent.parent.type === "ObjectPattern") ||
|
|
98
|
+
parent?.type === "ArrayPattern" ||
|
|
99
|
+
parent?.type === "RestElement");
|
|
100
|
+
}
|
|
101
|
+
function isFollowedAlias(node) {
|
|
102
|
+
const value = outermostExpression(node);
|
|
103
|
+
const declarator = value.parent;
|
|
104
|
+
const declaration = declarator?.parent;
|
|
105
|
+
return (declarator?.type === "VariableDeclarator" &&
|
|
106
|
+
declarator.init === value &&
|
|
107
|
+
declarator.id.type === "Identifier" &&
|
|
108
|
+
declaration?.type === "VariableDeclaration" &&
|
|
109
|
+
declaration.kind === "const" &&
|
|
110
|
+
declaration.parent.type !== "ExportNamedDeclaration");
|
|
57
111
|
}
|
|
58
112
|
function isTimeZoneDate(sourceCode, node) {
|
|
59
113
|
const binding = importedBinding(sourceCode, node);
|
|
@@ -84,11 +138,25 @@ export const noAmbientClock = defineRule({
|
|
|
84
138
|
context.report({ node, messageId: "clock" });
|
|
85
139
|
}
|
|
86
140
|
}
|
|
141
|
+
function reportHelper(reference) {
|
|
142
|
+
const use = helperUse(reference);
|
|
143
|
+
if ((use !== reference || !isFollowedAlias(reference)) && !isInjectedValue(use)) {
|
|
144
|
+
context.report({ node: use, messageId: "clock" });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
87
147
|
return {
|
|
148
|
+
Identifier(node) {
|
|
149
|
+
if (isClockHelper(sourceCode, node) && isValueReference(sourceCode, node)) {
|
|
150
|
+
reportHelper(node);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
88
153
|
MemberExpression(node) {
|
|
89
154
|
if (staticPropertyName(node) === "now" && isGlobalValue(sourceCode, node.object, "Date")) {
|
|
90
155
|
report(node);
|
|
91
156
|
}
|
|
157
|
+
if (isClockHelper(sourceCode, node) && !isWriteTarget(node)) {
|
|
158
|
+
reportHelper(node);
|
|
159
|
+
}
|
|
92
160
|
},
|
|
93
161
|
NewExpression(node) {
|
|
94
162
|
if (node.arguments.length === 0 &&
|
|
@@ -98,14 +166,7 @@ export const noAmbientClock = defineRule({
|
|
|
98
166
|
}
|
|
99
167
|
},
|
|
100
168
|
CallExpression(node) {
|
|
101
|
-
const helper = exportPath(sourceCode, node.callee);
|
|
102
|
-
const arity = helper === null ? undefined : curriedClockHelpers.get(helper);
|
|
103
|
-
const applied = arity === undefined ? null : fullApplication(node, arity);
|
|
104
|
-
if (applied !== null) {
|
|
105
|
-
report(applied);
|
|
106
|
-
}
|
|
107
169
|
if (isGlobalValue(sourceCode, node.callee, "Date") ||
|
|
108
|
-
(helper !== null && clockHelpers.has(helper)) ||
|
|
109
170
|
isCurrentTimeZoneDate(sourceCode, node)) {
|
|
110
171
|
report(node);
|
|
111
172
|
}
|
|
@@ -116,7 +177,7 @@ export const noAmbientClock = defineRule({
|
|
|
116
177
|
}
|
|
117
178
|
for (const specifier of node.specifiers) {
|
|
118
179
|
if (specifier.exportKind !== "type" &&
|
|
119
|
-
|
|
180
|
+
clockHelpers.has(helperPath(node.source.value, moduleExportName(specifier.local)))) {
|
|
120
181
|
context.report({ node: specifier, messageId: "clock" });
|
|
121
182
|
}
|
|
122
183
|
}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -27,12 +27,14 @@ export declare function staticString(node: ESTree.Node | null | undefined): stri
|
|
|
27
27
|
export declare function staticPropertyName(node: ESTree.Node): string | null;
|
|
28
28
|
export declare function moduleExportName(node: ESTree.ModuleExportName): string;
|
|
29
29
|
export declare function importedBinding(sourceCode: SourceCode, node: ESTree.Node): ImportedBinding | null;
|
|
30
|
+
export declare function isRequire(sourceCode: SourceCode, node: ESTree.Node): boolean;
|
|
30
31
|
export declare function apiReference(sourceCode: SourceCode, node: ESTree.Node): ApiReference | null;
|
|
31
32
|
export declare function testContext(sourceCode: SourceCode, node: ESTree.Node): TestContext | null;
|
|
32
33
|
export declare function projectPath(cwd: string, file: string): string;
|
|
33
34
|
export declare function matchesAny(path: string, patterns?: readonly string[]): boolean;
|
|
34
35
|
export declare function isGlobalValue(sourceCode: SourceCode, node: ESTree.Node | null | undefined, name: string): boolean;
|
|
35
36
|
export declare function isNodeGlobalValue(sourceCode: SourceCode, node: ESTree.Node | null | undefined, name: string): boolean;
|
|
37
|
+
export declare function isInjectedValue(source: ESTree.Node): boolean;
|
|
36
38
|
export declare function isInjectionSeam(source: ESTree.Node): boolean;
|
|
37
39
|
export declare function isShortCircuited(call: ESTree.CallExpression): boolean;
|
|
38
40
|
export declare function isEffectModule(sourceCode: SourceCode, node: ESTree.Node, module: string): boolean;
|
package/dist/shared/index.js
CHANGED
|
@@ -172,6 +172,41 @@ function cached(cache, variable, resolve) {
|
|
|
172
172
|
}
|
|
173
173
|
return cache.get(variable) ?? null;
|
|
174
174
|
}
|
|
175
|
+
const nodeModuleSources = new Set(["module", "node:module"]);
|
|
176
|
+
const loaderCache = new WeakMap();
|
|
177
|
+
function unchangedInit(variable) {
|
|
178
|
+
const [definition, ...otherDefinitions] = variable.defs;
|
|
179
|
+
const declarator = definition?.node;
|
|
180
|
+
return otherDefinitions.length === 0 &&
|
|
181
|
+
definition?.type === "Variable" &&
|
|
182
|
+
declarator?.type === "VariableDeclarator" &&
|
|
183
|
+
declarator.id === definition.name &&
|
|
184
|
+
variable.references.every((reference) => reference.init || !reference.isWrite())
|
|
185
|
+
? declarator.init
|
|
186
|
+
: null;
|
|
187
|
+
}
|
|
188
|
+
function isRequireFactory(node, resolve) {
|
|
189
|
+
const binding = node.type === "CallExpression" ? resolve(node.callee) : null;
|
|
190
|
+
return (binding !== null && nodeModuleSources.has(binding.source) && binding.name === "createRequire");
|
|
191
|
+
}
|
|
192
|
+
function isLoader(sourceCode, node, resolve) {
|
|
193
|
+
const callee = unwrapExpression(node);
|
|
194
|
+
if (isGlobal(sourceCode, callee, "require") || isRequireFactory(callee, resolve)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const variable = variableFor(sourceCode, callee);
|
|
198
|
+
return (variable !== null &&
|
|
199
|
+
cached(loaderCache, variable, () => {
|
|
200
|
+
const init = unchangedInit(variable);
|
|
201
|
+
return init !== null && isLoader(sourceCode, init, resolve);
|
|
202
|
+
}) === true);
|
|
203
|
+
}
|
|
204
|
+
function loadedSource(sourceCode, node, resolve) {
|
|
205
|
+
const required = node.type === "CallExpression" &&
|
|
206
|
+
node.arguments.length === 1 &&
|
|
207
|
+
isLoader(sourceCode, node.callee, resolve);
|
|
208
|
+
return required ? staticString(node.arguments[0]) : dynamicImportSource(node);
|
|
209
|
+
}
|
|
175
210
|
function importBinding(definition) {
|
|
176
211
|
const declaration = definition.parent;
|
|
177
212
|
const specifier = definition.node;
|
|
@@ -194,9 +229,9 @@ function importBinding(definition) {
|
|
|
194
229
|
}
|
|
195
230
|
export function importedBinding(sourceCode, node) {
|
|
196
231
|
const value = unwrapExpression(node);
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
return { source
|
|
232
|
+
const source = loadedSource(sourceCode, value, (callee) => importedBinding(sourceCode, callee));
|
|
233
|
+
if (source !== null) {
|
|
234
|
+
return { source, name: "*" };
|
|
200
235
|
}
|
|
201
236
|
if (value.type === "MemberExpression") {
|
|
202
237
|
return memberBinding(importedBinding(sourceCode, value.object), staticPropertyName(value));
|
|
@@ -213,6 +248,9 @@ export function importedBinding(sourceCode, node) {
|
|
|
213
248
|
: aliasTarget(alias, (init) => importedBinding(sourceCode, init), memberBinding);
|
|
214
249
|
});
|
|
215
250
|
}
|
|
251
|
+
export function isRequire(sourceCode, node) {
|
|
252
|
+
return isLoader(sourceCode, node, (callee) => importedBinding(sourceCode, callee));
|
|
253
|
+
}
|
|
216
254
|
function bindingReference(binding) {
|
|
217
255
|
const defaultExport = defaultImports.get(binding.source);
|
|
218
256
|
if (binding.name === "default") {
|
|
@@ -240,6 +278,10 @@ function memberReference(object, name) {
|
|
|
240
278
|
}
|
|
241
279
|
export function apiReference(sourceCode, node) {
|
|
242
280
|
const value = unwrapExpression(node);
|
|
281
|
+
const source = loadedSource(sourceCode, value, (callee) => importedBinding(sourceCode, callee));
|
|
282
|
+
if (source !== null) {
|
|
283
|
+
return { module: source, name: "*" };
|
|
284
|
+
}
|
|
243
285
|
if (value.type === "Identifier") {
|
|
244
286
|
const variable = variableFor(sourceCode, value);
|
|
245
287
|
if (!variable?.defs.length) {
|
|
@@ -288,8 +330,7 @@ export function apiReference(sourceCode, node) {
|
|
|
288
330
|
? apiReference(sourceCode, tag.object)
|
|
289
331
|
: null;
|
|
290
332
|
}
|
|
291
|
-
|
|
292
|
-
return dynamicSource === null ? null : { module: dynamicSource, name: "*" };
|
|
333
|
+
return null;
|
|
293
334
|
}
|
|
294
335
|
export function testContext(sourceCode, node) {
|
|
295
336
|
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
@@ -362,15 +403,9 @@ function isProvider(callback, value) {
|
|
|
362
403
|
holder.right === provider &&
|
|
363
404
|
["??", "||"].includes(holder.operator)));
|
|
364
405
|
}
|
|
365
|
-
export function
|
|
366
|
-
const parent = source.parent;
|
|
367
|
-
if (source.type !== "NewExpression" && source.type !== "CallExpression") {
|
|
368
|
-
const callee = outermostExpression(source);
|
|
369
|
-
const call = callee.parent;
|
|
370
|
-
return call?.type === "CallExpression" && call.callee === callee ? isInjectionSeam(call) : true;
|
|
371
|
-
}
|
|
406
|
+
export function isInjectedValue(source) {
|
|
372
407
|
let child = source;
|
|
373
|
-
for (let ancestor = parent; ancestor; child = ancestor, ancestor = ancestor.parent) {
|
|
408
|
+
for (let ancestor = source.parent; ancestor; child = ancestor, ancestor = ancestor.parent) {
|
|
374
409
|
if (ancestor.type === "AssignmentPattern") {
|
|
375
410
|
return ancestor.right === child;
|
|
376
411
|
}
|
|
@@ -380,6 +415,14 @@ export function isInjectionSeam(source) {
|
|
|
380
415
|
}
|
|
381
416
|
return false;
|
|
382
417
|
}
|
|
418
|
+
export function isInjectionSeam(source) {
|
|
419
|
+
if (source.type !== "NewExpression" && source.type !== "CallExpression") {
|
|
420
|
+
const callee = outermostExpression(source);
|
|
421
|
+
const call = callee.parent;
|
|
422
|
+
return call?.type === "CallExpression" && call.callee === callee ? isInjectionSeam(call) : true;
|
|
423
|
+
}
|
|
424
|
+
return isInjectedValue(source);
|
|
425
|
+
}
|
|
383
426
|
export function isShortCircuited(call) {
|
|
384
427
|
let current = call;
|
|
385
428
|
while (current.type === "CallExpression" || current.type === "MemberExpression") {
|