oxlint-plugin-react-doctor 0.5.6-dev.8057497 → 0.5.6-dev.869f220

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/dist/index.d.ts CHANGED
@@ -36,7 +36,6 @@ interface ControlFlowAnalysis {
36
36
  readonly cfgFor: (functionLike: EsTreeNode) => FunctionCfg | null;
37
37
  readonly enclosingFunction: (node: EsTreeNode) => EsTreeNode | null;
38
38
  readonly isUnconditionalFromEntry: (node: EsTreeNode) => boolean;
39
- readonly dominatesExit: (node: EsTreeNode) => boolean;
40
39
  }
41
40
  //#endregion
42
41
  //#region src/plugin/semantic/scope-analysis.d.ts
package/dist/index.js CHANGED
@@ -3019,7 +3019,7 @@ const ABSTRACT_ROLES = new Set([
3019
3019
  "widget",
3020
3020
  "window"
3021
3021
  ]);
3022
- const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
3022
+ const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
3023
3023
  //#endregion
3024
3024
  //#region src/plugin/rules/a11y/aria-role.ts
3025
3025
  const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
@@ -4710,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
4710
4710
  }
4711
4711
  });
4712
4712
  //#endregion
4713
+ //#region src/plugin/utils/is-presentation-role.ts
4714
+ const isPresentationRole = (openingElement) => {
4715
+ const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
4716
+ if (!roleAttribute) return false;
4717
+ const value = getJsxPropStringValue(roleAttribute);
4718
+ return value !== null && PRESENTATION_ROLES$1.has(value);
4719
+ };
4720
+ //#endregion
4713
4721
  //#region src/plugin/utils/is-pure-event-blocker-handler.ts
4714
4722
  const BLOCKER_METHOD_NAMES = new Set([
4715
4723
  "stopPropagation",
@@ -4747,7 +4755,6 @@ const isPureEventBlockerHandler = (attribute) => {
4747
4755
  };
4748
4756
  //#endregion
4749
4757
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4750
- const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
4751
4758
  const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4752
4759
  const KEY_HANDLERS = [
4753
4760
  "onKeyUp",
@@ -4772,11 +4779,7 @@ const clickEventsHaveKeyEvents = defineRule({
4772
4779
  if (!onClick) return;
4773
4780
  if (isPureEventBlockerHandler(onClick)) return;
4774
4781
  if (isHiddenFromScreenReader(node, context.settings)) return;
4775
- const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
4776
- if (roleAttribute) {
4777
- const roleValue = getJsxPropStringValue(roleAttribute);
4778
- if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
4779
- }
4782
+ if (isPresentationRole(node)) return;
4780
4783
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4781
4784
  context.report({
4782
4785
  node: node.name,
@@ -5747,7 +5750,7 @@ const displayName = defineRule({
5747
5750
  category: "Architecture",
5748
5751
  create: (context) => {
5749
5752
  const settings = resolveSettings$44(context.settings);
5750
- const ignoreNamed = settings.ignoreTranspilerName ? false : true;
5753
+ const ignoreNamed = !settings.ignoreTranspilerName;
5751
5754
  const reportAt = (node) => {
5752
5755
  context.report({
5753
5756
  node,
@@ -8170,7 +8173,6 @@ const htmlHasLang = defineRule({
8170
8173
  return { JSXOpeningElement(node) {
8171
8174
  const tag = getElementType(node, context.settings);
8172
8175
  if (!tagSet.has(tag)) return;
8173
- const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
8174
8176
  const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
8175
8177
  if (!lang) {
8176
8178
  context.report({
@@ -8179,16 +8181,8 @@ const htmlHasLang = defineRule({
8179
8181
  });
8180
8182
  return;
8181
8183
  }
8182
- const verdict = evaluateLang(lang.value);
8183
- if (verdict === "missing" || verdict === "empty") {
8184
- context.report({
8185
- node: lang,
8186
- message: MESSAGE$50
8187
- });
8188
- return;
8189
- }
8190
- if (hasSpread && !lang) context.report({
8191
- node: node.name,
8184
+ if (evaluateLang(lang.value) === "empty") context.report({
8185
+ node: lang,
8192
8186
  message: MESSAGE$50
8193
8187
  });
8194
8188
  } };
@@ -8902,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
8902
8896
  //#region src/plugin/utils/is-non-interactive-role.ts
8903
8897
  const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
8904
8898
  //#endregion
8905
- //#region src/plugin/utils/is-presentation-role.ts
8906
- const isPresentationRole = (openingElement) => {
8907
- const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
8908
- if (!roleAttribute) return false;
8909
- const value = getJsxPropStringValue(roleAttribute);
8910
- return value !== null && PRESENTATION_ROLES$2.has(value);
8911
- };
8912
- //#endregion
8913
8899
  //#region src/plugin/rules/a11y/interactive-supports-focus.ts
8914
8900
  const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
8915
8901
  const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
@@ -10684,6 +10670,24 @@ const hasJsxKeyAttribute = (openingElement) => {
10684
10670
  return false;
10685
10671
  };
10686
10672
  //#endregion
10673
+ //#region src/plugin/utils/is-non-children-jsx-attribute-value.ts
10674
+ const ascendThroughJsxValueWrappers = (node) => {
10675
+ let current = node;
10676
+ while (current.parent) {
10677
+ const parent = current.parent;
10678
+ if (!(isNodeOfType(parent, "ChainExpression") || isNodeOfType(parent, "TSAsExpression") || isNodeOfType(parent, "TSSatisfiesExpression") || isNodeOfType(parent, "TSNonNullExpression") || isNodeOfType(parent, "LogicalExpression") || isNodeOfType(parent, "ConditionalExpression") && parent.test !== current)) break;
10679
+ current = parent;
10680
+ }
10681
+ return current;
10682
+ };
10683
+ const isNonChildrenJsxAttributeValue = (node) => {
10684
+ const container = ascendThroughJsxValueWrappers(node).parent;
10685
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
10686
+ const attribute = container.parent;
10687
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) return false;
10688
+ return getJsxAttributeName(attribute.name) !== "children";
10689
+ };
10690
+ //#endregion
10687
10691
  //#region src/plugin/rules/react-builtins/jsx-key.ts
10688
10692
  const ITERATOR_METHOD_NAMES = new Set([
10689
10693
  "map",
@@ -10722,6 +10726,7 @@ const findEnclosingIteratorContext = (jsxNode) => {
10722
10726
  const arrayParent = parent.parent;
10723
10727
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
10724
10728
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
10729
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10725
10730
  return { kind: "array" };
10726
10731
  } else if (isNodeOfType(parent, "CallExpression")) {
10727
10732
  const callee = parent.callee;
@@ -10734,10 +10739,13 @@ const findEnclosingIteratorContext = (jsxNode) => {
10734
10739
  if (!targetArg) return null;
10735
10740
  let walker = current;
10736
10741
  while (walker && walker !== parent) {
10737
- if (walker === targetArg) return {
10738
- kind: "iterator",
10739
- callExpression: parent
10740
- };
10742
+ if (walker === targetArg) {
10743
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10744
+ return {
10745
+ kind: "iterator",
10746
+ callExpression: parent
10747
+ };
10748
+ }
10741
10749
  walker = walker.parent ?? null;
10742
10750
  }
10743
10751
  return null;
@@ -17430,6 +17438,7 @@ const noDanger = defineRule({
17430
17438
  title: "Raw HTML injection can run unsafe markup",
17431
17439
  severity: "warn",
17432
17440
  category: "Security",
17441
+ defaultEnabled: false,
17433
17442
  recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
17434
17443
  create: (context) => ({
17435
17444
  JSXOpeningElement(node) {
@@ -20190,15 +20199,20 @@ const noInlineExhaustiveStyle = defineRule({
20190
20199
  severity: "warn",
20191
20200
  tags: ["test-noise", "react-jsx-only"],
20192
20201
  recommendation: "Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.",
20193
- create: (context) => ({ JSXAttribute(node) {
20194
- const expression = getInlineStyleExpression(node);
20195
- if (!expression) return;
20196
- const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20197
- if (propertyCount >= 8) context.report({
20198
- node: expression,
20199
- message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20200
- });
20201
- } })
20202
+ create: (context) => {
20203
+ if (isGeneratedImageRenderContext(context)) return {};
20204
+ return { JSXAttribute(node) {
20205
+ const expression = getInlineStyleExpression(node);
20206
+ if (!expression) return;
20207
+ const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20208
+ if (propertyCount < 8) return;
20209
+ if (isGeneratedImageRenderContext(context, node.parent ?? void 0)) return;
20210
+ context.report({
20211
+ node: expression,
20212
+ message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20213
+ });
20214
+ } };
20215
+ }
20202
20216
  });
20203
20217
  //#endregion
20204
20218
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
@@ -25414,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
25414
25428
  visit(root);
25415
25429
  return found;
25416
25430
  };
25417
- const classExtendsReactComponent$1 = (classNode) => {
25418
- const superClass = classNode.superClass;
25419
- if (!superClass) return false;
25420
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25421
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
25422
- return false;
25423
- };
25424
25431
  const isReactClassComponent = (classNode) => {
25425
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
25426
25433
  return expressionContainsJsxOrCreateElement(classNode);
25427
25434
  };
25428
25435
  const findEnclosingComponent = (node) => {
@@ -25582,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
25582
25589
  create: (context) => {
25583
25590
  const settings = resolveSettings$8(context.settings);
25584
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
25585
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
25586
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
25587
25594
  if (isReturnOfMapCallback(candidateNode)) return;
25588
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -25603,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
25603
25610
  const inferredName = inferFunctionLikeName(node);
25604
25611
  const propInfo = isComponentDeclaredInProp(node);
25605
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
25606
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
25607
25614
  };
25608
25615
  return {
25609
25616
  FunctionDeclaration: checkFunctionLike,
@@ -25613,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
25613
25620
  if (!node.id) return;
25614
25621
  if (!isReactComponentName(node.id.name)) return;
25615
25622
  if (!isReactClassComponent(node)) return;
25616
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
25617
25624
  },
25618
25625
  ClassExpression(node) {
25619
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
25620
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
25621
25628
  if (!isReactClassComponent(node)) return;
25622
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
25623
25630
  },
25624
25631
  CallExpression(node) {
25625
25632
  if (!isHocCallee$1(node)) return;
25626
25633
  if (!hocCallContainsComponent(node)) return;
25627
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
25628
25635
  }
25629
25636
  };
25630
25637
  }
@@ -26064,13 +26071,6 @@ const skipTsExpression = (expression) => {
26064
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
26065
26072
  return expression;
26066
26073
  };
26067
- const classExtendsReactComponent = (classNode) => {
26068
- const superClass = classNode.superClass;
26069
- if (!superClass) return false;
26070
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
26071
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
26072
- return false;
26073
- };
26074
26074
  const isReactCreateContext = (initializer) => {
26075
26075
  if (!initializer) return false;
26076
26076
  const expression = skipTsExpression(initializer);
@@ -26261,7 +26261,7 @@ const onlyExportComponents = defineRule({
26261
26261
  if (stripped.id) {
26262
26262
  const idNode = stripped.id;
26263
26263
  isExportedNodeIds.add(stripped);
26264
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
26265
26265
  else exports.push({
26266
26266
  kind: "non-component",
26267
26267
  reportNode: idNode
@@ -26321,7 +26321,7 @@ const onlyExportComponents = defineRule({
26321
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
26322
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
26323
26323
  isExportedNodeIds.add(declaration);
26324
- if (isReactComponentName(declaration.id.name) && classExtendsReactComponent(declaration)) exports.push({ kind: "react-component" });
26324
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
26325
26325
  else exports.push({
26326
26326
  kind: "non-component",
26327
26327
  reportNode: declaration.id
@@ -35425,6 +35425,7 @@ const serverFetchWithoutRevalidate = defineRule({
35425
35425
  CallExpression(node) {
35426
35426
  if (!isServerSideFile) return;
35427
35427
  if (!isFetchCall(node)) return;
35428
+ if (isMutatingFetchCall(node)) return;
35428
35429
  const optionsArg = node.arguments?.[1];
35429
35430
  if (optionsArg && objectExpressionHasNextRevalidate(optionsArg)) return;
35430
35431
  const urlArg = node.arguments?.[0];
@@ -42277,32 +42278,6 @@ const computeUnconditionalSet = (cfg) => {
42277
42278
  }
42278
42279
  return unconditional;
42279
42280
  };
42280
- const computeDominatesExit = (cfg) => {
42281
- const reachableToExit = /* @__PURE__ */ new Set();
42282
- const queue = [cfg.exit];
42283
- while (queue.length > 0) {
42284
- const block = queue.shift();
42285
- if (reachableToExit.has(block)) continue;
42286
- reachableToExit.add(block);
42287
- for (const edge of block.predecessors) queue.push(edge.from);
42288
- }
42289
- const dominatesExit = /* @__PURE__ */ new Set();
42290
- const visit = (block) => {
42291
- if (block === cfg.exit) return true;
42292
- if (dominatesExit.has(block)) return true;
42293
- if (block.successors.length === 0) return false;
42294
- dominatesExit.add(block);
42295
- let allReach = true;
42296
- for (const edge of block.successors) if (!visit(edge.to)) {
42297
- allReach = false;
42298
- break;
42299
- }
42300
- if (!allReach) dominatesExit.delete(block);
42301
- return allReach;
42302
- };
42303
- for (const block of cfg.blocks) visit(block);
42304
- return dominatesExit;
42305
- };
42306
42281
  const analyzeControlFlow = (program) => {
42307
42282
  nextBlockId = 0;
42308
42283
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -42310,8 +42285,7 @@ const analyzeControlFlow = (program) => {
42310
42285
  const cfg = buildFunctionCfg(functionNode, body);
42311
42286
  functionCfgs.set(functionNode, {
42312
42287
  cfg,
42313
- unconditionalSet: computeUnconditionalSet(cfg),
42314
- dominatesExitSet: computeDominatesExit(cfg)
42288
+ unconditionalSet: computeUnconditionalSet(cfg)
42315
42289
  });
42316
42290
  };
42317
42291
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -42354,20 +42328,10 @@ const analyzeControlFlow = (program) => {
42354
42328
  if (!block) return true;
42355
42329
  return entry.unconditionalSet.has(block);
42356
42330
  };
42357
- const dominatesExit = (node) => {
42358
- const owner = enclosingFunction(node);
42359
- if (!owner) return true;
42360
- const entry = functionCfgs.get(owner);
42361
- if (!entry) return true;
42362
- const block = entry.cfg.blockOf(node);
42363
- if (!block) return true;
42364
- return entry.dominatesExitSet.has(block);
42365
- };
42366
42331
  return {
42367
42332
  cfgFor,
42368
42333
  enclosingFunction,
42369
- isUnconditionalFromEntry,
42370
- dominatesExit
42334
+ isUnconditionalFromEntry
42371
42335
  };
42372
42336
  };
42373
42337
  //#endregion
@@ -42392,8 +42356,7 @@ const buildFallbackScopes = () => ({
42392
42356
  const FALLBACK_CFG = {
42393
42357
  cfgFor: () => null,
42394
42358
  enclosingFunction: () => null,
42395
- isUnconditionalFromEntry: () => false,
42396
- dominatesExit: () => false
42359
+ isUnconditionalFromEntry: () => false
42397
42360
  };
42398
42361
  const wrapWithSemanticContext = (rule) => ({
42399
42362
  ...rule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.6-dev.8057497",
3
+ "version": "0.5.6-dev.869f220",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",