oxlint-plugin-react-doctor 0.2.4 → 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.js +42 -53
- package/package.json +2 -2
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;
|
|
@@ -3845,10 +3852,10 @@ const collectJsxLabelText = (jsxElementNode) => {
|
|
|
3845
3852
|
collectedFragments.push(expression.value);
|
|
3846
3853
|
continue;
|
|
3847
3854
|
}
|
|
3848
|
-
if (isNodeOfType(expression, "TemplateLiteral")
|
|
3849
|
-
const
|
|
3850
|
-
if (
|
|
3851
|
-
collectedFragments.push(
|
|
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
|
|
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
|
|
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
|
-
|
|
6173
|
-
|
|
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
|
-
|
|
6239
|
-
|
|
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
|
-
|
|
7517
|
-
|
|
7518
|
-
const rawSource =
|
|
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")
|
|
8003
|
-
const
|
|
8004
|
-
if (
|
|
8005
|
-
keyValue:
|
|
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
|
|
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")
|
|
24070
|
+
if (isNodeOfType(node, "TemplateLiteral")) return getStaticTemplateLiteralValue(node) === expected;
|
|
24082
24071
|
return false;
|
|
24083
24072
|
};
|
|
24084
24073
|
const readStaticPropertyKeyName = (property) => {
|
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.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",
|
|
@@ -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",
|