@jameswyse/oxc-config 0.4.0 → 0.6.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 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 or literal dynamic import, 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, 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`, 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 `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`. Destructuring `env` from any of these counts in a declaration, a parameter default such as `({ env } = process) => env`, or an assignment. Destructuring `env` through a namespace's `default`, such as `({ default: { env } } = p)`, or into a rest element, such as `({ ...rest } = process)`, isn't followed. 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 calls to 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` at the call that supplies their last curried argument. Helpers count whether they're imported by name, through a namespace, from a `date-fns/<name>` subpath or by literal dynamic import. 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`, partly applied `date-fns/fp` helpers, and re-exports of `TZDate`, `TZDateMini`, other date-fns helpers or types pass. Parameter defaults, uncalled `Date.now`, provider functions that only return the read, 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.
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, and an exported one where it's declared, including a name destructured from a namespace, such as `export const { isPast } = dates`. 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 { importedBinding, isGlobal, matchesAny, projectPath, variableFor, } from "../shared/index.js";
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,98 @@ 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 exportPath(sourceCode, node) {
35
+ function isClockHelper(sourceCode, node) {
39
36
  const binding = importedBinding(sourceCode, node);
40
- return binding === null ? null : helperPath(binding.source, binding.name);
37
+ return binding !== null && clockHelpers.has(helperPath(binding.source, binding.name));
41
38
  }
42
39
  function exposesClockHelper(source) {
43
- return [...clockExports].some((helper) => helperPath(source, helper.slice(helper.lastIndexOf("/") + 1)) === helper);
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"))));
57
+ }
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()));
44
66
  }
45
- function fullApplication(call, arity) {
46
- if (call.arguments.some((argument) => argument.type === "SpreadElement")) {
47
- return null;
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
- const remaining = arity - call.arguments.length;
50
- if (remaining <= 0) {
51
- return call;
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;
79
+ }
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");
111
+ }
112
+ function destructuredExports(sourceCode, node) {
113
+ const declaration = node.declaration;
114
+ if (declaration?.type !== "VariableDeclaration") {
115
+ return [];
52
116
  }
53
- const parent = outermostExpression(call).parent;
54
- return parent?.type === "CallExpression" && unwrapExpression(parent.callee) === call
55
- ? fullApplication(parent, remaining)
56
- : null;
117
+ return declaration.declarations
118
+ .filter((declarator) => declarator.id.type === "ObjectPattern")
119
+ .flatMap((declarator) => sourceCode.getDeclaredVariables(declarator))
120
+ .flatMap((variable) => variable.identifiers);
57
121
  }
58
122
  function isTimeZoneDate(sourceCode, node) {
59
123
  const binding = importedBinding(sourceCode, node);
@@ -84,11 +148,25 @@ export const noAmbientClock = defineRule({
84
148
  context.report({ node, messageId: "clock" });
85
149
  }
86
150
  }
151
+ function reportHelper(reference) {
152
+ const use = helperUse(reference);
153
+ if ((use !== reference || !isFollowedAlias(reference)) && !isInjectedValue(use)) {
154
+ context.report({ node: use, messageId: "clock" });
155
+ }
156
+ }
87
157
  return {
158
+ Identifier(node) {
159
+ if (isClockHelper(sourceCode, node) && isValueReference(sourceCode, node)) {
160
+ reportHelper(node);
161
+ }
162
+ },
88
163
  MemberExpression(node) {
89
164
  if (staticPropertyName(node) === "now" && isGlobalValue(sourceCode, node.object, "Date")) {
90
165
  report(node);
91
166
  }
167
+ if (isClockHelper(sourceCode, node) && !isWriteTarget(node)) {
168
+ reportHelper(node);
169
+ }
92
170
  },
93
171
  NewExpression(node) {
94
172
  if (node.arguments.length === 0 &&
@@ -98,25 +176,23 @@ export const noAmbientClock = defineRule({
98
176
  }
99
177
  },
100
178
  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
179
  if (isGlobalValue(sourceCode, node.callee, "Date") ||
108
- (helper !== null && clockHelpers.has(helper)) ||
109
180
  isCurrentTimeZoneDate(sourceCode, node)) {
110
181
  report(node);
111
182
  }
112
183
  },
113
184
  ExportNamedDeclaration(node) {
185
+ for (const binding of destructuredExports(sourceCode, node)) {
186
+ if (isClockHelper(sourceCode, binding)) {
187
+ context.report({ node: binding, messageId: "clock" });
188
+ }
189
+ }
114
190
  if (!node.source || node.exportKind === "type") {
115
191
  return;
116
192
  }
117
193
  for (const specifier of node.specifiers) {
118
194
  if (specifier.exportKind !== "type" &&
119
- clockExports.has(helperPath(node.source.value, moduleExportName(specifier.local)))) {
195
+ clockHelpers.has(helperPath(node.source.value, moduleExportName(specifier.local)))) {
120
196
  context.report({ node: specifier, messageId: "clock" });
121
197
  }
122
198
  }
@@ -20,6 +20,11 @@ function isEnvironmentSource(sourceCode, node) {
20
20
  isImportMeta(node) ||
21
21
  isProcessModule(sourceCode, node));
22
22
  }
23
+ function destructuresEnvironment(sourceCode, pattern, value) {
24
+ return (pattern.type === "ObjectPattern" &&
25
+ isEnvironmentSource(sourceCode, value) &&
26
+ pattern.properties.some((property) => property.type === "Property" && staticPropertyName(property) === "env"));
27
+ }
23
28
  function isReflectGet(sourceCode, node) {
24
29
  const callee = unwrapExpression(node.callee);
25
30
  const [target, key] = node.arguments;
@@ -48,9 +53,17 @@ export const noEnvAccess = defineRule({
48
53
  }
49
54
  },
50
55
  VariableDeclarator(node) {
51
- if (node.id.type === "ObjectPattern" &&
52
- isEnvironmentSource(sourceCode, node.init) &&
53
- node.id.properties.some((property) => property.type === "Property" && staticPropertyName(property) === "env")) {
56
+ if (destructuresEnvironment(sourceCode, node.id, node.init)) {
57
+ context.report({ node, messageId: "env" });
58
+ }
59
+ },
60
+ AssignmentPattern(node) {
61
+ if (destructuresEnvironment(sourceCode, node.left, node.right)) {
62
+ context.report({ node, messageId: "env" });
63
+ }
64
+ },
65
+ AssignmentExpression(node) {
66
+ if (destructuresEnvironment(sourceCode, node.left, node.right)) {
54
67
  context.report({ node, messageId: "env" });
55
68
  }
56
69
  },
@@ -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;
@@ -133,13 +133,6 @@ function memberBinding(owner, name) {
133
133
  ? { source: owner.source, name }
134
134
  : null;
135
135
  }
136
- function bindsName(property, name) {
137
- if (property.type !== "Property") {
138
- return false;
139
- }
140
- const target = property.value.type === "AssignmentPattern" ? property.value.left : property.value;
141
- return target.type === "Identifier" && target.name === name;
142
- }
143
136
  function constAlias(definition) {
144
137
  const declaration = definition.parent;
145
138
  const declarator = definition.node;
@@ -151,19 +144,35 @@ function constAlias(definition) {
151
144
  ? { init: declarator.init, pattern: declarator.id, name: definition.name.name }
152
145
  : null;
153
146
  }
154
- function destructuredName(pattern, name) {
155
- const property = pattern.type === "ObjectPattern"
156
- ? pattern.properties.find((candidate) => bindsName(candidate, name))
157
- : undefined;
158
- return property ? staticPropertyName(property) : null;
147
+ function destructuredPath(pattern, name) {
148
+ if (pattern.type !== "ObjectPattern") {
149
+ return null;
150
+ }
151
+ for (const property of pattern.properties) {
152
+ const key = property.type === "Property" ? staticPropertyName(property) : null;
153
+ if (property.type !== "Property" || key === null) {
154
+ continue;
155
+ }
156
+ const target = property.value.type === "AssignmentPattern" ? property.value.left : property.value;
157
+ if (target.type === "Identifier" && target.name === name) {
158
+ return [key];
159
+ }
160
+ const path = destructuredPath(target, name);
161
+ if (path !== null) {
162
+ return [key, ...path];
163
+ }
164
+ }
165
+ return null;
159
166
  }
160
167
  function aliasTarget(alias, resolve, member) {
161
168
  const initial = resolve(alias.init);
162
169
  if (alias.pattern.type === "Identifier") {
163
170
  return initial;
164
171
  }
165
- const name = destructuredName(alias.pattern, alias.name);
166
- return initial !== null && name !== null ? member(initial, name) : null;
172
+ const path = destructuredPath(alias.pattern, alias.name);
173
+ return path === null
174
+ ? null
175
+ : path.reduce((owner, name) => (owner === null ? null : member(owner, name)), initial);
167
176
  }
168
177
  function cached(cache, variable, resolve) {
169
178
  if (!cache.has(variable)) {
@@ -172,6 +181,41 @@ function cached(cache, variable, resolve) {
172
181
  }
173
182
  return cache.get(variable) ?? null;
174
183
  }
184
+ const nodeModuleSources = new Set(["module", "node:module"]);
185
+ const loaderCache = new WeakMap();
186
+ function unchangedInit(variable) {
187
+ const [definition, ...otherDefinitions] = variable.defs;
188
+ const declarator = definition?.node;
189
+ return otherDefinitions.length === 0 &&
190
+ definition?.type === "Variable" &&
191
+ declarator?.type === "VariableDeclarator" &&
192
+ declarator.id === definition.name &&
193
+ variable.references.every((reference) => reference.init || !reference.isWrite())
194
+ ? declarator.init
195
+ : null;
196
+ }
197
+ function isRequireFactory(node, resolve) {
198
+ const binding = node.type === "CallExpression" ? resolve(node.callee) : null;
199
+ return (binding !== null && nodeModuleSources.has(binding.source) && binding.name === "createRequire");
200
+ }
201
+ function isLoader(sourceCode, node, resolve) {
202
+ const callee = unwrapExpression(node);
203
+ if (isGlobal(sourceCode, callee, "require") || isRequireFactory(callee, resolve)) {
204
+ return true;
205
+ }
206
+ const variable = variableFor(sourceCode, callee);
207
+ return (variable !== null &&
208
+ cached(loaderCache, variable, () => {
209
+ const init = unchangedInit(variable);
210
+ return init !== null && isLoader(sourceCode, init, resolve);
211
+ }) === true);
212
+ }
213
+ function loadedSource(sourceCode, node, resolve) {
214
+ const required = node.type === "CallExpression" &&
215
+ node.arguments.length === 1 &&
216
+ isLoader(sourceCode, node.callee, resolve);
217
+ return required ? staticString(node.arguments[0]) : dynamicImportSource(node);
218
+ }
175
219
  function importBinding(definition) {
176
220
  const declaration = definition.parent;
177
221
  const specifier = definition.node;
@@ -194,9 +238,9 @@ function importBinding(definition) {
194
238
  }
195
239
  export function importedBinding(sourceCode, node) {
196
240
  const value = unwrapExpression(node);
197
- const dynamicSource = dynamicImportSource(value);
198
- if (dynamicSource !== null) {
199
- return { source: dynamicSource, name: "*" };
241
+ const source = loadedSource(sourceCode, value, (callee) => importedBinding(sourceCode, callee));
242
+ if (source !== null) {
243
+ return { source, name: "*" };
200
244
  }
201
245
  if (value.type === "MemberExpression") {
202
246
  return memberBinding(importedBinding(sourceCode, value.object), staticPropertyName(value));
@@ -213,6 +257,9 @@ export function importedBinding(sourceCode, node) {
213
257
  : aliasTarget(alias, (init) => importedBinding(sourceCode, init), memberBinding);
214
258
  });
215
259
  }
260
+ export function isRequire(sourceCode, node) {
261
+ return isLoader(sourceCode, node, (callee) => importedBinding(sourceCode, callee));
262
+ }
216
263
  function bindingReference(binding) {
217
264
  const defaultExport = defaultImports.get(binding.source);
218
265
  if (binding.name === "default") {
@@ -240,6 +287,10 @@ function memberReference(object, name) {
240
287
  }
241
288
  export function apiReference(sourceCode, node) {
242
289
  const value = unwrapExpression(node);
290
+ const source = loadedSource(sourceCode, value, (callee) => importedBinding(sourceCode, callee));
291
+ if (source !== null) {
292
+ return { module: source, name: "*" };
293
+ }
243
294
  if (value.type === "Identifier") {
244
295
  const variable = variableFor(sourceCode, value);
245
296
  if (!variable?.defs.length) {
@@ -288,8 +339,7 @@ export function apiReference(sourceCode, node) {
288
339
  ? apiReference(sourceCode, tag.object)
289
340
  : null;
290
341
  }
291
- const dynamicSource = dynamicImportSource(value);
292
- return dynamicSource === null ? null : { module: dynamicSource, name: "*" };
342
+ return null;
293
343
  }
294
344
  export function testContext(sourceCode, node) {
295
345
  for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
@@ -362,15 +412,9 @@ function isProvider(callback, value) {
362
412
  holder.right === provider &&
363
413
  ["??", "||"].includes(holder.operator)));
364
414
  }
365
- export function isInjectionSeam(source) {
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
- }
415
+ export function isInjectedValue(source) {
372
416
  let child = source;
373
- for (let ancestor = parent; ancestor; child = ancestor, ancestor = ancestor.parent) {
417
+ for (let ancestor = source.parent; ancestor; child = ancestor, ancestor = ancestor.parent) {
374
418
  if (ancestor.type === "AssignmentPattern") {
375
419
  return ancestor.right === child;
376
420
  }
@@ -380,6 +424,14 @@ export function isInjectionSeam(source) {
380
424
  }
381
425
  return false;
382
426
  }
427
+ export function isInjectionSeam(source) {
428
+ if (source.type !== "NewExpression" && source.type !== "CallExpression") {
429
+ const callee = outermostExpression(source);
430
+ const call = callee.parent;
431
+ return call?.type === "CallExpression" && call.callee === callee ? isInjectionSeam(call) : true;
432
+ }
433
+ return isInjectedValue(source);
434
+ }
383
435
  export function isShortCircuited(call) {
384
436
  let current = call;
385
437
  while (current.type === "CallExpression" || current.type === "MemberExpression") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jameswyse/oxc-config",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs with custom rules for TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "config",