oxlint-plugin-react-doctor 0.5.6-dev.937a7ca → 0.5.6-dev.93b796d

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) {
@@ -25419,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
25419
25428
  visit(root);
25420
25429
  return found;
25421
25430
  };
25422
- const classExtendsReactComponent$1 = (classNode) => {
25423
- const superClass = classNode.superClass;
25424
- if (!superClass) return false;
25425
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25426
- 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;
25427
- return false;
25428
- };
25429
25431
  const isReactClassComponent = (classNode) => {
25430
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
25431
25433
  return expressionContainsJsxOrCreateElement(classNode);
25432
25434
  };
25433
25435
  const findEnclosingComponent = (node) => {
@@ -25587,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
25587
25589
  create: (context) => {
25588
25590
  const settings = resolveSettings$8(context.settings);
25589
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
25590
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
25591
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
25592
25594
  if (isReturnOfMapCallback(candidateNode)) return;
25593
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -25608,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
25608
25610
  const inferredName = inferFunctionLikeName(node);
25609
25611
  const propInfo = isComponentDeclaredInProp(node);
25610
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
25611
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
25612
25614
  };
25613
25615
  return {
25614
25616
  FunctionDeclaration: checkFunctionLike,
@@ -25618,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
25618
25620
  if (!node.id) return;
25619
25621
  if (!isReactComponentName(node.id.name)) return;
25620
25622
  if (!isReactClassComponent(node)) return;
25621
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
25622
25624
  },
25623
25625
  ClassExpression(node) {
25624
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
25625
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
25626
25628
  if (!isReactClassComponent(node)) return;
25627
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
25628
25630
  },
25629
25631
  CallExpression(node) {
25630
25632
  if (!isHocCallee$1(node)) return;
25631
25633
  if (!hocCallContainsComponent(node)) return;
25632
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
25633
25635
  }
25634
25636
  };
25635
25637
  }
@@ -26069,13 +26071,6 @@ const skipTsExpression = (expression) => {
26069
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
26070
26072
  return expression;
26071
26073
  };
26072
- const classExtendsReactComponent = (classNode) => {
26073
- const superClass = classNode.superClass;
26074
- if (!superClass) return false;
26075
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
26076
- 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;
26077
- return false;
26078
- };
26079
26074
  const isReactCreateContext = (initializer) => {
26080
26075
  if (!initializer) return false;
26081
26076
  const expression = skipTsExpression(initializer);
@@ -26266,7 +26261,7 @@ const onlyExportComponents = defineRule({
26266
26261
  if (stripped.id) {
26267
26262
  const idNode = stripped.id;
26268
26263
  isExportedNodeIds.add(stripped);
26269
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
26270
26265
  else exports.push({
26271
26266
  kind: "non-component",
26272
26267
  reportNode: idNode
@@ -26326,7 +26321,7 @@ const onlyExportComponents = defineRule({
26326
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
26327
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
26328
26323
  isExportedNodeIds.add(declaration);
26329
- 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" });
26330
26325
  else exports.push({
26331
26326
  kind: "non-component",
26332
26327
  reportNode: declaration.id
@@ -42282,32 +42277,6 @@ const computeUnconditionalSet = (cfg) => {
42282
42277
  }
42283
42278
  return unconditional;
42284
42279
  };
42285
- const computeDominatesExit = (cfg) => {
42286
- const reachableToExit = /* @__PURE__ */ new Set();
42287
- const queue = [cfg.exit];
42288
- while (queue.length > 0) {
42289
- const block = queue.shift();
42290
- if (reachableToExit.has(block)) continue;
42291
- reachableToExit.add(block);
42292
- for (const edge of block.predecessors) queue.push(edge.from);
42293
- }
42294
- const dominatesExit = /* @__PURE__ */ new Set();
42295
- const visit = (block) => {
42296
- if (block === cfg.exit) return true;
42297
- if (dominatesExit.has(block)) return true;
42298
- if (block.successors.length === 0) return false;
42299
- dominatesExit.add(block);
42300
- let allReach = true;
42301
- for (const edge of block.successors) if (!visit(edge.to)) {
42302
- allReach = false;
42303
- break;
42304
- }
42305
- if (!allReach) dominatesExit.delete(block);
42306
- return allReach;
42307
- };
42308
- for (const block of cfg.blocks) visit(block);
42309
- return dominatesExit;
42310
- };
42311
42280
  const analyzeControlFlow = (program) => {
42312
42281
  nextBlockId = 0;
42313
42282
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -42315,8 +42284,7 @@ const analyzeControlFlow = (program) => {
42315
42284
  const cfg = buildFunctionCfg(functionNode, body);
42316
42285
  functionCfgs.set(functionNode, {
42317
42286
  cfg,
42318
- unconditionalSet: computeUnconditionalSet(cfg),
42319
- dominatesExitSet: computeDominatesExit(cfg)
42287
+ unconditionalSet: computeUnconditionalSet(cfg)
42320
42288
  });
42321
42289
  };
42322
42290
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -42359,20 +42327,10 @@ const analyzeControlFlow = (program) => {
42359
42327
  if (!block) return true;
42360
42328
  return entry.unconditionalSet.has(block);
42361
42329
  };
42362
- const dominatesExit = (node) => {
42363
- const owner = enclosingFunction(node);
42364
- if (!owner) return true;
42365
- const entry = functionCfgs.get(owner);
42366
- if (!entry) return true;
42367
- const block = entry.cfg.blockOf(node);
42368
- if (!block) return true;
42369
- return entry.dominatesExitSet.has(block);
42370
- };
42371
42330
  return {
42372
42331
  cfgFor,
42373
42332
  enclosingFunction,
42374
- isUnconditionalFromEntry,
42375
- dominatesExit
42333
+ isUnconditionalFromEntry
42376
42334
  };
42377
42335
  };
42378
42336
  //#endregion
@@ -42397,8 +42355,7 @@ const buildFallbackScopes = () => ({
42397
42355
  const FALLBACK_CFG = {
42398
42356
  cfgFor: () => null,
42399
42357
  enclosingFunction: () => null,
42400
- isUnconditionalFromEntry: () => false,
42401
- dominatesExit: () => false
42358
+ isUnconditionalFromEntry: () => false
42402
42359
  };
42403
42360
  const wrapWithSemanticContext = (rule) => ({
42404
42361
  ...rule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.6-dev.937a7ca",
3
+ "version": "0.5.6-dev.93b796d",
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",