oxlint-plugin-react-doctor 0.5.6-dev.b8170f8 → 0.5.6-dev.ed0258c

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}\`.`;
@@ -17452,6 +17438,7 @@ const noDanger = defineRule({
17452
17438
  title: "Raw HTML injection can run unsafe markup",
17453
17439
  severity: "warn",
17454
17440
  category: "Security",
17441
+ defaultEnabled: false,
17455
17442
  recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
17456
17443
  create: (context) => ({
17457
17444
  JSXOpeningElement(node) {
@@ -25441,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
25441
25428
  visit(root);
25442
25429
  return found;
25443
25430
  };
25444
- const classExtendsReactComponent$1 = (classNode) => {
25445
- const superClass = classNode.superClass;
25446
- if (!superClass) return false;
25447
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25448
- 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;
25449
- return false;
25450
- };
25451
25431
  const isReactClassComponent = (classNode) => {
25452
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
25453
25433
  return expressionContainsJsxOrCreateElement(classNode);
25454
25434
  };
25455
25435
  const findEnclosingComponent = (node) => {
@@ -25609,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
25609
25589
  create: (context) => {
25610
25590
  const settings = resolveSettings$8(context.settings);
25611
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
25612
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
25613
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
25614
25594
  if (isReturnOfMapCallback(candidateNode)) return;
25615
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -25630,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
25630
25610
  const inferredName = inferFunctionLikeName(node);
25631
25611
  const propInfo = isComponentDeclaredInProp(node);
25632
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
25633
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
25634
25614
  };
25635
25615
  return {
25636
25616
  FunctionDeclaration: checkFunctionLike,
@@ -25640,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
25640
25620
  if (!node.id) return;
25641
25621
  if (!isReactComponentName(node.id.name)) return;
25642
25622
  if (!isReactClassComponent(node)) return;
25643
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
25644
25624
  },
25645
25625
  ClassExpression(node) {
25646
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
25647
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
25648
25628
  if (!isReactClassComponent(node)) return;
25649
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
25650
25630
  },
25651
25631
  CallExpression(node) {
25652
25632
  if (!isHocCallee$1(node)) return;
25653
25633
  if (!hocCallContainsComponent(node)) return;
25654
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
25655
25635
  }
25656
25636
  };
25657
25637
  }
@@ -26091,13 +26071,6 @@ const skipTsExpression = (expression) => {
26091
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
26092
26072
  return expression;
26093
26073
  };
26094
- const classExtendsReactComponent = (classNode) => {
26095
- const superClass = classNode.superClass;
26096
- if (!superClass) return false;
26097
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
26098
- 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;
26099
- return false;
26100
- };
26101
26074
  const isReactCreateContext = (initializer) => {
26102
26075
  if (!initializer) return false;
26103
26076
  const expression = skipTsExpression(initializer);
@@ -26288,7 +26261,7 @@ const onlyExportComponents = defineRule({
26288
26261
  if (stripped.id) {
26289
26262
  const idNode = stripped.id;
26290
26263
  isExportedNodeIds.add(stripped);
26291
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
26292
26265
  else exports.push({
26293
26266
  kind: "non-component",
26294
26267
  reportNode: idNode
@@ -26348,7 +26321,7 @@ const onlyExportComponents = defineRule({
26348
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
26349
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
26350
26323
  isExportedNodeIds.add(declaration);
26351
- 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" });
26352
26325
  else exports.push({
26353
26326
  kind: "non-component",
26354
26327
  reportNode: declaration.id
@@ -42304,32 +42277,6 @@ const computeUnconditionalSet = (cfg) => {
42304
42277
  }
42305
42278
  return unconditional;
42306
42279
  };
42307
- const computeDominatesExit = (cfg) => {
42308
- const reachableToExit = /* @__PURE__ */ new Set();
42309
- const queue = [cfg.exit];
42310
- while (queue.length > 0) {
42311
- const block = queue.shift();
42312
- if (reachableToExit.has(block)) continue;
42313
- reachableToExit.add(block);
42314
- for (const edge of block.predecessors) queue.push(edge.from);
42315
- }
42316
- const dominatesExit = /* @__PURE__ */ new Set();
42317
- const visit = (block) => {
42318
- if (block === cfg.exit) return true;
42319
- if (dominatesExit.has(block)) return true;
42320
- if (block.successors.length === 0) return false;
42321
- dominatesExit.add(block);
42322
- let allReach = true;
42323
- for (const edge of block.successors) if (!visit(edge.to)) {
42324
- allReach = false;
42325
- break;
42326
- }
42327
- if (!allReach) dominatesExit.delete(block);
42328
- return allReach;
42329
- };
42330
- for (const block of cfg.blocks) visit(block);
42331
- return dominatesExit;
42332
- };
42333
42280
  const analyzeControlFlow = (program) => {
42334
42281
  nextBlockId = 0;
42335
42282
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -42337,8 +42284,7 @@ const analyzeControlFlow = (program) => {
42337
42284
  const cfg = buildFunctionCfg(functionNode, body);
42338
42285
  functionCfgs.set(functionNode, {
42339
42286
  cfg,
42340
- unconditionalSet: computeUnconditionalSet(cfg),
42341
- dominatesExitSet: computeDominatesExit(cfg)
42287
+ unconditionalSet: computeUnconditionalSet(cfg)
42342
42288
  });
42343
42289
  };
42344
42290
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -42381,20 +42327,10 @@ const analyzeControlFlow = (program) => {
42381
42327
  if (!block) return true;
42382
42328
  return entry.unconditionalSet.has(block);
42383
42329
  };
42384
- const dominatesExit = (node) => {
42385
- const owner = enclosingFunction(node);
42386
- if (!owner) return true;
42387
- const entry = functionCfgs.get(owner);
42388
- if (!entry) return true;
42389
- const block = entry.cfg.blockOf(node);
42390
- if (!block) return true;
42391
- return entry.dominatesExitSet.has(block);
42392
- };
42393
42330
  return {
42394
42331
  cfgFor,
42395
42332
  enclosingFunction,
42396
- isUnconditionalFromEntry,
42397
- dominatesExit
42333
+ isUnconditionalFromEntry
42398
42334
  };
42399
42335
  };
42400
42336
  //#endregion
@@ -42419,8 +42355,7 @@ const buildFallbackScopes = () => ({
42419
42355
  const FALLBACK_CFG = {
42420
42356
  cfgFor: () => null,
42421
42357
  enclosingFunction: () => null,
42422
- isUnconditionalFromEntry: () => false,
42423
- dominatesExit: () => false
42358
+ isUnconditionalFromEntry: () => false
42424
42359
  };
42425
42360
  const wrapWithSemanticContext = (rule) => ({
42426
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.b8170f8",
3
+ "version": "0.5.6-dev.ed0258c",
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",