oxlint-plugin-react-doctor 0.2.3 → 0.2.5

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
@@ -11713,5 +11713,10 @@ declare const REACT_COMPILER_RULES: Record<string, OxlintRuleSeverity>;
11713
11713
  //#region src/plugin/constants/style.d.ts
11714
11714
  declare const MOTION_LIBRARY_PACKAGES: Set<string>;
11715
11715
  //#endregion
11716
- export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, EXTERNAL_RULES, type EsTreeNode, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, type OxlintRuleSeverity, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, type Rule, type RuleFramework, type RulePlugin, type RuleSeverity, type RuleVisitors, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, plugin as default };
11716
+ //#region src/react-native-dependency-names.d.ts
11717
+ declare const REACT_NATIVE_DEPENDENCY_NAMES: ReadonlySet<string>;
11718
+ declare const REACT_NATIVE_DEPENDENCY_PREFIXES: ReadonlyArray<string>;
11719
+ declare const isReactNativeDependencyName: (dependencyName: string) => boolean;
11720
+ //#endregion
11721
+ export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, EXTERNAL_RULES, type EsTreeNode, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, type OxlintRuleSeverity, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, type Rule, type RuleFramework, type RulePlugin, type RuleSeverity, type RuleVisitors, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, plugin as default, isReactNativeDependencyName };
11717
11722
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -943,6 +943,15 @@ const anchorHasContent = defineRule({
943
943
  } })
944
944
  });
945
945
  //#endregion
946
+ //#region src/plugin/utils/get-static-template-literal-value.ts
947
+ const getStaticTemplateLiteralValue = (templateLiteral) => {
948
+ const expressions = templateLiteral.expressions ?? [];
949
+ const quasis = templateLiteral.quasis ?? [];
950
+ if (expressions.length !== 0 || quasis.length !== 1) return null;
951
+ const value = quasis[0]?.value;
952
+ return value?.cooked ?? value?.raw ?? null;
953
+ };
954
+ //#endregion
946
955
  //#region src/plugin/rules/a11y/anchor-is-valid.ts
947
956
  const MESSAGE_MISSING_HREF = "`<a>` element is missing an `href` — anchors without `href` aren't keyboard-focusable; use `<button>` for actions.";
948
957
  const MESSAGE_INCORRECT_HREF = "`<a>` element has invalid `href` (`#`, `javascript:`, or empty) — provide a real destination.";
@@ -971,8 +980,8 @@ const checkValueIsEmptyOrInvalid = (value, validHrefs) => {
971
980
  if (typeof expression.value === "string") return isInvalidHref(expression.value, validHrefs);
972
981
  }
973
982
  if (isNodeOfType(expression, "TemplateLiteral")) {
974
- if (expression.expressions.length > 0) return false;
975
- return isInvalidHref(expression.quasis[0]?.value.cooked ?? "", validHrefs);
983
+ const staticValue = getStaticTemplateLiteralValue(expression);
984
+ return staticValue === null ? false : isInvalidHref(staticValue, validHrefs);
976
985
  }
977
986
  }
978
987
  if (isNodeOfType(value, "JSXFragment")) return true;
@@ -1303,9 +1312,10 @@ const parseJsxValue = (value) => {
1303
1312
  }
1304
1313
  }
1305
1314
  if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && typeof expression.argument.value === "number") return -expression.argument.value;
1306
- if (isNodeOfType(expression, "TemplateLiteral") && expression.expressions.length === 0 && expression.quasis.length === 1) {
1307
- const cooked = expression.quasis[0].value.cooked ?? "";
1308
- const parsed = Number(cooked);
1315
+ if (isNodeOfType(expression, "TemplateLiteral")) {
1316
+ const staticValue = getStaticTemplateLiteralValue(expression);
1317
+ if (staticValue === null) return null;
1318
+ const parsed = Number(staticValue);
1309
1319
  return Number.isFinite(parsed) ? parsed : null;
1310
1320
  }
1311
1321
  if (isNodeOfType(expression, "ConditionalExpression")) {
@@ -1670,8 +1680,8 @@ const expressionToBoolean = (expression) => {
1670
1680
  return null;
1671
1681
  }
1672
1682
  if (isNodeOfType(expression, "TemplateLiteral")) {
1673
- if (expression.expressions.length === 0 && expression.quasis.length === 1) return (expression.quasis[0]?.value.cooked ?? "").length > 0;
1674
- return null;
1683
+ const staticValue = getStaticTemplateLiteralValue(expression);
1684
+ return staticValue === null ? null : staticValue.length > 0;
1675
1685
  }
1676
1686
  if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
1677
1687
  const inner = expressionToBoolean(expression.argument);
@@ -1702,10 +1712,7 @@ const parseAriaValueAsString = (value, booleanAsString) => {
1702
1712
  if (typeof expression.value === "boolean") return booleanAsString ? String(expression.value) : null;
1703
1713
  return null;
1704
1714
  }
1705
- if (isNodeOfType(expression, "TemplateLiteral")) {
1706
- if (expression.expressions.length === 0 && expression.quasis.length === 1) return (expression.quasis[0]?.value.cooked ?? "").toLowerCase();
1707
- return null;
1708
- }
1715
+ if (isNodeOfType(expression, "TemplateLiteral")) return getStaticTemplateLiteralValue(expression)?.toLowerCase() ?? null;
1709
1716
  if (booleanAsString && isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
1710
1717
  const inner = expressionToBoolean(expression.argument);
1711
1718
  if (inner === null) return null;
@@ -1718,7 +1725,7 @@ const isMultiQuasiTemplate = (value) => {
1718
1725
  if (!isNodeOfType(value, "JSXExpressionContainer")) return false;
1719
1726
  const expression = value.expression;
1720
1727
  if (!isNodeOfType(expression, "TemplateLiteral")) return false;
1721
- return expression.expressions.length > 0;
1728
+ return (expression.expressions ?? []).length > 0;
1722
1729
  };
1723
1730
  const isValidValueForType = (propType, value) => {
1724
1731
  if (!isTargetLiteralValue(value)) return true;
@@ -3102,9 +3109,9 @@ const isValidTypeValue = (rawValue, settings) => {
3102
3109
  };
3103
3110
  const isProvenValidExpression = (expression, settings) => {
3104
3111
  if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return isValidTypeValue(expression.value, settings);
3105
- if (isNodeOfType(expression, "TemplateLiteral") && expression.expressions.length === 0 && expression.quasis.length === 1) {
3106
- const cookedValue = expression.quasis[0].value.cooked;
3107
- if (typeof cookedValue === "string") return isValidTypeValue(cookedValue, settings);
3112
+ if (isNodeOfType(expression, "TemplateLiteral")) {
3113
+ const staticValue = getStaticTemplateLiteralValue(expression);
3114
+ if (staticValue !== null) return isValidTypeValue(staticValue, settings);
3108
3115
  }
3109
3116
  if (isNodeOfType(expression, "ConditionalExpression")) return isProvenValidExpression(expression.consequent, settings) && isProvenValidExpression(expression.alternate, settings);
3110
3117
  return false;
@@ -3845,10 +3852,10 @@ const collectJsxLabelText = (jsxElementNode) => {
3845
3852
  collectedFragments.push(expression.value);
3846
3853
  continue;
3847
3854
  }
3848
- if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) {
3849
- const rawTemplate = expression.quasis[0].value?.raw;
3850
- if (typeof rawTemplate === "string" && expression.expressions.length === 0) {
3851
- collectedFragments.push(rawTemplate);
3855
+ if (isNodeOfType(expression, "TemplateLiteral")) {
3856
+ const staticValue = getStaticTemplateLiteralValue(expression);
3857
+ if (staticValue !== null) {
3858
+ collectedFragments.push(staticValue);
3852
3859
  continue;
3853
3860
  }
3854
3861
  }
@@ -5031,7 +5038,7 @@ const symbolHasStableHookOrigin = (symbol) => {
5031
5038
  const initializer = unwrapExpression(initializerRaw);
5032
5039
  if (symbol.kind === "const") {
5033
5040
  if (isNodeOfType(initializer, "Literal") && (initializer.value === null || typeof initializer.value === "number" || typeof initializer.value === "string" || typeof initializer.value === "boolean")) return true;
5034
- if (isNodeOfType(initializer, "TemplateLiteral") && initializer.expressions.length === 0) return true;
5041
+ if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
5035
5042
  }
5036
5043
  if (!isNodeOfType(initializer, "CallExpression")) return false;
5037
5044
  const initializerHookName = getHookName(initializer.callee);
@@ -5251,7 +5258,7 @@ const isOutsideAllFunctions = (symbol) => {
5251
5258
  }
5252
5259
  return true;
5253
5260
  };
5254
- const isLiteralOrEmptyTemplate = (node) => isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateLiteral") && node.expressions.length === 0;
5261
+ const isLiteralOrEmptyTemplate = (node) => isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateLiteral") && getStaticTemplateLiteralValue(node) !== null;
5255
5262
  const isNonStringLiteral = (node) => isNodeOfType(node, "Literal") && typeof node.value !== "string";
5256
5263
  const isMatchingDepOrPrefix = (declaredKey, captureKey) => captureKey === declaredKey || captureKey.startsWith(`${declaredKey}.`);
5257
5264
  const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
@@ -6169,11 +6176,8 @@ const evaluateLang = (attributeValue) => {
6169
6176
  return "ok";
6170
6177
  }
6171
6178
  if (isNodeOfType(expression, "TemplateLiteral")) {
6172
- if (expression.expressions.length === 0 && expression.quasis.length === 1) {
6173
- const cooked = expression.quasis[0].value.cooked;
6174
- return cooked && cooked.length > 0 ? "ok" : "empty";
6175
- }
6176
- return "ok";
6179
+ const staticValue = getStaticTemplateLiteralValue(expression);
6180
+ return staticValue === null ? "ok" : staticValue.length > 0 ? "ok" : "empty";
6177
6181
  }
6178
6182
  return "ok";
6179
6183
  }
@@ -6235,11 +6239,8 @@ const evaluateTitleValue = (value) => {
6235
6239
  return "dynamic-ok";
6236
6240
  }
6237
6241
  if (isNodeOfType(expression, "TemplateLiteral")) {
6238
- if (expression.expressions.length === 0 && expression.quasis.length === 1) {
6239
- const cooked = expression.quasis[0].value.cooked;
6240
- return cooked && cooked.length > 0 ? "ok" : "empty";
6241
- }
6242
- return "dynamic-ok";
6242
+ const staticValue = getStaticTemplateLiteralValue(expression);
6243
+ return staticValue === null ? "dynamic-ok" : staticValue.length > 0 ? "ok" : "empty";
6243
6244
  }
6244
6245
  return "dynamic-ok";
6245
6246
  }
@@ -7513,10 +7514,9 @@ const checkExpressionContainer = (container, parentIsAttribute, context, setting
7513
7514
  return;
7514
7515
  }
7515
7516
  if (isNodeOfType(expression, "TemplateLiteral") && allowed === "never") {
7516
- if (expression.expressions.length !== 0 || expression.quasis.length !== 1) return;
7517
- const quasi = expression.quasis[0];
7518
- const rawSource = quasi.value.raw ?? "";
7519
- const cooked = quasi.value.cooked ?? "";
7517
+ const cooked = getStaticTemplateLiteralValue(expression);
7518
+ if (cooked === null) return;
7519
+ const rawSource = expression.quasis?.[0]?.value.raw ?? "";
7520
7520
  if (!parentIsAttribute && containsAnyQuote(cooked)) return;
7521
7521
  if (isAllowedStringLikeInContainer(rawSource, container, parentIsAttribute)) return;
7522
7522
  context.report({
@@ -7883,8 +7883,7 @@ const resolveSettings$31 = (settings) => {
7883
7883
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
7884
7884
  return {
7885
7885
  checkKeyMustBeforeSpread: ruleSettings.checkKeyMustBeforeSpread ?? true,
7886
- warnOnDuplicates: ruleSettings.warnOnDuplicates ?? false,
7887
- checkFragmentShorthand: ruleSettings.checkFragmentShorthand ?? true
7886
+ warnOnDuplicates: ruleSettings.warnOnDuplicates ?? false
7888
7887
  };
7889
7888
  };
7890
7889
  const findEnclosingIteratorContext = (jsxNode) => {
@@ -7999,10 +7998,10 @@ const getKeyAttributeValueString = (openingElement) => {
7999
7998
  };
8000
7999
  return null;
8001
8000
  }
8002
- if (isNodeOfType(expression, "TemplateLiteral") && expression.expressions.length === 0 && expression.quasis.length === 1) {
8003
- const cookedValue = expression.quasis[0].value.cooked;
8004
- if (typeof cookedValue === "string") return {
8005
- keyValue: cookedValue,
8001
+ if (isNodeOfType(expression, "TemplateLiteral")) {
8002
+ const staticValue = getStaticTemplateLiteralValue(expression);
8003
+ if (staticValue !== null) return {
8004
+ keyValue: staticValue,
8006
8005
  node: attribute
8007
8006
  };
8008
8007
  }
@@ -8042,16 +8041,6 @@ const jsxKey = defineRule({
8042
8041
  message: enclosingContext.kind === "array" ? MISSING_KEY_ARRAY : MISSING_KEY_ITERATOR
8043
8042
  });
8044
8043
  },
8045
- JSXFragment(node) {
8046
- if (!settings.checkFragmentShorthand) return;
8047
- const enclosingContext = findEnclosingIteratorContext(node);
8048
- if (!enclosingContext) return;
8049
- if (isWithinChildrenToArray(node)) return;
8050
- context.report({
8051
- node,
8052
- message: enclosingContext.kind === "array" ? MISSING_KEY_ARRAY : MISSING_KEY_ITERATOR
8053
- });
8054
- },
8055
8044
  ArrayExpression(node) {
8056
8045
  if (!settings.warnOnDuplicates) return;
8057
8046
  const seenKeys = /* @__PURE__ */ new Set();
@@ -13054,7 +13043,7 @@ const isFalseAttributeValue = (value) => {
13054
13043
  if (typeof expression.value === "string") return expression.value === "false";
13055
13044
  return false;
13056
13045
  }
13057
- if (isNodeOfType(expression, "TemplateLiteral")) return expression.expressions.length === 0 && expression.quasis.length === 1 && expression.quasis[0].value.cooked === "false";
13046
+ if (isNodeOfType(expression, "TemplateLiteral")) return getStaticTemplateLiteralValue(expression) === "false";
13058
13047
  }
13059
13048
  return false;
13060
13049
  };
@@ -24078,7 +24067,7 @@ const isPlatformSelectCallee = (callee) => {
24078
24067
  const isStringLiteralEqualTo = (node, expected) => {
24079
24068
  if (!node) return false;
24080
24069
  if (isNodeOfType(node, "Literal") && node.value === expected) return true;
24081
- if (isNodeOfType(node, "TemplateLiteral") && node.quasis.length === 1) return node.quasis[0]?.value?.cooked === expected;
24070
+ if (isNodeOfType(node, "TemplateLiteral")) return getStaticTemplateLiteralValue(node) === expected;
24082
24071
  return false;
24083
24072
  };
24084
24073
  const readStaticPropertyKeyName = (property) => {
@@ -33651,7 +33640,7 @@ const reactDoctorRules = [
33651
33640
  ];
33652
33641
  const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
33653
33642
  //#endregion
33654
- //#region ../types/dist/index.js
33643
+ //#region src/react-native-dependency-names.ts
33655
33644
  const REACT_NATIVE_DEPENDENCY_NAMES = new Set([
33656
33645
  "react-native",
33657
33646
  "react-native-tvos",
@@ -34424,6 +34413,6 @@ const REACT_COMPILER_RULES = toRuleMap(collectExternalRulesBySource("react-compi
34424
34413
  //#region src/index.ts
34425
34414
  var src_default = plugin;
34426
34415
  //#endregion
34427
- export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, src_default as default };
34416
+ export { ALL_REACT_DOCTOR_RULES, ALL_REACT_DOCTOR_RULE_KEYS, EXTERNAL_RULES, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, NEXTJS_RULES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_NATIVE_DEPENDENCY_NAMES, REACT_NATIVE_DEPENDENCY_PREFIXES, REACT_NATIVE_RULES, RECOMMENDED_RULES, RULES, TANSTACK_QUERY_RULES, TANSTACK_START_RULES, src_default as default, isReactNativeDependencyName };
34428
34417
 
34429
34418
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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",
@@ -41,19 +41,16 @@
41
41
  }
42
42
  },
43
43
  "dependencies": {
44
- "@types/eslint-scope": "^9.1.0",
45
- "@types/eslint-visitor-keys": "^3.3.2",
46
44
  "@typescript-eslint/types": "^8.59.3",
47
45
  "eslint-scope": "^9.1.2",
48
46
  "eslint-visitor-keys": "^5.0.1"
49
47
  },
50
48
  "devDependencies": {
51
49
  "@types/node": "^25.6.0",
52
- "oxc-parser": "^0.132.0",
53
- "@react-doctor/types": "0.2.3"
50
+ "oxc-parser": "^0.132.0"
54
51
  },
55
52
  "engines": {
56
- "node": ">=22.13.0"
53
+ "node": "^20.19.0 || >=22.12.0"
57
54
  },
58
55
  "scripts": {
59
56
  "dev": "vp pack --watch",