oxlint-plugin-react-doctor 0.2.4 → 0.2.6
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 +0 -40
- package/dist/index.js +55 -159
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -529,26 +529,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
529
529
|
readonly recommendation?: string;
|
|
530
530
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
531
531
|
};
|
|
532
|
-
}, {
|
|
533
|
-
readonly key: "react-doctor/design-no-bold-heading";
|
|
534
|
-
readonly id: "design-no-bold-heading";
|
|
535
|
-
readonly source: "react-doctor";
|
|
536
|
-
readonly originallyExternal: false;
|
|
537
|
-
readonly framework: "global";
|
|
538
|
-
readonly category: "Architecture";
|
|
539
|
-
readonly severity: "warn";
|
|
540
|
-
readonly rule: {
|
|
541
|
-
readonly framework: "global";
|
|
542
|
-
readonly category: "Architecture";
|
|
543
|
-
readonly id: string;
|
|
544
|
-
readonly severity: RuleSeverity;
|
|
545
|
-
readonly requires?: ReadonlyArray<string>;
|
|
546
|
-
readonly disabledBy?: ReadonlyArray<string>;
|
|
547
|
-
readonly tags?: ReadonlyArray<string>;
|
|
548
|
-
readonly defaultEnabled?: boolean;
|
|
549
|
-
readonly recommendation?: string;
|
|
550
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
551
|
-
};
|
|
552
532
|
}, {
|
|
553
533
|
readonly key: "react-doctor/design-no-em-dash-in-jsx-text";
|
|
554
534
|
readonly id: "design-no-em-dash-in-jsx-text";
|
|
@@ -6315,26 +6295,6 @@ declare const RULES: readonly [{
|
|
|
6315
6295
|
readonly recommendation?: string;
|
|
6316
6296
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
6317
6297
|
};
|
|
6318
|
-
}, {
|
|
6319
|
-
readonly key: "react-doctor/design-no-bold-heading";
|
|
6320
|
-
readonly id: "design-no-bold-heading";
|
|
6321
|
-
readonly source: "react-doctor";
|
|
6322
|
-
readonly originallyExternal: false;
|
|
6323
|
-
readonly framework: "global";
|
|
6324
|
-
readonly category: "Architecture";
|
|
6325
|
-
readonly severity: "warn";
|
|
6326
|
-
readonly rule: {
|
|
6327
|
-
readonly framework: "global";
|
|
6328
|
-
readonly category: "Architecture";
|
|
6329
|
-
readonly id: string;
|
|
6330
|
-
readonly severity: RuleSeverity;
|
|
6331
|
-
readonly requires?: ReadonlyArray<string>;
|
|
6332
|
-
readonly disabledBy?: ReadonlyArray<string>;
|
|
6333
|
-
readonly tags?: ReadonlyArray<string>;
|
|
6334
|
-
readonly defaultEnabled?: boolean;
|
|
6335
|
-
readonly recommendation?: string;
|
|
6336
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
6337
|
-
};
|
|
6338
6298
|
}, {
|
|
6339
6299
|
readonly key: "react-doctor/design-no-em-dash-in-jsx-text";
|
|
6340
6300
|
readonly id: "design-no-em-dash-in-jsx-text";
|
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
|
-
|
|
975
|
-
return isInvalidHref(
|
|
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")
|
|
1307
|
-
const
|
|
1308
|
-
|
|
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
|
-
|
|
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")
|
|
3106
|
-
const
|
|
3107
|
-
if (
|
|
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;
|
|
@@ -3520,19 +3527,6 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
3520
3527
|
}
|
|
3521
3528
|
});
|
|
3522
3529
|
const LONG_TRANSITION_DURATION_THRESHOLD_MS = 1e3;
|
|
3523
|
-
const HEADING_TAG_NAMES = new Set([
|
|
3524
|
-
"h1",
|
|
3525
|
-
"h2",
|
|
3526
|
-
"h3",
|
|
3527
|
-
"h4",
|
|
3528
|
-
"h5",
|
|
3529
|
-
"h6"
|
|
3530
|
-
]);
|
|
3531
|
-
const HEAVY_HEADING_TAILWIND_WEIGHTS = new Set([
|
|
3532
|
-
"font-bold",
|
|
3533
|
-
"font-extrabold",
|
|
3534
|
-
"font-black"
|
|
3535
|
-
]);
|
|
3536
3530
|
const VAGUE_BUTTON_LABELS = new Set([
|
|
3537
3531
|
"continue",
|
|
3538
3532
|
"submit",
|
|
@@ -3602,85 +3596,6 @@ const getOpeningElementTagName = (openingElement) => {
|
|
|
3602
3596
|
return null;
|
|
3603
3597
|
};
|
|
3604
3598
|
//#endregion
|
|
3605
|
-
//#region src/plugin/rules/react-ui/utils/get-class-name-literal.ts
|
|
3606
|
-
const getClassNameLiteral = (classAttribute) => {
|
|
3607
|
-
if (!isNodeOfType(classAttribute, "JSXAttribute")) return null;
|
|
3608
|
-
if (!classAttribute.value) return null;
|
|
3609
|
-
if (isNodeOfType(classAttribute.value, "Literal") && typeof classAttribute.value.value === "string") return classAttribute.value.value;
|
|
3610
|
-
if (isNodeOfType(classAttribute.value, "JSXExpressionContainer")) {
|
|
3611
|
-
const expression = classAttribute.value.expression;
|
|
3612
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
3613
|
-
if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) return expression.quasis[0].value?.raw ?? null;
|
|
3614
|
-
}
|
|
3615
|
-
return null;
|
|
3616
|
-
};
|
|
3617
|
-
//#endregion
|
|
3618
|
-
//#region src/plugin/rules/react-ui/no-bold-heading.ts
|
|
3619
|
-
const getInlineStyleObjectExpression = (jsxAttribute) => {
|
|
3620
|
-
if (!isNodeOfType(jsxAttribute, "JSXAttribute")) return null;
|
|
3621
|
-
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "style") return null;
|
|
3622
|
-
if (!isNodeOfType(jsxAttribute.value, "JSXExpressionContainer")) return null;
|
|
3623
|
-
const expression = jsxAttribute.value.expression;
|
|
3624
|
-
if (!isNodeOfType(expression, "ObjectExpression")) return null;
|
|
3625
|
-
return expression;
|
|
3626
|
-
};
|
|
3627
|
-
const getStylePropertyKeyName = (objectProperty) => {
|
|
3628
|
-
if (!isNodeOfType(objectProperty, "Property")) return null;
|
|
3629
|
-
if (isNodeOfType(objectProperty.key, "Identifier")) return objectProperty.key.name;
|
|
3630
|
-
if (isNodeOfType(objectProperty.key, "Literal") && typeof objectProperty.key.value === "string") return objectProperty.key.value;
|
|
3631
|
-
return null;
|
|
3632
|
-
};
|
|
3633
|
-
const getStylePropertyNumericValue = (objectProperty) => {
|
|
3634
|
-
if (!isNodeOfType(objectProperty, "Property")) return null;
|
|
3635
|
-
const valueNode = objectProperty.value;
|
|
3636
|
-
if (!valueNode) return null;
|
|
3637
|
-
if (isNodeOfType(valueNode, "Literal") && typeof valueNode.value === "number") return valueNode.value;
|
|
3638
|
-
if (isNodeOfType(valueNode, "Literal") && typeof valueNode.value === "string") {
|
|
3639
|
-
const parsed = parseFloat(valueNode.value);
|
|
3640
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
3641
|
-
}
|
|
3642
|
-
return null;
|
|
3643
|
-
};
|
|
3644
|
-
const noBoldHeading = defineRule({
|
|
3645
|
-
id: "design-no-bold-heading",
|
|
3646
|
-
tags: ["design", "test-noise"],
|
|
3647
|
-
severity: "warn",
|
|
3648
|
-
category: "Architecture",
|
|
3649
|
-
recommendation: "Use `font-semibold` (600) or `font-medium` (500) on headings — 700+ crushes letter counter shapes at display sizes",
|
|
3650
|
-
create: (context) => ({ JSXOpeningElement(openingNode) {
|
|
3651
|
-
const tagName = getOpeningElementTagName(openingNode);
|
|
3652
|
-
if (!tagName || !HEADING_TAG_NAMES.has(tagName)) return;
|
|
3653
|
-
const classAttribute = findJsxAttribute(openingNode.attributes ?? [], "className");
|
|
3654
|
-
if (classAttribute) {
|
|
3655
|
-
const classNameLiteral = getClassNameLiteral(classAttribute);
|
|
3656
|
-
if (classNameLiteral) {
|
|
3657
|
-
for (const tailwindWeightToken of HEAVY_HEADING_TAILWIND_WEIGHTS) if (new RegExp(`(?:^|\\s)${tailwindWeightToken}(?:$|\\s|:)`).test(classNameLiteral)) {
|
|
3658
|
-
context.report({
|
|
3659
|
-
node: classAttribute,
|
|
3660
|
-
message: `${tailwindWeightToken} on <${tagName}> crushes counter shapes at display sizes — use font-semibold (600) or font-medium (500)`
|
|
3661
|
-
});
|
|
3662
|
-
return;
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
}
|
|
3666
|
-
const styleAttribute = findJsxAttribute(openingNode.attributes ?? [], "style");
|
|
3667
|
-
if (!styleAttribute) return;
|
|
3668
|
-
const styleObject = getInlineStyleObjectExpression(styleAttribute);
|
|
3669
|
-
if (!styleObject) return;
|
|
3670
|
-
for (const objectProperty of styleObject.properties ?? []) {
|
|
3671
|
-
if (getStylePropertyKeyName(objectProperty) !== "fontWeight") continue;
|
|
3672
|
-
const numericWeight = getStylePropertyNumericValue(objectProperty);
|
|
3673
|
-
if (numericWeight !== null && numericWeight >= 700) {
|
|
3674
|
-
context.report({
|
|
3675
|
-
node: objectProperty,
|
|
3676
|
-
message: `fontWeight: ${numericWeight} on <${tagName}> crushes counter shapes at display sizes — use 500 or 600`
|
|
3677
|
-
});
|
|
3678
|
-
return;
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
} })
|
|
3682
|
-
});
|
|
3683
|
-
//#endregion
|
|
3684
3599
|
//#region src/plugin/rules/react-ui/utils/is-inside-excluded-typography-ancestor.ts
|
|
3685
3600
|
const isInsideExcludedTypographyAncestor = (jsxTextNode) => {
|
|
3686
3601
|
let cursor = jsxTextNode.parent;
|
|
@@ -3714,6 +3629,19 @@ const noEmDashInJsxText = defineRule({
|
|
|
3714
3629
|
} })
|
|
3715
3630
|
});
|
|
3716
3631
|
//#endregion
|
|
3632
|
+
//#region src/plugin/rules/react-ui/utils/get-class-name-literal.ts
|
|
3633
|
+
const getClassNameLiteral = (classAttribute) => {
|
|
3634
|
+
if (!isNodeOfType(classAttribute, "JSXAttribute")) return null;
|
|
3635
|
+
if (!classAttribute.value) return null;
|
|
3636
|
+
if (isNodeOfType(classAttribute.value, "Literal") && typeof classAttribute.value.value === "string") return classAttribute.value.value;
|
|
3637
|
+
if (isNodeOfType(classAttribute.value, "JSXExpressionContainer")) {
|
|
3638
|
+
const expression = classAttribute.value.expression;
|
|
3639
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
3640
|
+
if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) return expression.quasis[0].value?.raw ?? null;
|
|
3641
|
+
}
|
|
3642
|
+
return null;
|
|
3643
|
+
};
|
|
3644
|
+
//#endregion
|
|
3717
3645
|
//#region src/plugin/rules/react-ui/utils/collect-axis-shorthand-pairs.ts
|
|
3718
3646
|
const collectAxisShorthandPairs = (classNameValue, horizontalPattern, verticalPattern) => {
|
|
3719
3647
|
const horizontalValues = /* @__PURE__ */ new Set();
|
|
@@ -3845,10 +3773,10 @@ const collectJsxLabelText = (jsxElementNode) => {
|
|
|
3845
3773
|
collectedFragments.push(expression.value);
|
|
3846
3774
|
continue;
|
|
3847
3775
|
}
|
|
3848
|
-
if (isNodeOfType(expression, "TemplateLiteral")
|
|
3849
|
-
const
|
|
3850
|
-
if (
|
|
3851
|
-
collectedFragments.push(
|
|
3776
|
+
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
3777
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
3778
|
+
if (staticValue !== null) {
|
|
3779
|
+
collectedFragments.push(staticValue);
|
|
3852
3780
|
continue;
|
|
3853
3781
|
}
|
|
3854
3782
|
}
|
|
@@ -5031,7 +4959,7 @@ const symbolHasStableHookOrigin = (symbol) => {
|
|
|
5031
4959
|
const initializer = unwrapExpression(initializerRaw);
|
|
5032
4960
|
if (symbol.kind === "const") {
|
|
5033
4961
|
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
|
|
4962
|
+
if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
|
|
5035
4963
|
}
|
|
5036
4964
|
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
5037
4965
|
const initializerHookName = getHookName(initializer.callee);
|
|
@@ -5251,7 +5179,7 @@ const isOutsideAllFunctions = (symbol) => {
|
|
|
5251
5179
|
}
|
|
5252
5180
|
return true;
|
|
5253
5181
|
};
|
|
5254
|
-
const isLiteralOrEmptyTemplate = (node) => isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateLiteral") && node
|
|
5182
|
+
const isLiteralOrEmptyTemplate = (node) => isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateLiteral") && getStaticTemplateLiteralValue(node) !== null;
|
|
5255
5183
|
const isNonStringLiteral = (node) => isNodeOfType(node, "Literal") && typeof node.value !== "string";
|
|
5256
5184
|
const isMatchingDepOrPrefix = (declaredKey, captureKey) => captureKey === declaredKey || captureKey.startsWith(`${declaredKey}.`);
|
|
5257
5185
|
const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
|
|
@@ -6169,11 +6097,8 @@ const evaluateLang = (attributeValue) => {
|
|
|
6169
6097
|
return "ok";
|
|
6170
6098
|
}
|
|
6171
6099
|
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
return cooked && cooked.length > 0 ? "ok" : "empty";
|
|
6175
|
-
}
|
|
6176
|
-
return "ok";
|
|
6100
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
6101
|
+
return staticValue === null ? "ok" : staticValue.length > 0 ? "ok" : "empty";
|
|
6177
6102
|
}
|
|
6178
6103
|
return "ok";
|
|
6179
6104
|
}
|
|
@@ -6235,11 +6160,8 @@ const evaluateTitleValue = (value) => {
|
|
|
6235
6160
|
return "dynamic-ok";
|
|
6236
6161
|
}
|
|
6237
6162
|
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
return cooked && cooked.length > 0 ? "ok" : "empty";
|
|
6241
|
-
}
|
|
6242
|
-
return "dynamic-ok";
|
|
6163
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
6164
|
+
return staticValue === null ? "dynamic-ok" : staticValue.length > 0 ? "ok" : "empty";
|
|
6243
6165
|
}
|
|
6244
6166
|
return "dynamic-ok";
|
|
6245
6167
|
}
|
|
@@ -7513,10 +7435,9 @@ const checkExpressionContainer = (container, parentIsAttribute, context, setting
|
|
|
7513
7435
|
return;
|
|
7514
7436
|
}
|
|
7515
7437
|
if (isNodeOfType(expression, "TemplateLiteral") && allowed === "never") {
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
const rawSource =
|
|
7519
|
-
const cooked = quasi.value.cooked ?? "";
|
|
7438
|
+
const cooked = getStaticTemplateLiteralValue(expression);
|
|
7439
|
+
if (cooked === null) return;
|
|
7440
|
+
const rawSource = expression.quasis?.[0]?.value.raw ?? "";
|
|
7520
7441
|
if (!parentIsAttribute && containsAnyQuote(cooked)) return;
|
|
7521
7442
|
if (isAllowedStringLikeInContainer(rawSource, container, parentIsAttribute)) return;
|
|
7522
7443
|
context.report({
|
|
@@ -7883,8 +7804,7 @@ const resolveSettings$31 = (settings) => {
|
|
|
7883
7804
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
7884
7805
|
return {
|
|
7885
7806
|
checkKeyMustBeforeSpread: ruleSettings.checkKeyMustBeforeSpread ?? true,
|
|
7886
|
-
warnOnDuplicates: ruleSettings.warnOnDuplicates ?? false
|
|
7887
|
-
checkFragmentShorthand: ruleSettings.checkFragmentShorthand ?? true
|
|
7807
|
+
warnOnDuplicates: ruleSettings.warnOnDuplicates ?? false
|
|
7888
7808
|
};
|
|
7889
7809
|
};
|
|
7890
7810
|
const findEnclosingIteratorContext = (jsxNode) => {
|
|
@@ -7999,10 +7919,10 @@ const getKeyAttributeValueString = (openingElement) => {
|
|
|
7999
7919
|
};
|
|
8000
7920
|
return null;
|
|
8001
7921
|
}
|
|
8002
|
-
if (isNodeOfType(expression, "TemplateLiteral")
|
|
8003
|
-
const
|
|
8004
|
-
if (
|
|
8005
|
-
keyValue:
|
|
7922
|
+
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
7923
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
7924
|
+
if (staticValue !== null) return {
|
|
7925
|
+
keyValue: staticValue,
|
|
8006
7926
|
node: attribute
|
|
8007
7927
|
};
|
|
8008
7928
|
}
|
|
@@ -8042,16 +7962,6 @@ const jsxKey = defineRule({
|
|
|
8042
7962
|
message: enclosingContext.kind === "array" ? MISSING_KEY_ARRAY : MISSING_KEY_ITERATOR
|
|
8043
7963
|
});
|
|
8044
7964
|
},
|
|
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
7965
|
ArrayExpression(node) {
|
|
8056
7966
|
if (!settings.warnOnDuplicates) return;
|
|
8057
7967
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -13054,7 +12964,7 @@ const isFalseAttributeValue = (value) => {
|
|
|
13054
12964
|
if (typeof expression.value === "string") return expression.value === "false";
|
|
13055
12965
|
return false;
|
|
13056
12966
|
}
|
|
13057
|
-
if (isNodeOfType(expression, "TemplateLiteral")) return expression
|
|
12967
|
+
if (isNodeOfType(expression, "TemplateLiteral")) return getStaticTemplateLiteralValue(expression) === "false";
|
|
13058
12968
|
}
|
|
13059
12969
|
return false;
|
|
13060
12970
|
};
|
|
@@ -24078,7 +23988,7 @@ const isPlatformSelectCallee = (callee) => {
|
|
|
24078
23988
|
const isStringLiteralEqualTo = (node, expected) => {
|
|
24079
23989
|
if (!node) return false;
|
|
24080
23990
|
if (isNodeOfType(node, "Literal") && node.value === expected) return true;
|
|
24081
|
-
if (isNodeOfType(node, "TemplateLiteral")
|
|
23991
|
+
if (isNodeOfType(node, "TemplateLiteral")) return getStaticTemplateLiteralValue(node) === expected;
|
|
24082
23992
|
return false;
|
|
24083
23993
|
};
|
|
24084
23994
|
const readStaticPropertyKeyName = (property) => {
|
|
@@ -29892,20 +29802,6 @@ const reactDoctorRules = [
|
|
|
29892
29802
|
category: "Accessibility"
|
|
29893
29803
|
}
|
|
29894
29804
|
},
|
|
29895
|
-
{
|
|
29896
|
-
key: "react-doctor/design-no-bold-heading",
|
|
29897
|
-
id: "design-no-bold-heading",
|
|
29898
|
-
source: "react-doctor",
|
|
29899
|
-
originallyExternal: false,
|
|
29900
|
-
framework: "global",
|
|
29901
|
-
category: "Architecture",
|
|
29902
|
-
severity: "warn",
|
|
29903
|
-
rule: {
|
|
29904
|
-
...noBoldHeading,
|
|
29905
|
-
framework: "global",
|
|
29906
|
-
category: "Architecture"
|
|
29907
|
-
}
|
|
29908
|
-
},
|
|
29909
29805
|
{
|
|
29910
29806
|
key: "react-doctor/design-no-em-dash-in-jsx-text",
|
|
29911
29807
|
id: "design-no-em-dash-in-jsx-text",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"oxc-parser": "^0.132.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
|
-
"node": ">=22.
|
|
53
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"dev": "vp pack --watch",
|