oxlint-plugin-react-doctor 0.7.4-dev.118f806 → 0.7.4-dev.4a5d9bb

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.
Files changed (2) hide show
  1. package/dist/index.js +524 -209
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9191,6 +9191,24 @@ const fileContainsReleaseForUsage = (usage, context) => {
9191
9191
  });
9192
9192
  return didFindRelease;
9193
9193
  };
9194
+ const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
9195
+ const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
9196
+ if (!bindingIdentifier) return false;
9197
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
9198
+ const isSubscribeBinding = (candidateBinding) => {
9199
+ const symbol = context.scopes.symbolFor(candidateBinding);
9200
+ if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.length === 0) return false;
9201
+ visitedSymbolIds.add(symbol.id);
9202
+ return symbol.references.every((reference) => {
9203
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
9204
+ const referenceParent = referenceRoot.parent;
9205
+ if (isNodeOfType(referenceParent, "CallExpression") && referenceParent.arguments?.[0] === referenceRoot) return isReactApiCall(referenceParent, "useSyncExternalStore", context.scopes);
9206
+ const aliasDeclaration = referenceParent?.parent;
9207
+ return Boolean(isNodeOfType(referenceParent, "VariableDeclarator") && referenceParent.init === referenceRoot && isNodeOfType(referenceParent.id, "Identifier") && isNodeOfType(aliasDeclaration, "VariableDeclaration") && aliasDeclaration.kind === "const" && isSubscribeBinding(referenceParent.id));
9208
+ });
9209
+ };
9210
+ return isSubscribeBinding(bindingIdentifier);
9211
+ };
9194
9212
  const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
9195
9213
  let currentNode = resourceNode;
9196
9214
  let parentNode = currentNode.parent;
@@ -9212,6 +9230,8 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
9212
9230
  if (!body) return null;
9213
9231
  let leak = null;
9214
9232
  const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
9233
+ const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
9234
+ const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context);
9215
9235
  walkAst(body, (child) => {
9216
9236
  if (leak !== null) return false;
9217
9237
  if (isFunctionLike$1(child)) return false;
@@ -9226,7 +9246,7 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
9226
9246
  eventKey: null,
9227
9247
  handlerKey: null
9228
9248
  };
9229
- if (!fileContainsReleaseForUsage(socketUsage, context)) {
9249
+ if (!hasReleaseForUsage(socketUsage)) {
9230
9250
  leak = socketUsage;
9231
9251
  return false;
9232
9252
  }
@@ -9243,7 +9263,7 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
9243
9263
  eventKey: null,
9244
9264
  handlerKey: null
9245
9265
  };
9246
- if (!fileContainsReleaseForUsage(timerUsage, context)) {
9266
+ if (!hasReleaseForUsage(timerUsage)) {
9247
9267
  leak = timerUsage;
9248
9268
  return false;
9249
9269
  }
@@ -9257,7 +9277,7 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
9257
9277
  handleKey: findAssignedResourceKey(child, context),
9258
9278
  ...registrationDetails
9259
9279
  };
9260
- if (!hasSelfReleasingListenerOptions(child, context) && !fileContainsReleaseForUsage(subscriptionUsage, context)) leak = subscriptionUsage;
9280
+ if (!hasSelfReleasingListenerOptions(child, context) && !hasReleaseForUsage(subscriptionUsage)) leak = subscriptionUsage;
9261
9281
  return false;
9262
9282
  }
9263
9283
  });
@@ -20988,6 +21008,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
20988
21008
  continue;
20989
21009
  }
20990
21010
  if (isNodeOfType(statement, "ExportNamedDeclaration")) {
21011
+ if (statement.exportKind === "type") continue;
20991
21012
  const declaration = statement.declaration;
20992
21013
  if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
20993
21014
  recordVariableDeclaration(declaration);
@@ -21002,6 +21023,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
21002
21023
  }
21003
21024
  for (const specifier of statement.specifiers ?? []) {
21004
21025
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
21026
+ if (specifier.exportKind === "type") continue;
21005
21027
  const local = specifier.local;
21006
21028
  const exported = specifier.exported;
21007
21029
  if (!isNodeOfType(local, "Identifier")) continue;
@@ -21050,25 +21072,37 @@ const resolveImportedExportName = (importSpecifier) => {
21050
21072
  if (isNodeOfType(importSpecifier, "ImportDefaultSpecifier")) return "default";
21051
21073
  return null;
21052
21074
  };
21053
- const findReExportSourcesForName = (programRoot, exportedName) => {
21075
+ const findReExportTargetsForName = (programRoot, exportedName) => {
21054
21076
  if (!isNodeOfType(programRoot, "Program")) return [];
21055
- const exportAllSources = [];
21077
+ const exportAllTargets = [];
21056
21078
  for (const statement of programRoot.body ?? []) {
21057
21079
  if (isNodeOfType(statement, "ExportNamedDeclaration") && statement.source) {
21080
+ if (statement.exportKind === "type") continue;
21058
21081
  const sourceValue = statement.source.value;
21059
21082
  if (typeof sourceValue !== "string") continue;
21060
21083
  for (const specifier of statement.specifiers ?? []) {
21061
21084
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
21085
+ if (specifier.exportKind === "type") continue;
21062
21086
  const exported = specifier.exported;
21063
- if ((isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null) === exportedName) return [sourceValue];
21087
+ if ((isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null) !== exportedName) continue;
21088
+ const local = specifier.local;
21089
+ const importedName = isNodeOfType(local, "Identifier") ? local.name : isNodeOfType(local, "Literal") && typeof local.value === "string" ? local.value : null;
21090
+ if (importedName) return [{
21091
+ importedName,
21092
+ source: sourceValue
21093
+ }];
21064
21094
  }
21065
21095
  }
21066
21096
  if (isNodeOfType(statement, "ExportAllDeclaration") && statement.source) {
21097
+ if (statement.exportKind === "type" || statement.exported) continue;
21067
21098
  const sourceValue = statement.source.value;
21068
- if (typeof sourceValue === "string") exportAllSources.push(sourceValue);
21099
+ if (typeof sourceValue === "string") exportAllTargets.push({
21100
+ importedName: exportedName,
21101
+ source: sourceValue
21102
+ });
21069
21103
  }
21070
21104
  }
21071
- return exportAllSources;
21105
+ return exportAllTargets;
21072
21106
  };
21073
21107
  //#endregion
21074
21108
  //#region src/plugin/utils/attach-parent-references.ts
@@ -21157,139 +21191,6 @@ const parseSourceFile = (absoluteFilePath) => {
21157
21191
  return parsedProgram;
21158
21192
  };
21159
21193
  //#endregion
21160
- //#region src/plugin/utils/is-barrel-index-module.ts
21161
- const INDEX_MODULE_FILE_PATTERN = /^index\.(?:[cm]?[jt]sx?|mjs)$/;
21162
- const BINDING_IMPORT_DECLARATION_PATTERN = /^\s*import\s+(type\s+)?(?!["'])([^;]*?)\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
21163
- const BARREL_REEXPORT_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?(?:\*(?:\s+as\s+([\w$]+))?|\{([\s\S]*?)\})\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
21164
- const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?\{([\s\S]*?)\}\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
21165
- const barrelIndexModuleInfoCache = /* @__PURE__ */ new Map();
21166
- const isIndexModuleFilePath = (filePath) => INDEX_MODULE_FILE_PATTERN.test(path.basename(filePath));
21167
- const createNonBarrelInfo = () => ({
21168
- isBarrel: false,
21169
- exportsByName: /* @__PURE__ */ new Map(),
21170
- starExportSources: []
21171
- });
21172
- const addImportedBinding = (importedBindings, binding) => {
21173
- importedBindings.set(binding.localName, {
21174
- ...binding,
21175
- didExport: false
21176
- });
21177
- };
21178
- const collectNamedImportBindings = (namedSpecifiersText, source, declarationIsTypeOnly, importedBindings) => {
21179
- for (const specifier of parseExportSpecifiers(namedSpecifiersText, declarationIsTypeOnly)) addImportedBinding(importedBindings, {
21180
- localName: specifier.exportedName,
21181
- importedName: specifier.localName,
21182
- source,
21183
- isTypeOnly: specifier.isTypeOnly
21184
- });
21185
- };
21186
- const collectImportBindings = (importClause, source, declarationIsTypeOnly, importedBindings) => {
21187
- const trimmedImportClause = importClause.trim();
21188
- const namespaceMatch = trimmedImportClause.match(/(?:^|,\s*)\*\s+as\s+([\w$]+)/);
21189
- if (namespaceMatch?.[1]) addImportedBinding(importedBindings, {
21190
- localName: namespaceMatch[1],
21191
- importedName: "*",
21192
- source,
21193
- isTypeOnly: declarationIsTypeOnly
21194
- });
21195
- const namedImportMatch = trimmedImportClause.match(/\{([\s\S]*?)\}/);
21196
- if (namedImportMatch?.[1]) collectNamedImportBindings(namedImportMatch[1], source, declarationIsTypeOnly, importedBindings);
21197
- const defaultImportName = trimmedImportClause.split(",")[0]?.trim();
21198
- if (defaultImportName && !defaultImportName.startsWith("{") && !defaultImportName.startsWith("*")) addImportedBinding(importedBindings, {
21199
- localName: defaultImportName,
21200
- importedName: "default",
21201
- source,
21202
- isTypeOnly: declarationIsTypeOnly
21203
- });
21204
- };
21205
- const replaceKnownDeclarations = (sourceText, importedBindings, exportsByName, starExportSources) => {
21206
- let withoutKnownDeclarations = sourceText.replace(BINDING_IMPORT_DECLARATION_PATTERN, (_match, typeKeyword, importClause, source) => {
21207
- collectImportBindings(importClause, source, Boolean(typeKeyword), importedBindings);
21208
- return "";
21209
- });
21210
- withoutKnownDeclarations = withoutKnownDeclarations.replace(BARREL_REEXPORT_DECLARATION_PATTERN, (_match, typeKeyword, namespaceExportName, specifiersText, source) => {
21211
- const isTypeOnly = Boolean(typeKeyword);
21212
- if (namespaceExportName) {
21213
- exportsByName.set(namespaceExportName, {
21214
- exportedName: namespaceExportName,
21215
- importedName: "*",
21216
- source,
21217
- isTypeOnly
21218
- });
21219
- return "";
21220
- }
21221
- if (specifiersText) {
21222
- for (const specifier of parseExportSpecifiers(specifiersText, isTypeOnly)) exportsByName.set(specifier.exportedName, {
21223
- exportedName: specifier.exportedName,
21224
- importedName: specifier.localName,
21225
- source,
21226
- isTypeOnly: specifier.isTypeOnly
21227
- });
21228
- return "";
21229
- }
21230
- starExportSources.push(source);
21231
- return "";
21232
- });
21233
- withoutKnownDeclarations = withoutKnownDeclarations.replace(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN, (_match, typeKeyword, specifiersText) => {
21234
- for (const specifier of parseExportSpecifiers(specifiersText, Boolean(typeKeyword))) {
21235
- const importedBinding = importedBindings.get(specifier.localName);
21236
- if (!importedBinding) return _match;
21237
- importedBinding.didExport = true;
21238
- exportsByName.set(specifier.exportedName, {
21239
- exportedName: specifier.exportedName,
21240
- importedName: importedBinding.importedName,
21241
- source: importedBinding.source,
21242
- isTypeOnly: specifier.isTypeOnly || importedBinding.isTypeOnly
21243
- });
21244
- }
21245
- return "";
21246
- });
21247
- return withoutKnownDeclarations;
21248
- };
21249
- const hasUnexportedRuntimeImport = (importedBindings) => {
21250
- for (const binding of importedBindings.values()) if (!binding.isTypeOnly && !binding.didExport) return true;
21251
- return false;
21252
- };
21253
- const classifyBarrelModule = (sourceText) => {
21254
- const strippedSource = stripJsComments(sourceText).trim();
21255
- if (!strippedSource) return createNonBarrelInfo();
21256
- const importedBindings = /* @__PURE__ */ new Map();
21257
- const exportsByName = /* @__PURE__ */ new Map();
21258
- const starExportSources = [];
21259
- if (replaceKnownDeclarations(strippedSource, importedBindings, exportsByName, starExportSources).trim() || hasUnexportedRuntimeImport(importedBindings)) return createNonBarrelInfo();
21260
- return {
21261
- isBarrel: exportsByName.size > 0 || starExportSources.length > 0,
21262
- exportsByName,
21263
- starExportSources
21264
- };
21265
- };
21266
- const getBarrelIndexModuleInfo = (filePath) => {
21267
- if (!isIndexModuleFilePath(filePath)) return createNonBarrelInfo();
21268
- recordContentProbe(filePath);
21269
- let fileStat;
21270
- try {
21271
- fileStat = fs.statSync(filePath);
21272
- } catch {
21273
- fileStat = null;
21274
- }
21275
- const cachedResult = barrelIndexModuleInfoCache.get(filePath);
21276
- if (cachedResult !== void 0 && fileStat !== null && cachedResult.mtimeMs === fileStat.mtimeMs && cachedResult.size === fileStat.size) return cachedResult.moduleInfo;
21277
- if (fileStat === null) return createNonBarrelInfo();
21278
- let moduleInfo = createNonBarrelInfo();
21279
- try {
21280
- moduleInfo = classifyBarrelModule(fs.readFileSync(filePath, "utf8"));
21281
- } catch {
21282
- moduleInfo = createNonBarrelInfo();
21283
- }
21284
- barrelIndexModuleInfoCache.set(filePath, {
21285
- mtimeMs: fileStat.mtimeMs,
21286
- size: fileStat.size,
21287
- moduleInfo
21288
- });
21289
- return moduleInfo;
21290
- };
21291
- const isBarrelIndexModule = (filePath) => getBarrelIndexModuleInfo(filePath).isBarrel;
21292
- //#endregion
21293
21194
  //#region src/plugin/utils/resolve-relative-import-path.ts
21294
21195
  const MODULE_FILE_EXTENSIONS = [
21295
21196
  ".ts",
@@ -21406,35 +21307,6 @@ const resolveModuleFileFromAbsolutePath = (importPath) => {
21406
21307
  };
21407
21308
  const resolveRelativeImportPath = (filename, source) => resolveModuleFileFromAbsolutePath(path.resolve(path.dirname(filename), source));
21408
21309
  //#endregion
21409
- //#region src/plugin/utils/resolve-barrel-export-file-path.ts
21410
- const getUniqueFilePath = (filePaths) => {
21411
- const uniqueFilePaths = new Set(filePaths);
21412
- if (uniqueFilePaths.size !== 1) return null;
21413
- const [filePath] = uniqueFilePaths;
21414
- return filePath ?? null;
21415
- };
21416
- const resolveStarExportFilePath = (barrelFilePath, exportedName, source, visitedFilePaths) => {
21417
- const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, source);
21418
- if (!resolvedTargetPath) return null;
21419
- const nestedTargetPath = resolveBarrelExportFilePath(resolvedTargetPath, exportedName, new Set(visitedFilePaths));
21420
- if (nestedTargetPath) return nestedTargetPath;
21421
- return doesModuleExportName(resolvedTargetPath, exportedName) ? resolvedTargetPath : null;
21422
- };
21423
- const resolveBarrelExportFilePath = (barrelFilePath, exportedName, visitedFilePaths = /* @__PURE__ */ new Set()) => {
21424
- if (visitedFilePaths.has(barrelFilePath)) return null;
21425
- visitedFilePaths.add(barrelFilePath);
21426
- const moduleInfo = getBarrelIndexModuleInfo(barrelFilePath);
21427
- if (!moduleInfo.isBarrel) return null;
21428
- const target = moduleInfo.exportsByName.get(exportedName);
21429
- if (target) {
21430
- const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, target.source);
21431
- if (!resolvedTargetPath) return null;
21432
- return resolveBarrelExportFilePath(resolvedTargetPath, target.importedName, visitedFilePaths) ?? resolvedTargetPath;
21433
- }
21434
- if (exportedName === "default") return null;
21435
- return getUniqueFilePath(moduleInfo.starExportSources.map((source) => resolveStarExportFilePath(barrelFilePath, exportedName, source, visitedFilePaths)).filter((filePath) => Boolean(filePath)));
21436
- };
21437
- //#endregion
21438
21310
  //#region src/plugin/utils/resolve-tsconfig-alias.ts
21439
21311
  const TSCONFIG_FILE_NAMES = ["tsconfig.json", "jsconfig.json"];
21440
21312
  const isObjectRecord = (value) => typeof value === "object" && value !== null;
@@ -21613,25 +21485,29 @@ const resolveTsconfigAliasPath = (fromFilename, source) => {
21613
21485
  };
21614
21486
  //#endregion
21615
21487
  //#region src/plugin/utils/resolve-module-path.ts
21616
- const resolveModulePath = (fromFilename, source) => resolveRelativeImportPath(fromFilename, source) ?? resolveTsconfigAliasPath(fromFilename, source);
21488
+ const resolveModulePath = (fromFilename, source) => {
21489
+ if (path.isAbsolute(source)) return null;
21490
+ return source.startsWith(".") ? resolveRelativeImportPath(fromFilename, source) : resolveTsconfigAliasPath(fromFilename, source);
21491
+ };
21617
21492
  //#endregion
21618
21493
  //#region src/plugin/utils/resolve-cross-file-function-export.ts
21619
21494
  const resolveFunctionExportInFile = (filePath, exportedName, visitedFilePaths) => {
21620
21495
  if (visitedFilePaths.size >= 4) return null;
21621
21496
  if (visitedFilePaths.has(filePath)) return null;
21622
21497
  visitedFilePaths.add(filePath);
21623
- const actualFilePath = resolveBarrelExportFilePath(filePath, exportedName) ?? filePath;
21624
- const programRoot = parseSourceFile(actualFilePath);
21498
+ const programRoot = parseSourceFile(filePath);
21625
21499
  if (!programRoot) return null;
21626
21500
  const exported = findExportedFunctionBody(programRoot, exportedName);
21627
21501
  if (exported) return exported;
21628
- for (const reExportSource of findReExportSourcesForName(programRoot, exportedName)) {
21629
- const nextFilePath = resolveModulePath(actualFilePath, reExportSource);
21502
+ const resolvedCandidates = /* @__PURE__ */ new Set();
21503
+ for (const target of findReExportTargetsForName(programRoot, exportedName)) {
21504
+ const nextFilePath = resolveModulePath(filePath, target.source);
21630
21505
  if (!nextFilePath) continue;
21631
- const resolved = resolveFunctionExportInFile(nextFilePath, exportedName, visitedFilePaths);
21632
- if (resolved) return resolved;
21506
+ const resolved = resolveFunctionExportInFile(nextFilePath, target.importedName, new Set(visitedFilePaths));
21507
+ if (resolved) resolvedCandidates.add(resolved);
21633
21508
  }
21634
- return null;
21509
+ if (resolvedCandidates.size !== 1) return null;
21510
+ return resolvedCandidates.values().next().value ?? null;
21635
21511
  };
21636
21512
  const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
21637
21513
  const resolvedFilePath = resolveModulePath(fromFilename, source);
@@ -22814,6 +22690,46 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
22814
22690
  "parseInt"
22815
22691
  ]);
22816
22692
  const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
22693
+ const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
22694
+ "isRawJSON",
22695
+ "parse",
22696
+ "rawJSON",
22697
+ "stringify"
22698
+ ])], ["Math", new Set([
22699
+ "abs",
22700
+ "acos",
22701
+ "acosh",
22702
+ "asin",
22703
+ "asinh",
22704
+ "atan",
22705
+ "atan2",
22706
+ "atanh",
22707
+ "cbrt",
22708
+ "ceil",
22709
+ "clz32",
22710
+ "cos",
22711
+ "cosh",
22712
+ "exp",
22713
+ "floor",
22714
+ "fround",
22715
+ "hypot",
22716
+ "imul",
22717
+ "log",
22718
+ "log10",
22719
+ "log1p",
22720
+ "log2",
22721
+ "max",
22722
+ "min",
22723
+ "pow",
22724
+ "round",
22725
+ "sign",
22726
+ "sin",
22727
+ "sinh",
22728
+ "sqrt",
22729
+ "tan",
22730
+ "tanh",
22731
+ "trunc"
22732
+ ])]]);
22817
22733
  const PURE_MEMBER_TRANSFORM_NAMES = new Set([
22818
22734
  "concat",
22819
22735
  "filter",
@@ -22860,6 +22776,42 @@ const getParameterBindingIdentity = (analysis, functionNode, parameter) => {
22860
22776
  }
22861
22777
  return parameter;
22862
22778
  };
22779
+ const isAsyncOrGeneratorFunction = (functionNode) => Boolean(functionNode.async === true || functionNode.generator === true);
22780
+ const isModuleFunction = (functionNode) => {
22781
+ let ancestor = functionNode.parent;
22782
+ while (ancestor) {
22783
+ if (isFunctionLike$1(ancestor)) return false;
22784
+ if (isNodeOfType(ancestor, "Program")) return true;
22785
+ ancestor = ancestor.parent;
22786
+ }
22787
+ return false;
22788
+ };
22789
+ const getFunctionBindingNames = (functionNode) => {
22790
+ const names = /* @__PURE__ */ new Set();
22791
+ if ((isNodeOfType(functionNode, "FunctionDeclaration") || isNodeOfType(functionNode, "FunctionExpression")) && functionNode.id) names.add(functionNode.id.name);
22792
+ const parent = functionNode.parent;
22793
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) names.add(parent.id.name);
22794
+ return names;
22795
+ };
22796
+ const collectModuleBindingNames = (functionNode) => {
22797
+ let program = functionNode.parent;
22798
+ while (program && !isNodeOfType(program, "Program")) program = program.parent;
22799
+ const bindingNames = /* @__PURE__ */ new Set();
22800
+ if (!program || !isNodeOfType(program, "Program")) return bindingNames;
22801
+ for (const statement of program.body ?? []) {
22802
+ if (isNodeOfType(statement, "ImportDeclaration")) {
22803
+ for (const specifier of statement.specifiers ?? []) if (isNodeOfType(specifier.local, "Identifier")) bindingNames.add(specifier.local.name);
22804
+ continue;
22805
+ }
22806
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") || isNodeOfType(statement, "ExportDefaultDeclaration") ? statement.declaration : statement;
22807
+ if (isNodeOfType(declaration, "VariableDeclaration")) {
22808
+ for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, bindingNames);
22809
+ continue;
22810
+ }
22811
+ if ((isNodeOfType(declaration, "FunctionDeclaration") || isNodeOfType(declaration, "ClassDeclaration")) && isNodeOfType(declaration.id, "Identifier")) bindingNames.add(declaration.id.name);
22812
+ }
22813
+ return bindingNames;
22814
+ };
22863
22815
  const buildSubstitutions = (analysis, functionNode, argumentExpressions, parentFrame) => {
22864
22816
  const substitutions = /* @__PURE__ */ new Map();
22865
22817
  const parameters = getFunctionParameters(functionNode);
@@ -22990,7 +22942,7 @@ const collectIntroducedBindings = (analysis, functionNode) => {
22990
22942
  for (const parameter of getFunctionParameters(functionNode)) if (isNodeOfType(parameter, "Identifier")) introducedBindings.add(getParameterBindingIdentity(analysis, functionNode, parameter));
22991
22943
  return introducedBindings;
22992
22944
  };
22993
- const collectBoundedEffectExecutionFrames = (analysis, effectNode) => {
22945
+ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilename) => {
22994
22946
  const effectFunction = getEffectFn(analysis, effectNode);
22995
22947
  if (!effectFunction || !isFunctionLike$1(effectFunction) || effectFunction.async === true) return [];
22996
22948
  const invokedFunctionEvidence = collectEffectInvokedFunctions(effectFunction);
@@ -22999,7 +22951,8 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode) => {
22999
22951
  invocation: null,
23000
22952
  isDeferred: false,
23001
22953
  introducedBindings: /* @__PURE__ */ new Set(),
23002
- substitutions: /* @__PURE__ */ new Map()
22954
+ substitutions: /* @__PURE__ */ new Map(),
22955
+ currentFilename
23003
22956
  };
23004
22957
  const frames = [rootFrame];
23005
22958
  walkAst(effectFunction, (child) => {
@@ -23020,7 +22973,8 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode) => {
23020
22973
  invocation: child,
23021
22974
  isDeferred,
23022
22975
  introducedBindings,
23023
- substitutions: buildSubstitutions(analysis, callable, argumentsForCallable, rootFrame)
22976
+ substitutions: buildSubstitutions(analysis, callable, argumentsForCallable, rootFrame),
22977
+ currentFilename
23024
22978
  });
23025
22979
  };
23026
22980
  if (isFunctionLike$1(callee)) {
@@ -23073,6 +23027,174 @@ const isOpaqueHookCall = (callExpression) => {
23073
23027
  const calleeName = getCallCalleeName$1(callExpression);
23074
23028
  return Boolean(calleeName && /^use[A-Z0-9]/.test(calleeName) && calleeName !== "useMemo" && calleeName !== "useCallback" && calleeName !== "useEffectEvent");
23075
23029
  };
23030
+ const analyzeHelperStatements = (statements, environment, usedParameterIndices, analyzeExpression) => {
23031
+ let canContinue = true;
23032
+ for (const statement of statements) {
23033
+ if (!canContinue) {
23034
+ if (!isNodeOfType(statement, "EmptyStatement")) return {
23035
+ canContinue: false,
23036
+ isValid: false
23037
+ };
23038
+ continue;
23039
+ }
23040
+ if (isNodeOfType(statement, "EmptyStatement")) continue;
23041
+ if (isNodeOfType(statement, "ReturnStatement")) {
23042
+ if (!statement.argument || !analyzeExpression(statement.argument, environment, usedParameterIndices)) return {
23043
+ canContinue: false,
23044
+ isValid: false
23045
+ };
23046
+ canContinue = false;
23047
+ continue;
23048
+ }
23049
+ if (isNodeOfType(statement, "BlockStatement")) {
23050
+ const blockSummary = analyzeHelperStatements(statement.body ?? [], environment, usedParameterIndices, analyzeExpression);
23051
+ if (!blockSummary.isValid) return blockSummary;
23052
+ canContinue = blockSummary.canContinue;
23053
+ continue;
23054
+ }
23055
+ if (isNodeOfType(statement, "IfStatement")) {
23056
+ if (!analyzeExpression(statement.test, environment, usedParameterIndices)) return {
23057
+ canContinue: false,
23058
+ isValid: false
23059
+ };
23060
+ const consequentSummary = analyzeHelperStatements([statement.consequent], environment, usedParameterIndices, analyzeExpression);
23061
+ if (!consequentSummary.isValid) return consequentSummary;
23062
+ const alternateSummary = statement.alternate ? analyzeHelperStatements([statement.alternate], environment, usedParameterIndices, analyzeExpression) : {
23063
+ canContinue: true,
23064
+ isValid: true
23065
+ };
23066
+ if (!alternateSummary.isValid) return alternateSummary;
23067
+ canContinue = consequentSummary.canContinue || alternateSummary.canContinue;
23068
+ continue;
23069
+ }
23070
+ return {
23071
+ canContinue: false,
23072
+ isValid: false
23073
+ };
23074
+ }
23075
+ return {
23076
+ canContinue,
23077
+ isValid: true
23078
+ };
23079
+ };
23080
+ const analyzeHelperExpression = (expression, environment, usedParameterIndices) => {
23081
+ const node = stripParenExpression(expression);
23082
+ if (isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateElement")) return true;
23083
+ if (isNodeOfType(node, "Identifier")) {
23084
+ const parameterIndex = environment.parameterIndices.get(node.name);
23085
+ if (parameterIndex !== void 0) {
23086
+ if (parameterIndex !== null) usedParameterIndices.add(parameterIndex);
23087
+ return true;
23088
+ }
23089
+ return node.name === "undefined" || node.name === "NaN" || node.name === "Infinity";
23090
+ }
23091
+ if (isNodeOfType(node, "ArrayExpression")) return (node.elements ?? []).every((element) => !element || analyzeHelperExpression(element, environment, usedParameterIndices));
23092
+ if (isNodeOfType(node, "ObjectExpression")) return (node.properties ?? []).every((property) => {
23093
+ if (isNodeOfType(property, "SpreadElement")) return analyzeHelperExpression(property.argument, environment, usedParameterIndices);
23094
+ if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
23095
+ if (property.computed && !analyzeHelperExpression(property.key, environment, usedParameterIndices)) return false;
23096
+ return analyzeHelperExpression(property.value, environment, usedParameterIndices);
23097
+ });
23098
+ if (isNodeOfType(node, "TemplateLiteral")) return (node.expressions ?? []).every((templateExpression) => analyzeHelperExpression(templateExpression, environment, usedParameterIndices));
23099
+ if (isNodeOfType(node, "UnaryExpression")) return node.operator !== "delete" && analyzeHelperExpression(node.argument, environment, usedParameterIndices);
23100
+ if (isNodeOfType(node, "BinaryExpression") || isNodeOfType(node, "LogicalExpression")) return analyzeHelperExpression(node.left, environment, usedParameterIndices) && analyzeHelperExpression(node.right, environment, usedParameterIndices);
23101
+ if (isNodeOfType(node, "ConditionalExpression")) return analyzeHelperExpression(node.test, environment, usedParameterIndices) && analyzeHelperExpression(node.consequent, environment, usedParameterIndices) && analyzeHelperExpression(node.alternate, environment, usedParameterIndices);
23102
+ if (isNodeOfType(node, "MemberExpression")) {
23103
+ if (!analyzeHelperExpression(node.object, environment, usedParameterIndices)) return false;
23104
+ return !node.computed || analyzeHelperExpression(node.property, environment, usedParameterIndices);
23105
+ }
23106
+ if (isFunctionLike$1(node)) {
23107
+ if (isAsyncOrGeneratorFunction(node)) return false;
23108
+ const callbackParameterIndices = new Map(environment.parameterIndices);
23109
+ for (const parameter of getFunctionParameters(node)) {
23110
+ if (!isNodeOfType(parameter, "Identifier")) return false;
23111
+ callbackParameterIndices.set(parameter.name, null);
23112
+ }
23113
+ const callbackEnvironment = {
23114
+ parameterIndices: callbackParameterIndices,
23115
+ recursiveNames: environment.recursiveNames,
23116
+ shadowedGlobalNames: environment.shadowedGlobalNames
23117
+ };
23118
+ if (!isNodeOfType(node.body, "BlockStatement")) return analyzeHelperExpression(node.body, callbackEnvironment, usedParameterIndices);
23119
+ const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
23120
+ return callbackSummary.isValid && !callbackSummary.canContinue;
23121
+ }
23122
+ if (isNodeOfType(node, "CallExpression")) {
23123
+ const callee = stripParenExpression(node.callee);
23124
+ const calleeRoot = getMemberRoot(callee);
23125
+ const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && !environment.parameterIndices.has(callee.name) && !environment.recursiveNames.has(callee.name) && !environment.shadowedGlobalNames.has(callee.name);
23126
+ const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23127
+ const namespaceMemberName = getStaticMemberName(callee);
23128
+ const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23129
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23130
+ if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23131
+ if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23132
+ return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
23133
+ }
23134
+ if (isNodeOfType(node, "SpreadElement")) return analyzeHelperExpression(node.argument, environment, usedParameterIndices);
23135
+ return false;
23136
+ };
23137
+ const helperSummaryCache = /* @__PURE__ */ new WeakMap();
23138
+ const summarizeHelperReturn = (functionNode) => {
23139
+ if (!isFunctionLike$1(functionNode) || !isModuleFunction(functionNode)) return null;
23140
+ if (helperSummaryCache.has(functionNode)) return helperSummaryCache.get(functionNode) ?? null;
23141
+ if (isAsyncOrGeneratorFunction(functionNode)) {
23142
+ helperSummaryCache.set(functionNode, null);
23143
+ return null;
23144
+ }
23145
+ const parameterIndices = /* @__PURE__ */ new Map();
23146
+ const parameters = getFunctionParameters(functionNode);
23147
+ for (let parameterIndex = 0; parameterIndex < parameters.length; parameterIndex += 1) {
23148
+ const parameter = parameters[parameterIndex];
23149
+ if (!parameter || !isNodeOfType(parameter, "Identifier") || parameterIndices.has(parameter.name)) {
23150
+ helperSummaryCache.set(functionNode, null);
23151
+ return null;
23152
+ }
23153
+ parameterIndices.set(parameter.name, parameterIndex);
23154
+ }
23155
+ const environment = {
23156
+ parameterIndices,
23157
+ recursiveNames: getFunctionBindingNames(functionNode),
23158
+ shadowedGlobalNames: collectModuleBindingNames(functionNode)
23159
+ };
23160
+ const usedParameterIndices = /* @__PURE__ */ new Set();
23161
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) {
23162
+ if (!analyzeHelperExpression(functionNode.body, environment, usedParameterIndices)) {
23163
+ helperSummaryCache.set(functionNode, null);
23164
+ return null;
23165
+ }
23166
+ } else {
23167
+ const controlFlowSummary = analyzeHelperStatements(functionNode.body.body ?? [], environment, usedParameterIndices, analyzeHelperExpression);
23168
+ if (!controlFlowSummary.isValid || controlFlowSummary.canContinue) {
23169
+ helperSummaryCache.set(functionNode, null);
23170
+ return null;
23171
+ }
23172
+ }
23173
+ const summary = { usedParameterIndices };
23174
+ helperSummaryCache.set(functionNode, summary);
23175
+ return summary;
23176
+ };
23177
+ const resolveValueHelperFunction = (analysis, callee, currentFilename) => {
23178
+ if (!isNodeOfType(callee, "Identifier")) return null;
23179
+ const reference = getRef(analysis, callee);
23180
+ if (!reference?.resolved) return null;
23181
+ const importDefinition = reference.resolved.defs.find((definition) => definition.type === "ImportBinding");
23182
+ if (importDefinition) {
23183
+ if (!currentFilename) return null;
23184
+ const specifier = importDefinition.node;
23185
+ if (!isNodeOfType(specifier, "ImportSpecifier") && !isNodeOfType(specifier, "ImportDefaultSpecifier")) return null;
23186
+ const importDeclaration = specifier.parent;
23187
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return null;
23188
+ if (importDeclaration.importKind === "type" || isNodeOfType(specifier, "ImportSpecifier") && specifier.importKind === "type") return null;
23189
+ const source = importDeclaration.source?.value;
23190
+ if (typeof source !== "string") return null;
23191
+ const exportedName = resolveImportedExportName(specifier);
23192
+ if (!exportedName) return null;
23193
+ return resolveCrossFileFunctionExport(currentFilename, source, exportedName);
23194
+ }
23195
+ const callable = resolveWrappedCallable(analysis, callee);
23196
+ return callable && isModuleFunction(callable) ? callable : null;
23197
+ };
23076
23198
  const isLocallyConstructedObjectMember = (reference, memberExpression) => isNodeOfType(memberExpression, "MemberExpression") && reference.resolved?.defs.some((definition) => {
23077
23199
  const definitionNode = definition.node;
23078
23200
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
@@ -23158,38 +23280,55 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23158
23280
  const calleeRoot = getMemberRoot(callee);
23159
23281
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
23160
23282
  const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23161
- if (isPureMemberTransform) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23162
- for (const argument of node.arguments ?? []) {
23163
- if (isFunctionLike$1(argument)) continue;
23164
- mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23165
- }
23166
- if (isPureGlobalCall || isPureMemberTransform) return evidence;
23167
- if (isNodeOfType(callee, "MemberExpression")) {
23168
- evidence.hasUnknownSource = true;
23283
+ if (isPureGlobalCall || isPureMemberTransform) {
23284
+ if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23285
+ for (const argument of node.arguments ?? []) {
23286
+ if (isFunctionLike$1(argument)) continue;
23287
+ mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23288
+ }
23169
23289
  return evidence;
23170
23290
  }
23171
23291
  if (remainingCallFrames <= 0) {
23172
23292
  evidence.hasUnknownSource = true;
23173
23293
  return evidence;
23174
23294
  }
23175
- const callable = resolveWrappedCallable(analysis, callee);
23176
- if (!callable || callable.async === true || functionInvokesItself(analysis, callable)) {
23177
- evidence.hasUnknownSource = true;
23295
+ const localHelperFunction = resolveWrappedCallable(analysis, callee);
23296
+ if (localHelperFunction && !isModuleFunction(localHelperFunction)) {
23297
+ if (isAsyncOrGeneratorFunction(localHelperFunction) || functionInvokesItself(analysis, localHelperFunction)) {
23298
+ evidence.hasUnknownSource = true;
23299
+ return evidence;
23300
+ }
23301
+ const localHelperFrame = {
23302
+ functionNode: localHelperFunction,
23303
+ invocation: node,
23304
+ isDeferred: false,
23305
+ introducedBindings: /* @__PURE__ */ new Set(),
23306
+ substitutions: buildSubstitutions(analysis, localHelperFunction, node.arguments ?? [], frame),
23307
+ currentFilename: frame.currentFilename
23308
+ };
23309
+ const returnedExpressions = getReturnedExpressions(localHelperFunction);
23310
+ if (returnedExpressions.length === 0) {
23311
+ evidence.hasUnknownSource = true;
23312
+ return evidence;
23313
+ }
23314
+ for (const returnedExpression of returnedExpressions) mergeEvidence(evidence, collectValueEvidence(analysis, returnedExpression, localHelperFrame, remainingCallFrames - 1, new Set(visitedBindings)));
23178
23315
  return evidence;
23179
23316
  }
23180
- const valueFrame = {
23181
- functionNode: callable,
23182
- invocation: node,
23183
- isDeferred: false,
23184
- introducedBindings: /* @__PURE__ */ new Set(),
23185
- substitutions: buildSubstitutions(analysis, callable, node.arguments ?? [], frame)
23186
- };
23187
- const returnedExpressions = getReturnedExpressions(callable);
23188
- if (returnedExpressions.length === 0) {
23317
+ const helperFunction = resolveValueHelperFunction(analysis, callee, frame.currentFilename);
23318
+ const helperSummary = helperFunction ? summarizeHelperReturn(helperFunction) : null;
23319
+ if (!helperSummary) {
23189
23320
  evidence.hasUnknownSource = true;
23190
23321
  return evidence;
23191
23322
  }
23192
- for (const returnedExpression of returnedExpressions) mergeEvidence(evidence, collectValueEvidence(analysis, returnedExpression, valueFrame, remainingCallFrames - 1, new Set(visitedBindings)));
23323
+ const argumentsForHelper = node.arguments ?? [];
23324
+ for (const parameterIndex of helperSummary.usedParameterIndices) {
23325
+ const argument = argumentsForHelper[parameterIndex];
23326
+ if (!argument) {
23327
+ evidence.hasUnknownSource = true;
23328
+ return evidence;
23329
+ }
23330
+ mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames - 1, new Set(visitedBindings)));
23331
+ }
23193
23332
  return evidence;
23194
23333
  }
23195
23334
  if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
@@ -23269,8 +23408,8 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
23269
23408
  }
23270
23409
  return false;
23271
23410
  };
23272
- const collectEffectStateWriteFacts = (analysis, effectNode) => {
23273
- const frames = collectBoundedEffectExecutionFrames(analysis, effectNode);
23411
+ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
23412
+ const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
23274
23413
  if (frames.length === 0) return [];
23275
23414
  const effectHasCleanup = hasCleanup(analysis, effectNode);
23276
23415
  const cleanupManagedStateDeclarators = /* @__PURE__ */ new Set();
@@ -23290,7 +23429,8 @@ const collectEffectStateWriteFacts = (analysis, effectNode) => {
23290
23429
  invocation: callExpression,
23291
23430
  isDeferred: frame.isDeferred,
23292
23431
  introducedBindings: collectIntroducedBindings(analysis, writtenValue),
23293
- substitutions: /* @__PURE__ */ new Map()
23432
+ substitutions: /* @__PURE__ */ new Map(),
23433
+ currentFilename
23294
23434
  };
23295
23435
  valueEvidence = emptyEvidence();
23296
23436
  const returnedExpressions = getReturnedExpressions(writtenValue);
@@ -23339,7 +23479,7 @@ const noAdjustStateOnPropChange = defineRule({
23339
23479
  const dependencyReferences = getEffectDepsRefs(analysis, node);
23340
23480
  if (!dependencyReferences) return;
23341
23481
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
23342
- for (const fact of collectEffectStateWriteFacts(analysis, node)) {
23482
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
23343
23483
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
23344
23484
  context.report({
23345
23485
  node: fact.callExpression,
@@ -24600,6 +24740,139 @@ const createRelativeImportSource = (filename, targetFilePath) => {
24600
24740
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
24601
24741
  };
24602
24742
  //#endregion
24743
+ //#region src/plugin/utils/is-barrel-index-module.ts
24744
+ const INDEX_MODULE_FILE_PATTERN = /^index\.(?:[cm]?[jt]sx?|mjs)$/;
24745
+ const BINDING_IMPORT_DECLARATION_PATTERN = /^\s*import\s+(type\s+)?(?!["'])([^;]*?)\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24746
+ const BARREL_REEXPORT_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?(?:\*(?:\s+as\s+([\w$]+))?|\{([\s\S]*?)\})\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24747
+ const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?\{([\s\S]*?)\}\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24748
+ const barrelIndexModuleInfoCache = /* @__PURE__ */ new Map();
24749
+ const isIndexModuleFilePath = (filePath) => INDEX_MODULE_FILE_PATTERN.test(path.basename(filePath));
24750
+ const createNonBarrelInfo = () => ({
24751
+ isBarrel: false,
24752
+ exportsByName: /* @__PURE__ */ new Map(),
24753
+ starExportSources: []
24754
+ });
24755
+ const addImportedBinding = (importedBindings, binding) => {
24756
+ importedBindings.set(binding.localName, {
24757
+ ...binding,
24758
+ didExport: false
24759
+ });
24760
+ };
24761
+ const collectNamedImportBindings = (namedSpecifiersText, source, declarationIsTypeOnly, importedBindings) => {
24762
+ for (const specifier of parseExportSpecifiers(namedSpecifiersText, declarationIsTypeOnly)) addImportedBinding(importedBindings, {
24763
+ localName: specifier.exportedName,
24764
+ importedName: specifier.localName,
24765
+ source,
24766
+ isTypeOnly: specifier.isTypeOnly
24767
+ });
24768
+ };
24769
+ const collectImportBindings = (importClause, source, declarationIsTypeOnly, importedBindings) => {
24770
+ const trimmedImportClause = importClause.trim();
24771
+ const namespaceMatch = trimmedImportClause.match(/(?:^|,\s*)\*\s+as\s+([\w$]+)/);
24772
+ if (namespaceMatch?.[1]) addImportedBinding(importedBindings, {
24773
+ localName: namespaceMatch[1],
24774
+ importedName: "*",
24775
+ source,
24776
+ isTypeOnly: declarationIsTypeOnly
24777
+ });
24778
+ const namedImportMatch = trimmedImportClause.match(/\{([\s\S]*?)\}/);
24779
+ if (namedImportMatch?.[1]) collectNamedImportBindings(namedImportMatch[1], source, declarationIsTypeOnly, importedBindings);
24780
+ const defaultImportName = trimmedImportClause.split(",")[0]?.trim();
24781
+ if (defaultImportName && !defaultImportName.startsWith("{") && !defaultImportName.startsWith("*")) addImportedBinding(importedBindings, {
24782
+ localName: defaultImportName,
24783
+ importedName: "default",
24784
+ source,
24785
+ isTypeOnly: declarationIsTypeOnly
24786
+ });
24787
+ };
24788
+ const replaceKnownDeclarations = (sourceText, importedBindings, exportsByName, starExportSources) => {
24789
+ let withoutKnownDeclarations = sourceText.replace(BINDING_IMPORT_DECLARATION_PATTERN, (_match, typeKeyword, importClause, source) => {
24790
+ collectImportBindings(importClause, source, Boolean(typeKeyword), importedBindings);
24791
+ return "";
24792
+ });
24793
+ withoutKnownDeclarations = withoutKnownDeclarations.replace(BARREL_REEXPORT_DECLARATION_PATTERN, (_match, typeKeyword, namespaceExportName, specifiersText, source) => {
24794
+ const isTypeOnly = Boolean(typeKeyword);
24795
+ if (namespaceExportName) {
24796
+ exportsByName.set(namespaceExportName, {
24797
+ exportedName: namespaceExportName,
24798
+ importedName: "*",
24799
+ source,
24800
+ isTypeOnly
24801
+ });
24802
+ return "";
24803
+ }
24804
+ if (specifiersText) {
24805
+ for (const specifier of parseExportSpecifiers(specifiersText, isTypeOnly)) exportsByName.set(specifier.exportedName, {
24806
+ exportedName: specifier.exportedName,
24807
+ importedName: specifier.localName,
24808
+ source,
24809
+ isTypeOnly: specifier.isTypeOnly
24810
+ });
24811
+ return "";
24812
+ }
24813
+ starExportSources.push(source);
24814
+ return "";
24815
+ });
24816
+ withoutKnownDeclarations = withoutKnownDeclarations.replace(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN, (_match, typeKeyword, specifiersText) => {
24817
+ for (const specifier of parseExportSpecifiers(specifiersText, Boolean(typeKeyword))) {
24818
+ const importedBinding = importedBindings.get(specifier.localName);
24819
+ if (!importedBinding) return _match;
24820
+ importedBinding.didExport = true;
24821
+ exportsByName.set(specifier.exportedName, {
24822
+ exportedName: specifier.exportedName,
24823
+ importedName: importedBinding.importedName,
24824
+ source: importedBinding.source,
24825
+ isTypeOnly: specifier.isTypeOnly || importedBinding.isTypeOnly
24826
+ });
24827
+ }
24828
+ return "";
24829
+ });
24830
+ return withoutKnownDeclarations;
24831
+ };
24832
+ const hasUnexportedRuntimeImport = (importedBindings) => {
24833
+ for (const binding of importedBindings.values()) if (!binding.isTypeOnly && !binding.didExport) return true;
24834
+ return false;
24835
+ };
24836
+ const classifyBarrelModule = (sourceText) => {
24837
+ const strippedSource = stripJsComments(sourceText).trim();
24838
+ if (!strippedSource) return createNonBarrelInfo();
24839
+ const importedBindings = /* @__PURE__ */ new Map();
24840
+ const exportsByName = /* @__PURE__ */ new Map();
24841
+ const starExportSources = [];
24842
+ if (replaceKnownDeclarations(strippedSource, importedBindings, exportsByName, starExportSources).trim() || hasUnexportedRuntimeImport(importedBindings)) return createNonBarrelInfo();
24843
+ return {
24844
+ isBarrel: exportsByName.size > 0 || starExportSources.length > 0,
24845
+ exportsByName,
24846
+ starExportSources
24847
+ };
24848
+ };
24849
+ const getBarrelIndexModuleInfo = (filePath) => {
24850
+ if (!isIndexModuleFilePath(filePath)) return createNonBarrelInfo();
24851
+ recordContentProbe(filePath);
24852
+ let fileStat;
24853
+ try {
24854
+ fileStat = fs.statSync(filePath);
24855
+ } catch {
24856
+ fileStat = null;
24857
+ }
24858
+ const cachedResult = barrelIndexModuleInfoCache.get(filePath);
24859
+ if (cachedResult !== void 0 && fileStat !== null && cachedResult.mtimeMs === fileStat.mtimeMs && cachedResult.size === fileStat.size) return cachedResult.moduleInfo;
24860
+ if (fileStat === null) return createNonBarrelInfo();
24861
+ let moduleInfo = createNonBarrelInfo();
24862
+ try {
24863
+ moduleInfo = classifyBarrelModule(fs.readFileSync(filePath, "utf8"));
24864
+ } catch {
24865
+ moduleInfo = createNonBarrelInfo();
24866
+ }
24867
+ barrelIndexModuleInfoCache.set(filePath, {
24868
+ mtimeMs: fileStat.mtimeMs,
24869
+ size: fileStat.size,
24870
+ moduleInfo
24871
+ });
24872
+ return moduleInfo;
24873
+ };
24874
+ const isBarrelIndexModule = (filePath) => getBarrelIndexModuleInfo(filePath).isBarrel;
24875
+ //#endregion
24603
24876
  //#region src/react-native-dependency-names.ts
24604
24877
  const EXPO_MANAGED_DEPENDENCY_NAMES = new Set([
24605
24878
  "expo",
@@ -24802,6 +25075,35 @@ const classifyReactNativeFileTarget = (context) => {
24802
25075
  };
24803
25076
  const isReactNativeFileActive = (context) => classifyReactNativeFileTarget(context) !== "web";
24804
25077
  //#endregion
25078
+ //#region src/plugin/utils/resolve-barrel-export-file-path.ts
25079
+ const getUniqueFilePath = (filePaths) => {
25080
+ const uniqueFilePaths = new Set(filePaths);
25081
+ if (uniqueFilePaths.size !== 1) return null;
25082
+ const [filePath] = uniqueFilePaths;
25083
+ return filePath ?? null;
25084
+ };
25085
+ const resolveStarExportFilePath = (barrelFilePath, exportedName, source, visitedFilePaths) => {
25086
+ const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, source);
25087
+ if (!resolvedTargetPath) return null;
25088
+ const nestedTargetPath = resolveBarrelExportFilePath(resolvedTargetPath, exportedName, new Set(visitedFilePaths));
25089
+ if (nestedTargetPath) return nestedTargetPath;
25090
+ return doesModuleExportName(resolvedTargetPath, exportedName) ? resolvedTargetPath : null;
25091
+ };
25092
+ const resolveBarrelExportFilePath = (barrelFilePath, exportedName, visitedFilePaths = /* @__PURE__ */ new Set()) => {
25093
+ if (visitedFilePaths.has(barrelFilePath)) return null;
25094
+ visitedFilePaths.add(barrelFilePath);
25095
+ const moduleInfo = getBarrelIndexModuleInfo(barrelFilePath);
25096
+ if (!moduleInfo.isBarrel) return null;
25097
+ const target = moduleInfo.exportsByName.get(exportedName);
25098
+ if (target) {
25099
+ const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, target.source);
25100
+ if (!resolvedTargetPath) return null;
25101
+ return resolveBarrelExportFilePath(resolvedTargetPath, target.importedName, visitedFilePaths) ?? resolvedTargetPath;
25102
+ }
25103
+ if (exportedName === "default") return null;
25104
+ return getUniqueFilePath(moduleInfo.starExportSources.map((source) => resolveStarExportFilePath(barrelFilePath, exportedName, source, visitedFilePaths)).filter((filePath) => Boolean(filePath)));
25105
+ };
25106
+ //#endregion
24805
25107
  //#region src/plugin/rules/bundle-size/no-barrel-import.ts
24806
25108
  const TYPE_DECLARATION_FILE_PATTERN = /\.d\.[cm]?ts$/;
24807
25109
  const SERVER_ONLY_FILE_PATTERN = /\.server\.[cm]?[jt]sx?$/;
@@ -26117,7 +26419,7 @@ const noDerivedState = defineRule({
26117
26419
  if (!isUseEffect(node)) return;
26118
26420
  const analysis = getProgramAnalysis(node);
26119
26421
  if (!analysis) return;
26120
- for (const fact of collectEffectStateWriteFacts(analysis, node)) {
26422
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
26121
26423
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
26122
26424
  const stateName = getStateName$1(fact.stateDeclarator);
26123
26425
  context.report({
@@ -26139,7 +26441,7 @@ const noDerivedStateEffect = defineRule({
26139
26441
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
26140
26442
  const analysis = getProgramAnalysis(node);
26141
26443
  if (!analysis) return;
26142
- if (!collectEffectStateWriteFacts(analysis, node).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
26444
+ if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
26143
26445
  context.report({
26144
26446
  node,
26145
26447
  message: "You pay an extra render for state you can derive from other values."
@@ -30467,7 +30769,7 @@ const noInitializeState = defineRule({
30467
30769
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
30468
30770
  const analysis = getProgramAnalysis(node);
30469
30771
  if (!analysis) return;
30470
- for (const fact of collectEffectStateWriteFacts(analysis, node)) {
30772
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
30471
30773
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
30472
30774
  const stateName = getStateName(fact.stateDeclarator);
30473
30775
  context.report({
@@ -58897,6 +59199,11 @@ const CROSS_FILE_RULE_IDS = new Set([
58897
59199
  "no-indeterminate-attribute",
58898
59200
  "no-locale-format-in-render",
58899
59201
  "no-match-media-in-state-initializer",
59202
+ "no-adjust-state-on-prop-change",
59203
+ "no-derived-state",
59204
+ "no-derived-state-effect",
59205
+ "no-event-handler",
59206
+ "no-initialize-state",
58900
59207
  "no-mutating-reducer-state",
58901
59208
  "no-unguarded-browser-global-in-render-or-hook-init",
58902
59209
  "prefer-dynamic-import",
@@ -58950,6 +59257,9 @@ const collectNextjsSearchParamsDependencies = ({ absoluteFilePath, staticImports
58950
59257
  if (hasAncestorSuspenseLayout(absoluteFilePath)) return;
58951
59258
  for (const entry of flattenImportEntries(staticImports)) resolveCrossFileFunctionExport(absoluteFilePath, entry.source, entry.exportedName);
58952
59259
  };
59260
+ const collectEffectValueHelperDependencies = ({ absoluteFilePath, staticImports }) => {
59261
+ for (const entry of flattenImportEntries(staticImports)) resolveCrossFileFunctionExport(absoluteFilePath, entry.source, entry.exportedName);
59262
+ };
58953
59263
  const collectMutatingReducerDependencies = ({ absoluteFilePath, sourceText, staticImports, program }) => {
58954
59264
  const namedUseReducerLocals = /* @__PURE__ */ new Set();
58955
59265
  const reactObjectLocals = /* @__PURE__ */ new Set();
@@ -59036,6 +59346,11 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
59036
59346
  ["no-indeterminate-attribute", collectNearestManifestDependencies],
59037
59347
  ["no-locale-format-in-render", collectNearestManifestDependencies],
59038
59348
  ["no-match-media-in-state-initializer", collectNearestManifestDependencies],
59349
+ ["no-adjust-state-on-prop-change", collectEffectValueHelperDependencies],
59350
+ ["no-derived-state", collectEffectValueHelperDependencies],
59351
+ ["no-derived-state-effect", collectEffectValueHelperDependencies],
59352
+ ["no-event-handler", collectEffectValueHelperDependencies],
59353
+ ["no-initialize-state", collectEffectValueHelperDependencies],
59039
59354
  ["no-mutating-reducer-state", collectMutatingReducerDependencies],
59040
59355
  ["no-unguarded-browser-global-in-render-or-hook-init", collectNearestManifestDependencies],
59041
59356
  ["prefer-dynamic-import", collectNearestManifestDependencies],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.4-dev.118f806",
3
+ "version": "0.7.4-dev.4a5d9bb",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",