oxlint-plugin-react-doctor 0.6.1-dev.f07ee37 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +91 -94
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -212,16 +212,8 @@ const sliceBelowSourceRoot = (filename) => {
212
212
  if (cutAt < 0) return filename;
213
213
  return filename.slice(cutAt);
214
214
  };
215
- let lastFilename;
216
- let lastResult = false;
217
215
  const isTestlikeFilename = (rawFilename) => {
218
216
  if (!rawFilename) return false;
219
- if (rawFilename === lastFilename) return lastResult;
220
- lastFilename = rawFilename;
221
- lastResult = computeIsTestlikeFilename(rawFilename);
222
- return lastResult;
223
- };
224
- const computeIsTestlikeFilename = (rawFilename) => {
225
217
  const filename = rawFilename.replaceAll("\\", "/");
226
218
  const lastSlash = filename.lastIndexOf("/");
227
219
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -991,8 +983,16 @@ const buildBindingIndex = (root) => {
991
983
  }
992
984
  }
993
985
  }
986
+ const nodeRecord = node;
987
+ for (const key of Object.keys(nodeRecord)) {
988
+ if (key === "parent") continue;
989
+ const child = nodeRecord[key];
990
+ if (Array.isArray(child)) {
991
+ for (const item of child) if (isAstNode(item)) visit(item);
992
+ } else if (isAstNode(child)) visit(child);
993
+ }
994
994
  };
995
- walkAst(root, visit);
995
+ visit(root);
996
996
  return out;
997
997
  };
998
998
  const programRootCache = /* @__PURE__ */ new WeakMap();
@@ -1423,40 +1423,49 @@ const getElementType = (openingElement, settings) => {
1423
1423
  //#region src/plugin/utils/find-import-source-for-name.ts
1424
1424
  const collectFromProgram = (programRoot) => {
1425
1425
  const lookup = /* @__PURE__ */ new Map();
1426
- const bodyStatements = programRoot.body ?? [];
1427
- for (const node of bodyStatements) {
1428
- if (node.type !== "ImportDeclaration" || !("source" in node) || !node.source) continue;
1429
- const source = node.source.value;
1430
- if (typeof source !== "string") continue;
1431
- if (!("specifiers" in node) || !Array.isArray(node.specifiers)) continue;
1432
- for (const specifier of node.specifiers) {
1433
- if (!("local" in specifier) || !specifier.local) continue;
1434
- const local = specifier.local;
1435
- if (typeof local.name !== "string") continue;
1436
- if (specifier.type === "ImportDefaultSpecifier") lookup.set(local.name, {
1437
- source,
1438
- imported: null,
1439
- isDefault: true,
1440
- isNamespace: false
1441
- });
1442
- else if (specifier.type === "ImportNamespaceSpecifier") lookup.set(local.name, {
1443
- source,
1444
- imported: null,
1445
- isDefault: false,
1446
- isNamespace: true
1447
- });
1448
- else if (specifier.type === "ImportSpecifier") {
1449
- const importedNode = specifier.imported;
1450
- const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
1451
- lookup.set(local.name, {
1426
+ const visit = (node) => {
1427
+ if (node.type === "ImportDeclaration" && "source" in node && node.source) {
1428
+ const source = node.source.value;
1429
+ if (typeof source !== "string") return;
1430
+ if ("specifiers" in node && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
1431
+ if (!("local" in specifier) || !specifier.local) continue;
1432
+ const local = specifier.local;
1433
+ if (typeof local.name !== "string") continue;
1434
+ if (specifier.type === "ImportDefaultSpecifier") lookup.set(local.name, {
1452
1435
  source,
1453
- imported: importedName,
1454
- isDefault: false,
1436
+ imported: null,
1437
+ isDefault: true,
1455
1438
  isNamespace: false
1456
1439
  });
1440
+ else if (specifier.type === "ImportNamespaceSpecifier") lookup.set(local.name, {
1441
+ source,
1442
+ imported: null,
1443
+ isDefault: false,
1444
+ isNamespace: true
1445
+ });
1446
+ else if (specifier.type === "ImportSpecifier") {
1447
+ const importedNode = specifier.imported;
1448
+ const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
1449
+ lookup.set(local.name, {
1450
+ source,
1451
+ imported: importedName,
1452
+ isDefault: false,
1453
+ isNamespace: false
1454
+ });
1455
+ }
1457
1456
  }
1457
+ return;
1458
1458
  }
1459
- }
1459
+ const nodeRecord = node;
1460
+ for (const key of Object.keys(nodeRecord)) {
1461
+ if (key === "parent") continue;
1462
+ const child = nodeRecord[key];
1463
+ if (Array.isArray(child)) {
1464
+ for (const item of child) if (isAstNode(item)) visit(item);
1465
+ } else if (isAstNode(child)) visit(child);
1466
+ }
1467
+ };
1468
+ visit(programRoot);
1460
1469
  return lookup;
1461
1470
  };
1462
1471
  const importLookupCache = /* @__PURE__ */ new WeakMap();
@@ -1470,12 +1479,6 @@ const getImportLookup = (node) => {
1470
1479
  }
1471
1480
  return cached;
1472
1481
  };
1473
- const hasImportFromModules = (contextNode, moduleSources) => {
1474
- const lookup = getImportLookup(contextNode);
1475
- if (!lookup) return false;
1476
- for (const info of lookup.values()) if (moduleSources.includes(info.source)) return true;
1477
- return false;
1478
- };
1479
1482
  const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) => {
1480
1483
  const lookup = getImportLookup(contextNode);
1481
1484
  if (!lookup) return false;
@@ -1628,7 +1631,6 @@ const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
1628
1631
  //#region src/plugin/utils/is-generated-image-render-context.ts
1629
1632
  const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
1630
1633
  const SATORI_MODULE = "satori";
1631
- const GENERATED_IMAGE_MODULES = [...IMAGE_RESPONSE_MODULES, SATORI_MODULE];
1632
1634
  const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
1633
1635
  const isGeneratedImageRenderFilename = (rawFilename) => {
1634
1636
  if (!rawFilename) return false;
@@ -1772,7 +1774,7 @@ const collectGeneratedImageJsxNodes = (programRoot) => {
1772
1774
  const cached = generatedImageJsxCache.get(programRoot);
1773
1775
  if (cached) return cached;
1774
1776
  const generatedImageJsxNodes = /* @__PURE__ */ new WeakSet();
1775
- if (hasImportFromModules(programRoot, GENERATED_IMAGE_MODULES)) walkAst(programRoot, (descendantNode) => {
1777
+ walkAst(programRoot, (descendantNode) => {
1776
1778
  if (!isGeneratedImageRendererCall(descendantNode)) return;
1777
1779
  for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, generatedImageJsxNodes, /* @__PURE__ */ new Set());
1778
1780
  });
@@ -3965,11 +3967,9 @@ const collectScopedReferenceIdentifierNames = (node, into, shadowed) => {
3965
3967
  return;
3966
3968
  }
3967
3969
  if (typeof node.type === "string" && node.type.startsWith("TS")) return;
3968
- const nodeRecord = node;
3969
- for (const key of Object.keys(nodeRecord)) {
3970
+ for (const [key, child] of Object.entries(node)) {
3970
3971
  if (key === "parent") continue;
3971
3972
  if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
3972
- const child = nodeRecord[key];
3973
3973
  if (Array.isArray(child)) {
3974
3974
  for (const item of child) if (isAstNode(item)) collectScopedReferenceIdentifierNames(item, into, shadowed);
3975
3975
  } else if (isAstNode(child)) collectScopedReferenceIdentifierNames(child, into, shadowed);
@@ -10439,15 +10439,10 @@ const jsCombineIterations = defineRule({
10439
10439
  severity: "warn",
10440
10440
  recommendation: "Combine `.map().filter()` style chains into one pass with `.reduce()` or a `for...of` loop, so you only loop over the list once",
10441
10441
  create: (context) => {
10442
- let programNode = null;
10443
- let generatorNamesInFile = null;
10444
- const getGeneratorNamesInFile = () => {
10445
- generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
10446
- return generatorNamesInFile;
10447
- };
10442
+ let generatorNamesInFile = /* @__PURE__ */ new Set();
10448
10443
  return {
10449
- Program(node) {
10450
- programNode = node;
10444
+ Program(programNode) {
10445
+ generatorNamesInFile = collectGeneratorNames(programNode);
10451
10446
  },
10452
10447
  CallExpression(node) {
10453
10448
  if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
@@ -10469,7 +10464,7 @@ const jsCombineIterations = defineRule({
10469
10464
  if (isNullFilteringPredicate(filterArgument)) return;
10470
10465
  if (isTypePredicateArrow(filterArgument)) return;
10471
10466
  }
10472
- if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
10467
+ if (isReceiverChainIteratorRooted(innerCall.callee.object, generatorNamesInFile)) return;
10473
10468
  if (isSmallLiteralArrayRootedChain(innerCall.callee.object)) return;
10474
10469
  if (isStringSplitRootedChain(innerCall.callee.object)) return;
10475
10470
  context.report({
@@ -29526,18 +29521,21 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
29526
29521
  reportNode
29527
29522
  };
29528
29523
  };
29529
- const collectRelevantNodes = (programRoot) => {
29530
- const exportNodes = [];
29531
- const componentCandidates = [];
29532
- walkAst(programRoot, (child) => {
29533
- const childType = child.type;
29534
- if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
29535
- else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator") componentCandidates.push(child);
29536
- });
29537
- return {
29538
- exportNodes,
29539
- componentCandidates
29524
+ const collectAllNodes = (programRoot) => {
29525
+ const out = [];
29526
+ const visit = (node) => {
29527
+ out.push(node);
29528
+ const record = node;
29529
+ for (const key of Object.keys(record)) {
29530
+ if (key === "parent") continue;
29531
+ const child = record[key];
29532
+ if (Array.isArray(child)) {
29533
+ for (const item of child) if (isAstNode(item)) visit(item);
29534
+ } else if (isAstNode(child)) visit(child);
29535
+ }
29540
29536
  };
29537
+ visit(programRoot);
29538
+ return out;
29541
29539
  };
29542
29540
  const isEntryPointFile = (filename) => {
29543
29541
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
@@ -29584,13 +29582,13 @@ const onlyExportComponents = defineRule({
29584
29582
  };
29585
29583
  return { Program(node) {
29586
29584
  if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
29587
- const { exportNodes, componentCandidates } = collectRelevantNodes(node);
29585
+ const allNodes = collectAllNodes(node);
29588
29586
  const exports = [];
29589
29587
  let hasReactExport = false;
29590
29588
  let hasAnyExports = false;
29591
29589
  const localComponents = [];
29592
29590
  const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
29593
- for (const child of exportNodes) {
29591
+ for (const child of allNodes) {
29594
29592
  if (isNodeOfType(child, "ExportAllDeclaration")) {
29595
29593
  if (child.exportKind === "type") continue;
29596
29594
  hasAnyExports = true;
@@ -29726,7 +29724,7 @@ const onlyExportComponents = defineRule({
29726
29724
  }
29727
29725
  return false;
29728
29726
  };
29729
- for (const child of componentCandidates) {
29727
+ for (const child of allNodes) {
29730
29728
  if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
29731
29729
  if (isReactComponentName(child.id.name) && !isInsideExport(child)) localComponents.push(child.id);
29732
29730
  }
@@ -46843,29 +46841,23 @@ const FALLBACK_CFG = {
46843
46841
  enclosingFunction: () => null,
46844
46842
  isUnconditionalFromEntry: () => false
46845
46843
  };
46846
- const scopesByProgram = /* @__PURE__ */ new WeakMap();
46847
- const cfgByProgram = /* @__PURE__ */ new WeakMap();
46848
46844
  const wrapWithSemanticContext = (rule) => ({
46849
46845
  ...rule,
46850
46846
  create: (baseContext) => {
46851
46847
  let programRoot = null;
46848
+ let cachedScopes = null;
46849
+ let cachedCfg = null;
46852
46850
  const getScopes = () => {
46851
+ if (cachedScopes) return cachedScopes;
46853
46852
  if (!programRoot) return buildFallbackScopes();
46854
- let scopes = scopesByProgram.get(programRoot);
46855
- if (!scopes) {
46856
- scopes = analyzeScopes(programRoot);
46857
- scopesByProgram.set(programRoot, scopes);
46858
- }
46859
- return scopes;
46853
+ cachedScopes = analyzeScopes(programRoot);
46854
+ return cachedScopes;
46860
46855
  };
46861
46856
  const getCfg = () => {
46857
+ if (cachedCfg) return cachedCfg;
46862
46858
  if (!programRoot) return FALLBACK_CFG;
46863
- let cfg = cfgByProgram.get(programRoot);
46864
- if (!cfg) {
46865
- cfg = analyzeControlFlow(programRoot);
46866
- cfgByProgram.set(programRoot, cfg);
46867
- }
46868
- return cfg;
46859
+ cachedCfg = analyzeControlFlow(programRoot);
46860
+ return cachedCfg;
46869
46861
  };
46870
46862
  const enrichedContext = {
46871
46863
  report: baseContext.report,
@@ -46880,18 +46872,23 @@ const wrapWithSemanticContext = (rule) => ({
46880
46872
  return getCfg();
46881
46873
  }
46882
46874
  };
46875
+ const captureRootIfNeeded = (node) => {
46876
+ if (programRoot) return;
46877
+ programRoot = findProgramRoot(node);
46878
+ };
46883
46879
  const visitors = rule.create(enrichedContext);
46884
- const passthroughVisitors = {};
46880
+ const wrappedVisitors = {};
46885
46881
  for (const [nodeType, handler] of Object.entries(visitors)) {
46886
46882
  if (typeof handler !== "function") continue;
46887
- passthroughVisitors[nodeType] = handler;
46883
+ wrappedVisitors[nodeType] = ((node) => {
46884
+ captureRootIfNeeded(node);
46885
+ handler(node);
46886
+ });
46888
46887
  }
46889
- const innerProgramHandler = passthroughVisitors.Program;
46890
- passthroughVisitors.Program = ((node) => {
46891
- programRoot = node;
46892
- if (innerProgramHandler) innerProgramHandler(node);
46893
- });
46894
- return passthroughVisitors;
46888
+ if (!visitors.Program) wrappedVisitors.Program = (node) => {
46889
+ captureRootIfNeeded(node);
46890
+ };
46891
+ return wrappedVisitors;
46895
46892
  }
46896
46893
  });
46897
46894
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.6.1-dev.f07ee37",
3
+ "version": "0.6.1",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",