oxlint-plugin-react-doctor 0.4.2-dev.829655c → 0.4.2-dev.93d4eec
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 +348 -348
- package/dist/index.js +1202 -829
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -719,7 +719,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
|
|
|
719
719
|
if (totalEffects === 0) return;
|
|
720
720
|
context.report({
|
|
721
721
|
node: openingElement,
|
|
722
|
-
message: `Every hide
|
|
722
|
+
message: `Every hide and show rebuilds ${effectfulChildren.join(", ")} from scratch because <Activity> wraps components with ${totalEffects} effect hook${totalEffects === 1 ? "" : "s"}.`
|
|
723
723
|
});
|
|
724
724
|
}
|
|
725
725
|
};
|
|
@@ -823,15 +823,550 @@ const getElementType = (openingElement, settings) => {
|
|
|
823
823
|
return baseName;
|
|
824
824
|
};
|
|
825
825
|
//#endregion
|
|
826
|
+
//#region src/plugin/utils/find-import-source-for-name.ts
|
|
827
|
+
const collectFromProgram = (programRoot) => {
|
|
828
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
829
|
+
const visit = (node) => {
|
|
830
|
+
if (node.type === "ImportDeclaration" && "source" in node && node.source) {
|
|
831
|
+
const source = node.source.value;
|
|
832
|
+
if (typeof source !== "string") return;
|
|
833
|
+
if ("specifiers" in node && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
|
|
834
|
+
if (!("local" in specifier) || !specifier.local) continue;
|
|
835
|
+
const local = specifier.local;
|
|
836
|
+
if (typeof local.name !== "string") continue;
|
|
837
|
+
if (specifier.type === "ImportDefaultSpecifier") lookup.set(local.name, {
|
|
838
|
+
source,
|
|
839
|
+
imported: null,
|
|
840
|
+
isDefault: true,
|
|
841
|
+
isNamespace: false
|
|
842
|
+
});
|
|
843
|
+
else if (specifier.type === "ImportNamespaceSpecifier") lookup.set(local.name, {
|
|
844
|
+
source,
|
|
845
|
+
imported: null,
|
|
846
|
+
isDefault: false,
|
|
847
|
+
isNamespace: true
|
|
848
|
+
});
|
|
849
|
+
else if (specifier.type === "ImportSpecifier") {
|
|
850
|
+
const importedNode = specifier.imported;
|
|
851
|
+
const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
|
|
852
|
+
lookup.set(local.name, {
|
|
853
|
+
source,
|
|
854
|
+
imported: importedName,
|
|
855
|
+
isDefault: false,
|
|
856
|
+
isNamespace: false
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const nodeRecord = node;
|
|
863
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
864
|
+
if (key === "parent") continue;
|
|
865
|
+
const child = nodeRecord[key];
|
|
866
|
+
if (Array.isArray(child)) {
|
|
867
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
868
|
+
} else if (isAstNode(child)) visit(child);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
visit(programRoot);
|
|
872
|
+
return lookup;
|
|
873
|
+
};
|
|
874
|
+
const importLookupCache = /* @__PURE__ */ new WeakMap();
|
|
875
|
+
const getImportLookup = (node) => {
|
|
876
|
+
const programRoot = findProgramRoot(node);
|
|
877
|
+
if (!programRoot) return null;
|
|
878
|
+
let cached = importLookupCache.get(programRoot);
|
|
879
|
+
if (!cached) {
|
|
880
|
+
cached = collectFromProgram(programRoot);
|
|
881
|
+
importLookupCache.set(programRoot, cached);
|
|
882
|
+
}
|
|
883
|
+
return cached;
|
|
884
|
+
};
|
|
885
|
+
const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
886
|
+
const lookup = getImportLookup(contextNode);
|
|
887
|
+
if (!lookup) return false;
|
|
888
|
+
const info = lookup.get(localIdentifierName);
|
|
889
|
+
if (!info) return false;
|
|
890
|
+
return info.source === moduleSource;
|
|
891
|
+
};
|
|
892
|
+
const isNamespaceImportFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
893
|
+
const lookup = getImportLookup(contextNode);
|
|
894
|
+
if (!lookup) return false;
|
|
895
|
+
const info = lookup.get(localIdentifierName);
|
|
896
|
+
if (!info) return false;
|
|
897
|
+
return info.isNamespace && info.source === moduleSource;
|
|
898
|
+
};
|
|
899
|
+
const isDefaultImportFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
900
|
+
const lookup = getImportLookup(contextNode);
|
|
901
|
+
if (!lookup) return false;
|
|
902
|
+
const info = lookup.get(localIdentifierName);
|
|
903
|
+
if (!info) return false;
|
|
904
|
+
return info.isDefault && info.source === moduleSource;
|
|
905
|
+
};
|
|
906
|
+
const getImportedNameFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
907
|
+
const lookup = getImportLookup(contextNode);
|
|
908
|
+
if (!lookup) return null;
|
|
909
|
+
const info = lookup.get(localIdentifierName);
|
|
910
|
+
if (!info) return null;
|
|
911
|
+
if (info.source !== moduleSource) return null;
|
|
912
|
+
return info.imported;
|
|
913
|
+
};
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/plugin/utils/find-variable-initializer.ts
|
|
916
|
+
const FUNCTION_LIKE_TYPES$1 = new Set([
|
|
917
|
+
"FunctionDeclaration",
|
|
918
|
+
"FunctionExpression",
|
|
919
|
+
"ArrowFunctionExpression",
|
|
920
|
+
"MethodDefinition",
|
|
921
|
+
"Program"
|
|
922
|
+
]);
|
|
923
|
+
const findScopeOwner = (node) => {
|
|
924
|
+
let ancestor = node;
|
|
925
|
+
while (ancestor) {
|
|
926
|
+
if (FUNCTION_LIKE_TYPES$1.has(ancestor.type)) return ancestor;
|
|
927
|
+
ancestor = ancestor.parent ?? null;
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
};
|
|
931
|
+
const findBlockScopeOwner = (declaratorNode, declarationKind) => {
|
|
932
|
+
if (declarationKind !== "let" && declarationKind !== "const") return findScopeOwner(declaratorNode);
|
|
933
|
+
let ancestor = declaratorNode.parent;
|
|
934
|
+
while (ancestor) {
|
|
935
|
+
if (ancestor.type === "BlockStatement") {
|
|
936
|
+
const blockParent = ancestor.parent;
|
|
937
|
+
if (blockParent && (blockParent.type === "FunctionDeclaration" || blockParent.type === "FunctionExpression" || blockParent.type === "ArrowFunctionExpression" || blockParent.type === "MethodDefinition")) return findScopeOwner(declaratorNode);
|
|
938
|
+
return ancestor;
|
|
939
|
+
}
|
|
940
|
+
if (FUNCTION_LIKE_TYPES$1.has(ancestor.type)) return ancestor;
|
|
941
|
+
ancestor = ancestor.parent ?? null;
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
};
|
|
945
|
+
const collectFromBindingPattern = (pattern, initializer, scopeOwner, out) => {
|
|
946
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
947
|
+
const list = out.get(pattern.name) ?? [];
|
|
948
|
+
list.push({
|
|
949
|
+
bindingIdentifier: pattern,
|
|
950
|
+
initializer,
|
|
951
|
+
scopeOwner
|
|
952
|
+
});
|
|
953
|
+
out.set(pattern.name, list);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
957
|
+
for (const property of pattern.properties) if (isNodeOfType(property, "Property")) {
|
|
958
|
+
const valueNode = property.value;
|
|
959
|
+
collectFromBindingPattern(valueNode, isNodeOfType(valueNode, "AssignmentPattern") ? valueNode.right : null, scopeOwner, out);
|
|
960
|
+
} else if (isNodeOfType(property, "RestElement")) collectFromBindingPattern(property.argument, null, scopeOwner, out);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
964
|
+
for (const element of pattern.elements) {
|
|
965
|
+
if (!element) continue;
|
|
966
|
+
collectFromBindingPattern(element, isNodeOfType(element, "AssignmentPattern") ? element.right ?? null : null, scopeOwner, out);
|
|
967
|
+
}
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) {
|
|
971
|
+
collectFromBindingPattern(pattern.left, pattern.right ?? null, scopeOwner, out);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (isNodeOfType(pattern, "RestElement")) collectFromBindingPattern(pattern.argument, null, scopeOwner, out);
|
|
975
|
+
};
|
|
976
|
+
const buildBindingIndex = (root) => {
|
|
977
|
+
const out = /* @__PURE__ */ new Map();
|
|
978
|
+
const visit = (node) => {
|
|
979
|
+
if (isNodeOfType(node, "VariableDeclarator")) {
|
|
980
|
+
const declaration = node.parent;
|
|
981
|
+
const scopeOwner = findBlockScopeOwner(node, declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : void 0);
|
|
982
|
+
if (scopeOwner) collectFromBindingPattern(node.id, node.init ?? null, scopeOwner, out);
|
|
983
|
+
}
|
|
984
|
+
if ((isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression")) && node.id) {
|
|
985
|
+
const enclosing = node.parent ? findScopeOwner(node.parent) : null;
|
|
986
|
+
if (enclosing) {
|
|
987
|
+
const list = out.get(node.id.name) ?? [];
|
|
988
|
+
list.push({
|
|
989
|
+
bindingIdentifier: node.id,
|
|
990
|
+
initializer: node,
|
|
991
|
+
scopeOwner: enclosing
|
|
992
|
+
});
|
|
993
|
+
out.set(node.id.name, list);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if ((isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) && node.id) {
|
|
997
|
+
const enclosing = node.parent ? findScopeOwner(node.parent) : null;
|
|
998
|
+
if (enclosing) {
|
|
999
|
+
const list = out.get(node.id.name) ?? [];
|
|
1000
|
+
list.push({
|
|
1001
|
+
bindingIdentifier: node.id,
|
|
1002
|
+
initializer: node,
|
|
1003
|
+
scopeOwner: enclosing
|
|
1004
|
+
});
|
|
1005
|
+
out.set(node.id.name, list);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) {
|
|
1009
|
+
if (Array.isArray(node.params)) for (const param of node.params) {
|
|
1010
|
+
if (!param) continue;
|
|
1011
|
+
collectFromBindingPattern(param, null, node, out);
|
|
1012
|
+
if (isNodeOfType(param, "AssignmentPattern")) collectFromBindingPattern(param.left ?? null, param.right ?? null, node, out);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (isNodeOfType(node, "ImportDeclaration")) {
|
|
1016
|
+
const scopeOwner = findScopeOwner(node);
|
|
1017
|
+
if (scopeOwner && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
|
|
1018
|
+
const local = specifier.local;
|
|
1019
|
+
if (local && isNodeOfType(local, "Identifier")) {
|
|
1020
|
+
const list = out.get(local.name) ?? [];
|
|
1021
|
+
list.push({
|
|
1022
|
+
bindingIdentifier: local,
|
|
1023
|
+
initializer: specifier,
|
|
1024
|
+
scopeOwner
|
|
1025
|
+
});
|
|
1026
|
+
out.set(local.name, list);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (node.type === "TSImportEqualsDeclaration" || node.type === "TSEnumDeclaration" || node.type === "TSModuleDeclaration") {
|
|
1031
|
+
const idNode = node.id;
|
|
1032
|
+
if (idNode && idNode.type === "Identifier") {
|
|
1033
|
+
const idObject = idNode;
|
|
1034
|
+
const scopeOwner = findScopeOwner(node);
|
|
1035
|
+
if (scopeOwner && typeof idObject.name === "string") {
|
|
1036
|
+
const list = out.get(idObject.name) ?? [];
|
|
1037
|
+
list.push({
|
|
1038
|
+
bindingIdentifier: idNode,
|
|
1039
|
+
initializer: null,
|
|
1040
|
+
scopeOwner
|
|
1041
|
+
});
|
|
1042
|
+
out.set(idObject.name, list);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const nodeRecord = node;
|
|
1047
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
1048
|
+
if (key === "parent") continue;
|
|
1049
|
+
const child = nodeRecord[key];
|
|
1050
|
+
if (Array.isArray(child)) {
|
|
1051
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
1052
|
+
} else if (isAstNode(child)) visit(child);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
visit(root);
|
|
1056
|
+
return out;
|
|
1057
|
+
};
|
|
1058
|
+
const programRootCache = /* @__PURE__ */ new WeakMap();
|
|
1059
|
+
const getBindingIndex = (referenceNode) => {
|
|
1060
|
+
const programRoot = findProgramRoot(referenceNode);
|
|
1061
|
+
if (!programRoot) return null;
|
|
1062
|
+
let index = programRootCache.get(programRoot);
|
|
1063
|
+
if (!index) {
|
|
1064
|
+
index = buildBindingIndex(programRoot);
|
|
1065
|
+
programRootCache.set(programRoot, index);
|
|
1066
|
+
}
|
|
1067
|
+
return index;
|
|
1068
|
+
};
|
|
1069
|
+
const findVariableInitializer = (referenceNode, bindingName) => {
|
|
1070
|
+
const index = getBindingIndex(referenceNode);
|
|
1071
|
+
if (!index) return null;
|
|
1072
|
+
const candidates = index.get(bindingName);
|
|
1073
|
+
if (!candidates || candidates.length === 0) return null;
|
|
1074
|
+
const referenceAncestors = /* @__PURE__ */ new Set();
|
|
1075
|
+
let walker = referenceNode;
|
|
1076
|
+
while (walker) {
|
|
1077
|
+
referenceAncestors.add(walker);
|
|
1078
|
+
walker = walker.parent ?? null;
|
|
1079
|
+
}
|
|
1080
|
+
let best = null;
|
|
1081
|
+
for (const candidate of candidates) {
|
|
1082
|
+
if (!referenceAncestors.has(candidate.scopeOwner)) continue;
|
|
1083
|
+
if (best === null) {
|
|
1084
|
+
best = candidate;
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
let cursor = candidate.scopeOwner;
|
|
1088
|
+
while (cursor) {
|
|
1089
|
+
if (cursor === best.scopeOwner) {
|
|
1090
|
+
best = candidate;
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
cursor = cursor.parent ?? null;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return best;
|
|
1097
|
+
};
|
|
1098
|
+
//#endregion
|
|
1099
|
+
//#region src/plugin/utils/flatten-jsx-name.ts
|
|
1100
|
+
/**
|
|
1101
|
+
* Flattens a JSX opener / member-expression chain into a dotted name.
|
|
1102
|
+
*
|
|
1103
|
+
* `<Foo />` → `"Foo"`
|
|
1104
|
+
* `<Namespace.Foo />` → `"Namespace.Foo"`
|
|
1105
|
+
* `<a.b.c />` → `"a.b.c"`
|
|
1106
|
+
*
|
|
1107
|
+
* Returns `null` when the chain root isn't a `JSXIdentifier` (e.g.
|
|
1108
|
+
* the rare-but-valid `<this.x />` case, which JSX surfaces as a
|
|
1109
|
+
* `JSXThisExpression` root).
|
|
1110
|
+
*
|
|
1111
|
+
* Used by `forbid-elements` and `jsx-props-no-spreading`. Two other
|
|
1112
|
+
* rules (`forbid-component-props`, `utils/get-element-type`) keep
|
|
1113
|
+
* their own extended variants because they handle additional node
|
|
1114
|
+
* types (ThisExpression / JSXNamespacedName) and return a non-
|
|
1115
|
+
* nullable string with a sentinel value — semantically distinct.
|
|
1116
|
+
*/
|
|
1117
|
+
const flattenJsxName$1 = (node) => {
|
|
1118
|
+
if (isNodeOfType(node, "JSXIdentifier")) return node.name;
|
|
1119
|
+
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
1120
|
+
const objectName = flattenJsxName$1(node.object);
|
|
1121
|
+
if (!objectName) return null;
|
|
1122
|
+
return `${objectName}.${node.property.name}`;
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
};
|
|
1126
|
+
//#endregion
|
|
1127
|
+
//#region src/plugin/utils/is-member-property.ts
|
|
1128
|
+
const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
|
|
1129
|
+
//#endregion
|
|
1130
|
+
//#region src/plugin/constants/nextjs.ts
|
|
1131
|
+
const NEXTJS_SOURCE_FILE_EXTENSION_GROUP = "(?:tsx?|jsx?|mts|mjs)";
|
|
1132
|
+
const PAGE_FILE_PATTERN = new RegExp(`/page\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1133
|
+
const PAGE_OR_LAYOUT_FILE_PATTERN = new RegExp(`/(page|layout)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1134
|
+
const INTERNAL_PAGE_PATH_PATTERN = /\/(?:(?:\((?:dashboard|admin|settings|account|internal|manage|console|portal|auth|onboarding|app|ee|protected)\))|(?:dashboard|admin|settings|account|internal|manage|console|portal))\//i;
|
|
1135
|
+
const PAGES_DIRECTORY_PATTERN = /\/pages\//;
|
|
1136
|
+
const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
|
|
1137
|
+
"redirect",
|
|
1138
|
+
"permanentRedirect",
|
|
1139
|
+
"notFound",
|
|
1140
|
+
"forbidden",
|
|
1141
|
+
"unauthorized"
|
|
1142
|
+
]);
|
|
1143
|
+
const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
|
|
1144
|
+
const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
|
|
1145
|
+
const APP_DIRECTORY_PATTERN = /\/app\//;
|
|
1146
|
+
const ROUTE_HANDLER_FILE_PATTERN = new RegExp(`/route\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1147
|
+
const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
|
|
1148
|
+
const MUTATING_ROUTE_SEGMENTS = new Set([
|
|
1149
|
+
"logout",
|
|
1150
|
+
"log-out",
|
|
1151
|
+
"signout",
|
|
1152
|
+
"sign-out",
|
|
1153
|
+
"unsubscribe",
|
|
1154
|
+
"delete",
|
|
1155
|
+
"remove",
|
|
1156
|
+
"revoke",
|
|
1157
|
+
"cancel",
|
|
1158
|
+
"deactivate"
|
|
1159
|
+
]);
|
|
1160
|
+
const ERROR_BOUNDARY_FILE_PATTERN = new RegExp(`/(error|global-error)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1161
|
+
const GLOBAL_ERROR_FILE_PATTERN = new RegExp(`/global-error\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1162
|
+
const ROUTE_HANDLER_HTTP_METHODS = new Set([
|
|
1163
|
+
"GET",
|
|
1164
|
+
"POST",
|
|
1165
|
+
"PUT",
|
|
1166
|
+
"PATCH",
|
|
1167
|
+
"DELETE",
|
|
1168
|
+
"OPTIONS",
|
|
1169
|
+
"HEAD"
|
|
1170
|
+
]);
|
|
1171
|
+
const GOOGLE_ANALYTICS_SCRIPT_PATTERN = /google-analytics\.com|googletagmanager\.com\/gtag/;
|
|
1172
|
+
const OG_IMAGE_FILE_PATTERN = new RegExp(`/(opengraph-image|twitter-image)\\d*\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1173
|
+
//#endregion
|
|
826
1174
|
//#region src/plugin/utils/is-nextjs-metadata-image-route-filename.ts
|
|
1175
|
+
const METADATA_IMAGE_ROUTE_FILE_PATTERN = new RegExp(`^(opengraph-image|twitter-image|icon|apple-icon)\\d*\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
827
1176
|
const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
828
1177
|
if (!rawFilename) return false;
|
|
829
|
-
return
|
|
1178
|
+
return METADATA_IMAGE_ROUTE_FILE_PATTERN.test(path.basename(rawFilename));
|
|
830
1179
|
};
|
|
831
1180
|
//#endregion
|
|
832
1181
|
//#region src/plugin/utils/normalize-filename.ts
|
|
833
1182
|
const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
|
|
834
1183
|
//#endregion
|
|
1184
|
+
//#region src/plugin/utils/strip-paren-expression.ts
|
|
1185
|
+
const TS_WRAPPER_TYPES = new Set([
|
|
1186
|
+
"ParenthesizedExpression",
|
|
1187
|
+
"TSAsExpression",
|
|
1188
|
+
"TSSatisfiesExpression",
|
|
1189
|
+
"TSTypeAssertion",
|
|
1190
|
+
"TSNonNullExpression",
|
|
1191
|
+
"TSInstantiationExpression"
|
|
1192
|
+
]);
|
|
1193
|
+
const stripParenExpression = (node) => {
|
|
1194
|
+
let current = node;
|
|
1195
|
+
while (true) {
|
|
1196
|
+
if (TS_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) {
|
|
1197
|
+
current = current.expression;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (isNodeOfType(current, "ChainExpression") && current.expression) {
|
|
1201
|
+
current = current.expression;
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
return current;
|
|
1207
|
+
};
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1210
|
+
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
1211
|
+
const SATORI_MODULE = "satori";
|
|
1212
|
+
const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
|
|
1213
|
+
const isGeneratedImageRenderFilename = (rawFilename) => {
|
|
1214
|
+
if (!rawFilename) return false;
|
|
1215
|
+
return isNextjsMetadataImageRouteFilename(normalizeFilename$1(rawFilename));
|
|
1216
|
+
};
|
|
1217
|
+
const isImageResponseCallee = (contextNode, callee) => {
|
|
1218
|
+
if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
|
|
1219
|
+
if (!isMemberProperty(callee, "ImageResponse")) return false;
|
|
1220
|
+
if (!isNodeOfType(callee.object, "Identifier")) return false;
|
|
1221
|
+
const namespaceIdentifierName = callee.object.name;
|
|
1222
|
+
return IMAGE_RESPONSE_MODULES.some((moduleSource) => isNamespaceImportFromModule(contextNode, namespaceIdentifierName, moduleSource));
|
|
1223
|
+
};
|
|
1224
|
+
const isSatoriCallee = (contextNode, callee) => {
|
|
1225
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
1226
|
+
if (getImportedNameFromModule(contextNode, callee.name, SATORI_MODULE) === "satori") return true;
|
|
1227
|
+
return isDefaultImportFromModule(contextNode, callee.name, SATORI_MODULE);
|
|
1228
|
+
};
|
|
1229
|
+
const isGeneratedImageRendererCall = (node) => {
|
|
1230
|
+
if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return false;
|
|
1231
|
+
if (!isNodeOfType(node.callee, "Identifier") && !isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
1232
|
+
return isImageResponseCallee(node, node.callee) || isSatoriCallee(node, node.callee);
|
|
1233
|
+
};
|
|
1234
|
+
const isComponentIdentifierName = (name) => {
|
|
1235
|
+
const firstCharacter = name[0];
|
|
1236
|
+
return Boolean(firstCharacter && firstCharacter === firstCharacter.toUpperCase());
|
|
1237
|
+
};
|
|
1238
|
+
const isFunctionLike$3 = (node) => Boolean(node && (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")));
|
|
1239
|
+
const markFunctionReturnJsx = (functionNode, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
|
|
1240
|
+
if (!isFunctionLike$3(functionNode)) return;
|
|
1241
|
+
if (isNodeOfType(functionNode, "ArrowFunctionExpression")) {
|
|
1242
|
+
const body = stripParenExpression(functionNode.body);
|
|
1243
|
+
if (!isNodeOfType(body, "BlockStatement")) {
|
|
1244
|
+
markGeneratedImageExpression(body, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const body = functionNode.body;
|
|
1249
|
+
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
1250
|
+
walkAst(body, (descendantNode) => {
|
|
1251
|
+
if (descendantNode !== body && isFunctionLike$3(descendantNode)) return false;
|
|
1252
|
+
if (!isNodeOfType(descendantNode, "ReturnStatement")) return;
|
|
1253
|
+
if (!descendantNode.argument) return;
|
|
1254
|
+
markGeneratedImageExpression(stripParenExpression(descendantNode.argument), programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1255
|
+
});
|
|
1256
|
+
};
|
|
1257
|
+
const hasNormalJsxUsage = (programRoot, componentName, generatedImageJsxNodes) => {
|
|
1258
|
+
let hasNormalUsage = false;
|
|
1259
|
+
walkAst(programRoot, (descendantNode) => {
|
|
1260
|
+
if (hasNormalUsage) return false;
|
|
1261
|
+
if (!isNodeOfType(descendantNode, "JSXOpeningElement")) return;
|
|
1262
|
+
if (generatedImageJsxNodes.has(descendantNode)) return;
|
|
1263
|
+
if (flattenJsxName$1(descendantNode.name) !== componentName) return;
|
|
1264
|
+
hasNormalUsage = true;
|
|
1265
|
+
return false;
|
|
1266
|
+
});
|
|
1267
|
+
return hasNormalUsage;
|
|
1268
|
+
};
|
|
1269
|
+
const markComponentRenderJsx = (programRoot, openingElement, generatedImageJsxNodes, visitedComponentNames) => {
|
|
1270
|
+
const tagName = flattenJsxName$1(openingElement.name);
|
|
1271
|
+
if (!tagName || tagName.includes(".") || !isComponentIdentifierName(tagName)) return;
|
|
1272
|
+
if (visitedComponentNames.has(tagName)) return;
|
|
1273
|
+
if (hasNormalJsxUsage(programRoot, tagName, generatedImageJsxNodes)) return;
|
|
1274
|
+
const binding = findVariableInitializer(openingElement, tagName);
|
|
1275
|
+
if (!binding?.initializer) return;
|
|
1276
|
+
visitedComponentNames.add(tagName);
|
|
1277
|
+
markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1278
|
+
};
|
|
1279
|
+
const isInsideGeneratedImageRendererArgument = (node) => {
|
|
1280
|
+
let cursor = node.parent;
|
|
1281
|
+
while (cursor) {
|
|
1282
|
+
if (isGeneratedImageRendererCall(cursor)) return true;
|
|
1283
|
+
cursor = cursor.parent ?? null;
|
|
1284
|
+
}
|
|
1285
|
+
return false;
|
|
1286
|
+
};
|
|
1287
|
+
const hasNormalFunctionCallUsage = (programRoot, functionName) => {
|
|
1288
|
+
let hasNormalUsage = false;
|
|
1289
|
+
walkAst(programRoot, (descendantNode) => {
|
|
1290
|
+
if (hasNormalUsage) return false;
|
|
1291
|
+
if (!isNodeOfType(descendantNode, "CallExpression")) return;
|
|
1292
|
+
if (!isNodeOfType(descendantNode.callee, "Identifier")) return;
|
|
1293
|
+
if (descendantNode.callee.name !== functionName) return;
|
|
1294
|
+
if (isInsideGeneratedImageRendererArgument(descendantNode)) return;
|
|
1295
|
+
hasNormalUsage = true;
|
|
1296
|
+
return false;
|
|
1297
|
+
});
|
|
1298
|
+
return hasNormalUsage;
|
|
1299
|
+
};
|
|
1300
|
+
const markJsxSubtree = (node, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
|
|
1301
|
+
walkAst(node, (descendantNode) => {
|
|
1302
|
+
if (!isNodeOfType(descendantNode, "JSXOpeningElement")) return;
|
|
1303
|
+
generatedImageJsxNodes.add(descendantNode);
|
|
1304
|
+
markComponentRenderJsx(programRoot, descendantNode, generatedImageJsxNodes, visitedComponentNames);
|
|
1305
|
+
});
|
|
1306
|
+
};
|
|
1307
|
+
const markGeneratedImageExpression = (expression, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
|
|
1308
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
1309
|
+
if (isNodeOfType(unwrappedExpression, "JSXElement") || isNodeOfType(unwrappedExpression, "JSXFragment")) {
|
|
1310
|
+
markJsxSubtree(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (isFunctionLike$3(unwrappedExpression)) {
|
|
1314
|
+
markFunctionReturnJsx(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
1318
|
+
markGeneratedImageExpression(unwrappedExpression.consequent, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1319
|
+
markGeneratedImageExpression(unwrappedExpression.alternate, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
1323
|
+
markGeneratedImageExpression(unwrappedExpression.left, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1324
|
+
markGeneratedImageExpression(unwrappedExpression.right, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
1328
|
+
const callee = unwrappedExpression.callee;
|
|
1329
|
+
if (isFunctionLike$3(callee)) {
|
|
1330
|
+
markFunctionReturnJsx(callee, programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (!isNodeOfType(callee, "Identifier")) return;
|
|
1334
|
+
if (visitedComponentNames.has(callee.name)) return;
|
|
1335
|
+
if (hasNormalJsxUsage(programRoot, callee.name, generatedImageJsxNodes)) return;
|
|
1336
|
+
if (hasNormalFunctionCallUsage(programRoot, callee.name)) return;
|
|
1337
|
+
const binding = findVariableInitializer(callee, callee.name);
|
|
1338
|
+
if (!binding?.initializer || !isFunctionLike$3(stripParenExpression(binding.initializer))) return;
|
|
1339
|
+
visitedComponentNames.add(callee.name);
|
|
1340
|
+
markFunctionReturnJsx(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
1344
|
+
if (visitedComponentNames.has(unwrappedExpression.name)) return;
|
|
1345
|
+
visitedComponentNames.add(unwrappedExpression.name);
|
|
1346
|
+
const binding = findVariableInitializer(unwrappedExpression, unwrappedExpression.name);
|
|
1347
|
+
if (!binding?.initializer) return;
|
|
1348
|
+
markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
const collectGeneratedImageJsxNodes = (programRoot) => {
|
|
1352
|
+
const cached = generatedImageJsxCache.get(programRoot);
|
|
1353
|
+
if (cached) return cached;
|
|
1354
|
+
const generatedImageJsxNodes = /* @__PURE__ */ new WeakSet();
|
|
1355
|
+
walkAst(programRoot, (descendantNode) => {
|
|
1356
|
+
if (!isGeneratedImageRendererCall(descendantNode)) return;
|
|
1357
|
+
for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, generatedImageJsxNodes, /* @__PURE__ */ new Set());
|
|
1358
|
+
});
|
|
1359
|
+
generatedImageJsxCache.set(programRoot, generatedImageJsxNodes);
|
|
1360
|
+
return generatedImageJsxNodes;
|
|
1361
|
+
};
|
|
1362
|
+
const isGeneratedImageRenderContext = (context, node) => {
|
|
1363
|
+
if (isGeneratedImageRenderFilename(context.filename)) return true;
|
|
1364
|
+
if (!node) return false;
|
|
1365
|
+
const programRoot = findProgramRoot(node);
|
|
1366
|
+
if (!programRoot) return false;
|
|
1367
|
+
return collectGeneratedImageJsxNodes(programRoot).has(node);
|
|
1368
|
+
};
|
|
1369
|
+
//#endregion
|
|
835
1370
|
//#region src/plugin/utils/is-hidden-from-screen-reader.ts
|
|
836
1371
|
const isHiddenFromScreenReader = (openingElement, settings) => {
|
|
837
1372
|
if (getElementType(openingElement, settings).toLowerCase() === "input") {
|
|
@@ -879,12 +1414,12 @@ const objectHasAccessibleChild = (jsxElement, settings) => {
|
|
|
879
1414
|
};
|
|
880
1415
|
//#endregion
|
|
881
1416
|
//#region src/plugin/rules/a11y/alt-text.ts
|
|
882
|
-
const MISSING_ALT_PROP = "
|
|
883
|
-
const MISSING_ALT_VALUE = "
|
|
884
|
-
const ARIA_LABEL_VALUE = "
|
|
885
|
-
const ARIA_LABELLEDBY_VALUE = "
|
|
1417
|
+
const MISSING_ALT_PROP = "Screen reader users cannot access this image without `alt`. Add `alt=\"image_description\"`, or `alt=\"\"` if it is decorative.";
|
|
1418
|
+
const MISSING_ALT_VALUE = "Screen reader users cannot access this image because its `alt` is empty or invalid. Add a short description, or `alt=\"\"` if it is decorative.";
|
|
1419
|
+
const ARIA_LABEL_VALUE = "Screen reader users hear nothing here because `aria-label` has no value, so give it a short description.";
|
|
1420
|
+
const ARIA_LABELLEDBY_VALUE = "Screen reader users hear nothing here because `aria-labelledby` has no value, so point it at the id of the text that labels this.";
|
|
886
1421
|
const PREFER_ALT = "Screen readers skip a decorative image more reliably with `alt=\"\"` than `role=\"presentation\"`, so use `alt=\"\"` instead.";
|
|
887
|
-
const MESSAGE_OBJECT = "
|
|
1422
|
+
const MESSAGE_OBJECT = "Screen reader users cannot use this `<object>` because assistive tech cannot describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
|
|
888
1423
|
const MESSAGE_AREA = "Blind users can't use this `<area>` of the image map because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
889
1424
|
const MESSAGE_INPUT_IMAGE = "Blind users can't use this image button because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
890
1425
|
const resolveSettings$52 = (settings) => {
|
|
@@ -1003,7 +1538,7 @@ const altText = defineRule({
|
|
|
1003
1538
|
recommendation: "Give every meaningful image an `alt`, `aria-label`, or `aria-labelledby`.",
|
|
1004
1539
|
category: "Accessibility",
|
|
1005
1540
|
create: (context) => {
|
|
1006
|
-
if (
|
|
1541
|
+
if (isGeneratedImageRenderContext(context)) return {};
|
|
1007
1542
|
const settings = resolveSettings$52(context.settings);
|
|
1008
1543
|
const checkImg = !settings.elements || settings.elements.includes("img");
|
|
1009
1544
|
const checkObject = !settings.elements || settings.elements.includes("object");
|
|
@@ -1014,6 +1549,7 @@ const altText = defineRule({
|
|
|
1014
1549
|
const areaAliases = new Set(settings.area ?? []);
|
|
1015
1550
|
const inputImageAliases = new Set(settings["input[type=\"image\"]"] ?? []);
|
|
1016
1551
|
return { JSXOpeningElement(node) {
|
|
1552
|
+
if (isGeneratedImageRenderContext(context, node)) return;
|
|
1017
1553
|
const tag = getElementType(node, context.settings);
|
|
1018
1554
|
if (checkImg && (tag === "img" || imgAliases.has(tag))) {
|
|
1019
1555
|
imgRule(node, node, context);
|
|
@@ -2212,7 +2748,7 @@ const ABSTRACT_ROLES = new Set([
|
|
|
2212
2748
|
const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
|
|
2213
2749
|
//#endregion
|
|
2214
2750
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
2215
|
-
const buildBaseMessage = (suffix) => `
|
|
2751
|
+
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}`;
|
|
2216
2752
|
const resolveSettings$49 = (settings) => {
|
|
2217
2753
|
const reactDoctor = settings?.["react-doctor"];
|
|
2218
2754
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
|
|
@@ -2226,7 +2762,7 @@ const ariaRole = defineRule({
|
|
|
2226
2762
|
title: "Invalid ARIA role",
|
|
2227
2763
|
tags: ["react-jsx-only"],
|
|
2228
2764
|
severity: "error",
|
|
2229
|
-
recommendation: "Use a real, non-abstract ARIA role.",
|
|
2765
|
+
recommendation: "Use a real, non-abstract ARIA role so assistive tech can expose the element correctly.",
|
|
2230
2766
|
category: "Accessibility",
|
|
2231
2767
|
create: (context) => {
|
|
2232
2768
|
const settings = resolveSettings$49(context.settings);
|
|
@@ -2330,7 +2866,7 @@ const LOOP_TYPES = [
|
|
|
2330
2866
|
"WhileStatement",
|
|
2331
2867
|
"DoWhileStatement"
|
|
2332
2868
|
];
|
|
2333
|
-
const FUNCTION_LIKE_TYPES
|
|
2869
|
+
const FUNCTION_LIKE_TYPES = new Set([
|
|
2334
2870
|
"FunctionDeclaration",
|
|
2335
2871
|
"FunctionExpression",
|
|
2336
2872
|
"ArrowFunctionExpression"
|
|
@@ -3021,7 +3557,7 @@ const asyncDeferAwait = defineRule({
|
|
|
3021
3557
|
title: "await before an early-return guard",
|
|
3022
3558
|
severity: "warn",
|
|
3023
3559
|
tags: ["test-noise"],
|
|
3024
|
-
recommendation: "Move the `await` below the early-return guard so the skip path stays fast",
|
|
3560
|
+
recommendation: "Move the `await` below the early-return guard so the skip path stays fast and avoids unnecessary async work.",
|
|
3025
3561
|
create: (context) => {
|
|
3026
3562
|
const inspectStatements = (statements) => {
|
|
3027
3563
|
for (let statementIndex = 0; statementIndex < statements.length - 1; statementIndex++) {
|
|
@@ -3264,7 +3800,7 @@ const autocompleteValid = defineRule({
|
|
|
3264
3800
|
title: "Invalid autocomplete value",
|
|
3265
3801
|
tags: ["react-jsx-only"],
|
|
3266
3802
|
severity: "warn",
|
|
3267
|
-
recommendation: "Use a valid autofill token in `autoComplete
|
|
3803
|
+
recommendation: "Use a valid autofill token in `autoComplete` so browsers can fill the right field reliably.",
|
|
3268
3804
|
category: "Accessibility",
|
|
3269
3805
|
create: (context) => {
|
|
3270
3806
|
const settings = resolveSettings$48(context.settings);
|
|
@@ -3319,7 +3855,7 @@ const isCreateElementCall = (node) => {
|
|
|
3319
3855
|
//#endregion
|
|
3320
3856
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
3321
3857
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
3322
|
-
const INVALID_MESSAGE = "This button
|
|
3858
|
+
const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
|
|
3323
3859
|
const resolveSettings$47 = (settings) => {
|
|
3324
3860
|
const reactDoctor = settings?.["react-doctor"];
|
|
3325
3861
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
|
|
@@ -3360,7 +3896,7 @@ const buttonHasType = defineRule({
|
|
|
3360
3896
|
id: "button-has-type",
|
|
3361
3897
|
title: "Button missing explicit type",
|
|
3362
3898
|
severity: "warn",
|
|
3363
|
-
recommendation: "
|
|
3899
|
+
recommendation: "Set an explicit button `type` so plain buttons do not submit forms by accident: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
|
|
3364
3900
|
create: (context) => {
|
|
3365
3901
|
const settings = resolveSettings$47(context.settings);
|
|
3366
3902
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -3430,7 +3966,7 @@ const buttonHasType = defineRule({
|
|
|
3430
3966
|
//#endregion
|
|
3431
3967
|
//#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
|
|
3432
3968
|
const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
|
|
3433
|
-
const EXCLUSIVE_MESSAGE = "This input
|
|
3969
|
+
const EXCLUSIVE_MESSAGE = "This input mixes `checked` with `defaultChecked`, so React can't tell whether it is controlled or uncontrolled.";
|
|
3434
3970
|
const resolveSettings$46 = (settings) => {
|
|
3435
3971
|
const reactDoctor = settings?.["react-doctor"];
|
|
3436
3972
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
|
|
@@ -3481,7 +4017,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
3481
4017
|
id: "checked-requires-onchange-or-readonly",
|
|
3482
4018
|
title: "Checked input without onChange",
|
|
3483
4019
|
severity: "warn",
|
|
3484
|
-
recommendation: "Add `onChange`
|
|
4020
|
+
recommendation: "Add `onChange`, `readOnly`, or `defaultChecked` so React knows whether the checkbox is editable, display-only, or uncontrolled.",
|
|
3485
4021
|
category: "Correctness",
|
|
3486
4022
|
create: (context) => {
|
|
3487
4023
|
const settings = resolveSettings$46(context.settings);
|
|
@@ -3628,9 +4164,6 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
3628
4164
|
} })
|
|
3629
4165
|
});
|
|
3630
4166
|
//#endregion
|
|
3631
|
-
//#region src/plugin/utils/is-member-property.ts
|
|
3632
|
-
const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
|
|
3633
|
-
//#endregion
|
|
3634
4167
|
//#region src/plugin/rules/client/client-passive-event-listeners.ts
|
|
3635
4168
|
const clientPassiveEventListeners = defineRule({
|
|
3636
4169
|
id: "client-passive-event-listeners",
|
|
@@ -3670,31 +4203,6 @@ const isReactComponentName = (name) => {
|
|
|
3670
4203
|
return firstCharacter >= 65 && firstCharacter <= 90;
|
|
3671
4204
|
};
|
|
3672
4205
|
//#endregion
|
|
3673
|
-
//#region src/plugin/utils/strip-paren-expression.ts
|
|
3674
|
-
const TS_WRAPPER_TYPES = new Set([
|
|
3675
|
-
"ParenthesizedExpression",
|
|
3676
|
-
"TSAsExpression",
|
|
3677
|
-
"TSSatisfiesExpression",
|
|
3678
|
-
"TSTypeAssertion",
|
|
3679
|
-
"TSNonNullExpression",
|
|
3680
|
-
"TSInstantiationExpression"
|
|
3681
|
-
]);
|
|
3682
|
-
const stripParenExpression = (node) => {
|
|
3683
|
-
let current = node;
|
|
3684
|
-
while (true) {
|
|
3685
|
-
if (TS_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) {
|
|
3686
|
-
current = current.expression;
|
|
3687
|
-
continue;
|
|
3688
|
-
}
|
|
3689
|
-
if (isNodeOfType(current, "ChainExpression") && current.expression) {
|
|
3690
|
-
current = current.expression;
|
|
3691
|
-
continue;
|
|
3692
|
-
}
|
|
3693
|
-
break;
|
|
3694
|
-
}
|
|
3695
|
-
return current;
|
|
3696
|
-
};
|
|
3697
|
-
//#endregion
|
|
3698
4206
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
3699
4207
|
const MESSAGE$48 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
3700
4208
|
const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
|
|
@@ -4003,7 +4511,7 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
4003
4511
|
severity: "warn",
|
|
4004
4512
|
defaultEnabled: false,
|
|
4005
4513
|
category: "Architecture",
|
|
4006
|
-
recommendation: "Collapse
|
|
4514
|
+
recommendation: "Collapse matching padding axes to `p-N` so duplicated classes do not make spacing harder to scan; keep split axes only when breakpoints differ.",
|
|
4007
4515
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
4008
4516
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
4009
4517
|
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
@@ -4013,7 +4521,7 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
4013
4521
|
if (matchedPairs.length === 0) return;
|
|
4014
4522
|
for (const matchedPair of matchedPairs) context.report({
|
|
4015
4523
|
node: jsxAttribute,
|
|
4016
|
-
message: `px-${matchedPair.value}
|
|
4524
|
+
message: `px-${matchedPair.value} and py-${matchedPair.value} duplicate p-${matchedPair.value}, so the class list is noisier without changing spacing.`
|
|
4017
4525
|
});
|
|
4018
4526
|
} })
|
|
4019
4527
|
});
|
|
@@ -4027,7 +4535,7 @@ const noRedundantSizeAxes = defineRule({
|
|
|
4027
4535
|
severity: "warn",
|
|
4028
4536
|
defaultEnabled: false,
|
|
4029
4537
|
category: "Architecture",
|
|
4030
|
-
recommendation: "Collapse
|
|
4538
|
+
recommendation: "Collapse matching width and height to `size-N` so duplicated classes do not make layout harder to scan.",
|
|
4031
4539
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
4032
4540
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
4033
4541
|
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
@@ -4037,7 +4545,7 @@ const noRedundantSizeAxes = defineRule({
|
|
|
4037
4545
|
if (matchedPairs.length === 0) return;
|
|
4038
4546
|
for (const matchedPair of matchedPairs) context.report({
|
|
4039
4547
|
node: jsxAttribute,
|
|
4040
|
-
message: `w-${matchedPair.value}
|
|
4548
|
+
message: `w-${matchedPair.value} and h-${matchedPair.value} duplicate size-${matchedPair.value}, so the class list is noisier without changing layout.`
|
|
4041
4549
|
});
|
|
4042
4550
|
} })
|
|
4043
4551
|
});
|
|
@@ -4072,7 +4580,7 @@ const noSpaceOnFlexChildren = defineRule({
|
|
|
4072
4580
|
const spaceValue = spaceMatch[2];
|
|
4073
4581
|
context.report({
|
|
4074
4582
|
node: jsxAttribute,
|
|
4075
|
-
message: `space-${spaceAxis}-${spaceValue} on a flex or grid parent
|
|
4583
|
+
message: `space-${spaceAxis}-${spaceValue} on a flex or grid parent can leave uneven gaps when children hide, wrap, or render in RTL layouts.`
|
|
4076
4584
|
});
|
|
4077
4585
|
} })
|
|
4078
4586
|
});
|
|
@@ -4085,14 +4593,14 @@ const noThreePeriodEllipsis = defineRule({
|
|
|
4085
4593
|
severity: "warn",
|
|
4086
4594
|
defaultEnabled: false,
|
|
4087
4595
|
category: "Architecture",
|
|
4088
|
-
recommendation: "Use the real ellipsis \"…\"
|
|
4596
|
+
recommendation: "Use the real ellipsis character (\"…\") so UI labels look polished and consistent instead of like three separate periods.",
|
|
4089
4597
|
create: (context) => ({ JSXText(jsxTextNode) {
|
|
4090
4598
|
const textValue = typeof jsxTextNode.value === "string" ? jsxTextNode.value : "";
|
|
4091
4599
|
if (!TRAILING_THREE_PERIOD_ELLIPSIS_PATTERN.test(textValue)) return;
|
|
4092
4600
|
if (isInsideExcludedTypographyAncestor(jsxTextNode)) return;
|
|
4093
4601
|
context.report({
|
|
4094
4602
|
node: jsxTextNode,
|
|
4095
|
-
message: "
|
|
4603
|
+
message: "Use the real ellipsis character (\"…\") instead of three period characters."
|
|
4096
4604
|
});
|
|
4097
4605
|
} })
|
|
4098
4606
|
});
|
|
@@ -4154,7 +4662,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
4154
4662
|
if (!VAGUE_BUTTON_LABELS.has(normalizedLabel)) return;
|
|
4155
4663
|
context.report({
|
|
4156
4664
|
node: jsxElementNode.openingElement ?? jsxElementNode,
|
|
4157
|
-
message: `Screen reader
|
|
4665
|
+
message: `Screen reader users may not know what "${labelText}" does. Use a specific action label.`
|
|
4158
4666
|
});
|
|
4159
4667
|
} })
|
|
4160
4668
|
});
|
|
@@ -4740,10 +5248,10 @@ const effectNeedsCleanup = defineRule({
|
|
|
4740
5248
|
if (usages.length === 0) return;
|
|
4741
5249
|
if (effectHasCleanupReturn(callback, usages)) return;
|
|
4742
5250
|
const firstUsage = usages[0];
|
|
4743
|
-
const
|
|
5251
|
+
const resourceKind = firstUsage.kind === "timer" ? "timer" : "subscription";
|
|
4744
5252
|
context.report({
|
|
4745
5253
|
node,
|
|
4746
|
-
message: `\`${firstUsage.resourceName}
|
|
5254
|
+
message: `\`${firstUsage.resourceName}\` creates a ${resourceKind} in useEffect without returning cleanup. Return a cleanup function so it does not leak after unmount.`
|
|
4747
5255
|
});
|
|
4748
5256
|
} })
|
|
4749
5257
|
});
|
|
@@ -5391,21 +5899,21 @@ const isOutsideAllFunctions = (symbol) => {
|
|
|
5391
5899
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-messages.ts
|
|
5392
5900
|
const buildMissingDepMessage = (hookName, depName) => `\`${hookName}\` can run with a stale \`${depName}\` & show your users old data.`;
|
|
5393
5901
|
const buildUnnecessaryDepMessage = (hookName, depName) => `\`${hookName}\` re-runs whenever \`${depName}\` changes even though it never uses it.`;
|
|
5394
|
-
const buildDuplicateDepMessage = (hookName, depName) => `\`${hookName}\` lists \`${depName}\` twice
|
|
5395
|
-
const buildLiteralDepMessage = (hookName) => `A literal in \`${hookName}\`'s dependency array never changes
|
|
5902
|
+
const buildDuplicateDepMessage = (hookName, depName) => `\`${hookName}\` lists \`${depName}\` twice, adding dependency-array noise without changing when it runs.`;
|
|
5903
|
+
const buildLiteralDepMessage = (hookName) => `A literal in \`${hookName}\`'s dependency array never changes, so it adds noise without protecting against stale values.`;
|
|
5396
5904
|
const buildRefCurrentDepMessage = (hookName, depName) => `\`${hookName}\` won't re-run when \`${depName}\` changes, since a ref never triggers a redraw.`;
|
|
5397
|
-
const buildNonArrayDepsMessage = (hookName) => `\`${hookName}\`'s dependencies can't be checked because its second argument isn't an inline array.`;
|
|
5905
|
+
const buildNonArrayDepsMessage = (hookName) => `\`${hookName}\`'s dependencies can't be checked because its second argument isn't an inline array, so stale values can slip through.`;
|
|
5398
5906
|
const buildMissingDepArrayMessage = (hookName) => `\`${hookName}\` re-runs on every render with no dependency array.`;
|
|
5399
5907
|
const buildMissingCallbackMessage = (hookName) => `\`${hookName}\` crashes without a function as its first argument.`;
|
|
5400
|
-
const buildEffectEventDepMessage = () => `A function from \`useEffectEvent\` is stable
|
|
5401
|
-
const buildSpreadDepMessage = (hookName) =>
|
|
5402
|
-
const buildComplexDepMessage = (hookName) =>
|
|
5908
|
+
const buildEffectEventDepMessage = () => `A function from \`useEffectEvent\` is stable, so listing it adds noise and defeats the event/dependency split.`;
|
|
5909
|
+
const buildSpreadDepMessage = (hookName) => `A spread in \`${hookName}\`'s dependency array hides the actual deps, so stale values can slip through.`;
|
|
5910
|
+
const buildComplexDepMessage = (hookName) => `A complex expression in \`${hookName}\`'s dependency array hides the real value, so stale values can slip through.`;
|
|
5403
5911
|
const buildAsyncEffectMessage = (hookName) => `\`${hookName}\` was given an async function, so its cleanup breaks.`;
|
|
5404
|
-
const buildUnknownCallbackMessage = (hookName) => `\`${hookName}\`'s dependencies can't be checked
|
|
5912
|
+
const buildUnknownCallbackMessage = (hookName) => `\`${hookName}\`'s callback is defined elsewhere, so dependencies can't be checked and stale values can slip through.`;
|
|
5405
5913
|
const buildUnstableDepMessage = (hookName, depName) => `\`${depName}\` is rebuilt every render, so \`${hookName}\` runs every time.`;
|
|
5406
5914
|
const buildSetStateWithoutDepsMessage = (hookName, setterName) => `\`${hookName}\` calls \`${setterName}\` with no dependency array, so it can loop forever & freeze the component.`;
|
|
5407
5915
|
const buildRefCleanupMessage = (depName) => `Your cleanup may read the wrong node since the ref \`${depName}\` can change before it runs.`;
|
|
5408
|
-
const buildAssignmentMessage = (name) => `Assigning to \`${name}\` inside a hook is thrown away after each render.`;
|
|
5916
|
+
const buildAssignmentMessage = (name) => `Assigning to \`${name}\` inside a hook is thrown away after each render, so the next render reads the old value.`;
|
|
5409
5917
|
//#endregion
|
|
5410
5918
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-settings.ts
|
|
5411
5919
|
const resolveExhaustiveDepsSettings = (settings) => {
|
|
@@ -6301,27 +6809,27 @@ const isForbiddenForTag = (entry, tag) => {
|
|
|
6301
6809
|
if (entry.allowedForPatterns.some((regex) => regex.test(tag))) return false;
|
|
6302
6810
|
return true;
|
|
6303
6811
|
};
|
|
6304
|
-
const flattenJsxName
|
|
6812
|
+
const flattenJsxName = (name) => {
|
|
6305
6813
|
if (isNodeOfType(name, "JSXIdentifier")) return name.name;
|
|
6306
|
-
if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName
|
|
6814
|
+
if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName(name.object)}.${name.property.name}`;
|
|
6307
6815
|
if (name.type === "ThisExpression" || name.type === "JSXThisExpression") return "this";
|
|
6308
6816
|
return "";
|
|
6309
6817
|
};
|
|
6310
6818
|
const isSupportedJsxName = (name) => isNodeOfType(name, "JSXIdentifier") || isNodeOfType(name, "JSXMemberExpression");
|
|
6311
|
-
const buildMessage$24 = (propName, message) => message ?? `Your project blocks the \`${propName}\` prop on this component.`;
|
|
6819
|
+
const buildMessage$24 = (propName, message) => message ?? `Your project blocks the \`${propName}\` prop on this component, so this bypasses the component API contract.`;
|
|
6312
6820
|
const forbidComponentProps = defineRule({
|
|
6313
6821
|
id: "forbid-component-props",
|
|
6314
|
-
title: "
|
|
6822
|
+
title: "Blocked component prop bypasses API contract",
|
|
6315
6823
|
severity: "warn",
|
|
6316
6824
|
defaultEnabled: false,
|
|
6317
|
-
recommendation: "
|
|
6825
|
+
recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
|
|
6318
6826
|
category: "Architecture",
|
|
6319
6827
|
create: (context) => {
|
|
6320
6828
|
const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
|
|
6321
6829
|
return { JSXOpeningElement(node) {
|
|
6322
6830
|
if (entries.length === 0) return;
|
|
6323
6831
|
if (!isSupportedJsxName(node.name)) return;
|
|
6324
|
-
const tag = flattenJsxName
|
|
6832
|
+
const tag = flattenJsxName(node.name);
|
|
6325
6833
|
if (!tag) return;
|
|
6326
6834
|
if (!(isReactComponentName(tag.split(".")[0]) || tag.includes("."))) return;
|
|
6327
6835
|
for (const attribute of node.attributes) {
|
|
@@ -6345,7 +6853,7 @@ const forbidComponentProps = defineRule({
|
|
|
6345
6853
|
});
|
|
6346
6854
|
//#endregion
|
|
6347
6855
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
6348
|
-
const buildMessage$23 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags.`;
|
|
6856
|
+
const buildMessage$23 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
|
|
6349
6857
|
const resolveSettings$42 = (settings) => {
|
|
6350
6858
|
const reactDoctor = settings?.["react-doctor"];
|
|
6351
6859
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
@@ -6363,9 +6871,9 @@ const resolveSettings$42 = (settings) => {
|
|
|
6363
6871
|
};
|
|
6364
6872
|
const forbidDomProps = defineRule({
|
|
6365
6873
|
id: "forbid-dom-props",
|
|
6366
|
-
title: "
|
|
6874
|
+
title: "Blocked DOM prop bypasses project contract",
|
|
6367
6875
|
severity: "warn",
|
|
6368
|
-
recommendation: "
|
|
6876
|
+
recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
|
|
6369
6877
|
category: "Architecture",
|
|
6370
6878
|
create: (context) => {
|
|
6371
6879
|
const forbidMap = resolveSettings$42(context.settings);
|
|
@@ -6416,34 +6924,6 @@ const flattenCalleeName = (callee) => {
|
|
|
6416
6924
|
return null;
|
|
6417
6925
|
};
|
|
6418
6926
|
//#endregion
|
|
6419
|
-
//#region src/plugin/utils/flatten-jsx-name.ts
|
|
6420
|
-
/**
|
|
6421
|
-
* Flattens a JSX opener / member-expression chain into a dotted name.
|
|
6422
|
-
*
|
|
6423
|
-
* `<Foo />` → `"Foo"`
|
|
6424
|
-
* `<Namespace.Foo />` → `"Namespace.Foo"`
|
|
6425
|
-
* `<a.b.c />` → `"a.b.c"`
|
|
6426
|
-
*
|
|
6427
|
-
* Returns `null` when the chain root isn't a `JSXIdentifier` (e.g.
|
|
6428
|
-
* the rare-but-valid `<this.x />` case, which JSX surfaces as a
|
|
6429
|
-
* `JSXThisExpression` root).
|
|
6430
|
-
*
|
|
6431
|
-
* Used by `forbid-elements` and `jsx-props-no-spreading`. Two other
|
|
6432
|
-
* rules (`forbid-component-props`, `utils/get-element-type`) keep
|
|
6433
|
-
* their own extended variants because they handle additional node
|
|
6434
|
-
* types (ThisExpression / JSXNamespacedName) and return a non-
|
|
6435
|
-
* nullable string with a sentinel value — semantically distinct.
|
|
6436
|
-
*/
|
|
6437
|
-
const flattenJsxName = (node) => {
|
|
6438
|
-
if (isNodeOfType(node, "JSXIdentifier")) return node.name;
|
|
6439
|
-
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
6440
|
-
const objectName = flattenJsxName(node.object);
|
|
6441
|
-
if (!objectName) return null;
|
|
6442
|
-
return `${objectName}.${node.property.name}`;
|
|
6443
|
-
}
|
|
6444
|
-
return null;
|
|
6445
|
-
};
|
|
6446
|
-
//#endregion
|
|
6447
6927
|
//#region src/plugin/utils/is-react-function-call.ts
|
|
6448
6928
|
const PRAGMA = "React";
|
|
6449
6929
|
const isReactFunctionCall = (node, expectedCall) => {
|
|
@@ -6454,7 +6934,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
6454
6934
|
};
|
|
6455
6935
|
//#endregion
|
|
6456
6936
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
6457
|
-
const buildMessage$22 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here.`;
|
|
6937
|
+
const buildMessage$22 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here, so code stays on the approved UI surface.`;
|
|
6458
6938
|
const resolveSettings$41 = (settings) => {
|
|
6459
6939
|
const reactDoctor = settings?.["react-doctor"];
|
|
6460
6940
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
@@ -6466,16 +6946,16 @@ const resolveSettings$41 = (settings) => {
|
|
|
6466
6946
|
const flattenMemberName$1 = flattenCalleeName;
|
|
6467
6947
|
const forbidElements = defineRule({
|
|
6468
6948
|
id: "forbid-elements",
|
|
6469
|
-
title: "
|
|
6949
|
+
title: "Blocked element bypasses approved UI primitives",
|
|
6470
6950
|
severity: "warn",
|
|
6471
|
-
recommendation: "
|
|
6951
|
+
recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
|
|
6472
6952
|
category: "Architecture",
|
|
6473
6953
|
create: (context) => {
|
|
6474
6954
|
const forbidMap = resolveSettings$41(context.settings);
|
|
6475
6955
|
return {
|
|
6476
6956
|
JSXOpeningElement(node) {
|
|
6477
6957
|
if (forbidMap.size === 0) return;
|
|
6478
|
-
const fullName = flattenJsxName(node.name);
|
|
6958
|
+
const fullName = flattenJsxName$1(node.name);
|
|
6479
6959
|
if (!fullName || !forbidMap.has(fullName)) return;
|
|
6480
6960
|
context.report({
|
|
6481
6961
|
node: node.name,
|
|
@@ -6512,7 +6992,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
6512
6992
|
id: "forward-ref-uses-ref",
|
|
6513
6993
|
title: "forwardRef without ref parameter",
|
|
6514
6994
|
severity: "warn",
|
|
6515
|
-
recommendation: "
|
|
6995
|
+
recommendation: "Accept the `ref` parameter or drop `forwardRef` so parents are not promised a ref that never reaches the node.",
|
|
6516
6996
|
category: "Architecture",
|
|
6517
6997
|
create: (context) => ({ CallExpression(node) {
|
|
6518
6998
|
if (getCalleeName$1(node) !== "forwardRef") return;
|
|
@@ -6573,8 +7053,8 @@ const headingHasContent = defineRule({
|
|
|
6573
7053
|
});
|
|
6574
7054
|
//#endregion
|
|
6575
7055
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
6576
|
-
const REQUIRE_DESTRUCTURE_MESSAGE = "
|
|
6577
|
-
const NAMING_CONVENTION_MESSAGE = "
|
|
7056
|
+
const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
|
|
7057
|
+
const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
|
|
6578
7058
|
const resolveSettings$39 = (settings) => {
|
|
6579
7059
|
const reactDoctor = settings?.["react-doctor"];
|
|
6580
7060
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
@@ -6599,7 +7079,7 @@ const hookUseState = defineRule({
|
|
|
6599
7079
|
title: "useState not destructured",
|
|
6600
7080
|
severity: "warn",
|
|
6601
7081
|
defaultEnabled: false,
|
|
6602
|
-
recommendation: "Destructure useState as `const [thing, setThing] = useState(…)
|
|
7082
|
+
recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
|
|
6603
7083
|
category: "Architecture",
|
|
6604
7084
|
create: (context) => {
|
|
6605
7085
|
const { allowDestructuredState } = resolveSettings$39(context.settings);
|
|
@@ -6677,7 +7157,7 @@ const HOOKS_WITH_DEP_ARRAY = new Set([
|
|
|
6677
7157
|
"useMemo",
|
|
6678
7158
|
"useImperativeHandle"
|
|
6679
7159
|
]);
|
|
6680
|
-
const NAN_MESSAGE = "`NaN` in a dependency array
|
|
7160
|
+
const NAN_MESSAGE = "`NaN` in a dependency array never compares as changed with `Object.is`, so normalize the value before passing it as a dependency.";
|
|
6681
7161
|
const isNanLiteral = (node) => {
|
|
6682
7162
|
if (isNodeOfType(node, "Identifier") && node.name === "NaN") return true;
|
|
6683
7163
|
if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && node.object.name === "Number" && isNodeOfType(node.property, "Identifier") && node.property.name === "NaN") return true;
|
|
@@ -6980,7 +7460,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
6980
7460
|
});
|
|
6981
7461
|
//#endregion
|
|
6982
7462
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
6983
|
-
const MESSAGE$43 = "
|
|
7463
|
+
const MESSAGE$43 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
6984
7464
|
const evaluateTitleValue = (value) => {
|
|
6985
7465
|
if (!value) return "missing";
|
|
6986
7466
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -7010,7 +7490,7 @@ const iframeHasTitle = defineRule({
|
|
|
7010
7490
|
title: "iframe missing title",
|
|
7011
7491
|
tags: ["react-jsx-only"],
|
|
7012
7492
|
severity: "warn",
|
|
7013
|
-
recommendation: "Add a descriptive `title`
|
|
7493
|
+
recommendation: "Add a descriptive `title` so screen reader users know what the embedded frame contains.",
|
|
7014
7494
|
category: "Accessibility",
|
|
7015
7495
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
7016
7496
|
const tag = getElementType(node, context.settings);
|
|
@@ -7078,7 +7558,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
7078
7558
|
id: "iframe-missing-sandbox",
|
|
7079
7559
|
title: "iframe missing sandbox attribute",
|
|
7080
7560
|
severity: "warn",
|
|
7081
|
-
recommendation: "Add `sandbox=\"\"`
|
|
7561
|
+
recommendation: "Add `sandbox=\"\"` or a curated value so embedded pages cannot get full access to your site by default.",
|
|
7082
7562
|
category: "Security",
|
|
7083
7563
|
create: (context) => ({
|
|
7084
7564
|
JSXOpeningElement(node) {
|
|
@@ -7185,8 +7665,10 @@ const imgRedundantAlt = defineRule({
|
|
|
7185
7665
|
recommendation: "Do not put 'image' or 'photo' in alt text. Describe what is shown.",
|
|
7186
7666
|
category: "Accessibility",
|
|
7187
7667
|
create: (context) => {
|
|
7668
|
+
if (isGeneratedImageRenderContext(context)) return {};
|
|
7188
7669
|
const settings = resolveSettings$37(context.settings);
|
|
7189
7670
|
return { JSXOpeningElement(node) {
|
|
7671
|
+
if (isGeneratedImageRenderContext(context, node)) return;
|
|
7190
7672
|
const tag = getElementType(node, context.settings);
|
|
7191
7673
|
if (!settings.components.includes(tag)) return;
|
|
7192
7674
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
@@ -7284,7 +7766,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
7284
7766
|
title: "Interactive element not focusable",
|
|
7285
7767
|
tags: ["react-jsx-only"],
|
|
7286
7768
|
severity: "warn",
|
|
7287
|
-
recommendation: "Add
|
|
7769
|
+
recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
|
|
7288
7770
|
category: "Accessibility",
|
|
7289
7771
|
create: (context) => {
|
|
7290
7772
|
const settings = resolveSettings$36(context.settings);
|
|
@@ -7308,88 +7790,6 @@ const interactiveSupportsFocus = defineRule({
|
|
|
7308
7790
|
}
|
|
7309
7791
|
});
|
|
7310
7792
|
//#endregion
|
|
7311
|
-
//#region src/plugin/utils/find-import-source-for-name.ts
|
|
7312
|
-
const collectFromProgram = (programRoot) => {
|
|
7313
|
-
const lookup = /* @__PURE__ */ new Map();
|
|
7314
|
-
const visit = (node) => {
|
|
7315
|
-
if (node.type === "ImportDeclaration" && "source" in node && node.source) {
|
|
7316
|
-
const source = node.source.value;
|
|
7317
|
-
if (typeof source !== "string") return;
|
|
7318
|
-
if ("specifiers" in node && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
|
|
7319
|
-
if (!("local" in specifier) || !specifier.local) continue;
|
|
7320
|
-
const local = specifier.local;
|
|
7321
|
-
if (typeof local.name !== "string") continue;
|
|
7322
|
-
if (specifier.type === "ImportDefaultSpecifier") lookup.set(local.name, {
|
|
7323
|
-
source,
|
|
7324
|
-
imported: null,
|
|
7325
|
-
isDefault: true,
|
|
7326
|
-
isNamespace: false
|
|
7327
|
-
});
|
|
7328
|
-
else if (specifier.type === "ImportNamespaceSpecifier") lookup.set(local.name, {
|
|
7329
|
-
source,
|
|
7330
|
-
imported: null,
|
|
7331
|
-
isDefault: false,
|
|
7332
|
-
isNamespace: true
|
|
7333
|
-
});
|
|
7334
|
-
else if (specifier.type === "ImportSpecifier") {
|
|
7335
|
-
const importedNode = specifier.imported;
|
|
7336
|
-
const importedName = importedNode?.name ?? (typeof importedNode?.value === "string" ? importedNode.value : null);
|
|
7337
|
-
lookup.set(local.name, {
|
|
7338
|
-
source,
|
|
7339
|
-
imported: importedName,
|
|
7340
|
-
isDefault: false,
|
|
7341
|
-
isNamespace: false
|
|
7342
|
-
});
|
|
7343
|
-
}
|
|
7344
|
-
}
|
|
7345
|
-
return;
|
|
7346
|
-
}
|
|
7347
|
-
const nodeRecord = node;
|
|
7348
|
-
for (const key of Object.keys(nodeRecord)) {
|
|
7349
|
-
if (key === "parent") continue;
|
|
7350
|
-
const child = nodeRecord[key];
|
|
7351
|
-
if (Array.isArray(child)) {
|
|
7352
|
-
for (const item of child) if (isAstNode(item)) visit(item);
|
|
7353
|
-
} else if (isAstNode(child)) visit(child);
|
|
7354
|
-
}
|
|
7355
|
-
};
|
|
7356
|
-
visit(programRoot);
|
|
7357
|
-
return lookup;
|
|
7358
|
-
};
|
|
7359
|
-
const importLookupCache = /* @__PURE__ */ new WeakMap();
|
|
7360
|
-
const getImportLookup = (node) => {
|
|
7361
|
-
const programRoot = findProgramRoot(node);
|
|
7362
|
-
if (!programRoot) return null;
|
|
7363
|
-
let cached = importLookupCache.get(programRoot);
|
|
7364
|
-
if (!cached) {
|
|
7365
|
-
cached = collectFromProgram(programRoot);
|
|
7366
|
-
importLookupCache.set(programRoot, cached);
|
|
7367
|
-
}
|
|
7368
|
-
return cached;
|
|
7369
|
-
};
|
|
7370
|
-
const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
7371
|
-
const lookup = getImportLookup(contextNode);
|
|
7372
|
-
if (!lookup) return false;
|
|
7373
|
-
const info = lookup.get(localIdentifierName);
|
|
7374
|
-
if (!info) return false;
|
|
7375
|
-
return info.source === moduleSource;
|
|
7376
|
-
};
|
|
7377
|
-
const isNamespaceImportFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
7378
|
-
const lookup = getImportLookup(contextNode);
|
|
7379
|
-
if (!lookup) return false;
|
|
7380
|
-
const info = lookup.get(localIdentifierName);
|
|
7381
|
-
if (!info) return false;
|
|
7382
|
-
return info.isNamespace && info.source === moduleSource;
|
|
7383
|
-
};
|
|
7384
|
-
const getImportedNameFromModule = (contextNode, localIdentifierName, moduleSource) => {
|
|
7385
|
-
const lookup = getImportLookup(contextNode);
|
|
7386
|
-
if (!lookup) return null;
|
|
7387
|
-
const info = lookup.get(localIdentifierName);
|
|
7388
|
-
if (!info) return null;
|
|
7389
|
-
if (info.source !== moduleSource) return null;
|
|
7390
|
-
return info.imported;
|
|
7391
|
-
};
|
|
7392
|
-
//#endregion
|
|
7393
7793
|
//#region src/plugin/rules/jotai/jotai-derived-atom-returns-fresh-object.ts
|
|
7394
7794
|
const isAtomFromJotai = (callExpression) => {
|
|
7395
7795
|
if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
|
|
@@ -8184,7 +8584,7 @@ const jsHoistRegexp = defineRule({
|
|
|
8184
8584
|
create: (context) => createLoopAwareVisitors({ NewExpression(node) {
|
|
8185
8585
|
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "RegExp") context.report({
|
|
8186
8586
|
node,
|
|
8187
|
-
message: "
|
|
8587
|
+
message: "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop."
|
|
8188
8588
|
});
|
|
8189
8589
|
} })
|
|
8190
8590
|
});
|
|
@@ -8558,7 +8958,7 @@ const jsSetMapLookups = defineRule({
|
|
|
8558
8958
|
if (isIndexedArrayElementWithStringArgument(node.callee.object, node.arguments?.[0])) return;
|
|
8559
8959
|
context.report({
|
|
8560
8960
|
node,
|
|
8561
|
-
message: `This
|
|
8961
|
+
message: `This scales poorly because \`array.${methodName}()\` inside a loop scans the whole list every time. Use a Set for constant-time lookups.`
|
|
8562
8962
|
});
|
|
8563
8963
|
} })
|
|
8564
8964
|
});
|
|
@@ -8569,7 +8969,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
8569
8969
|
title: "Spread copy before sort()",
|
|
8570
8970
|
tags: ["test-noise"],
|
|
8571
8971
|
severity: "warn",
|
|
8572
|
-
disabledBy: ["react-native"],
|
|
8972
|
+
disabledBy: ["react-native", "pre-es2023"],
|
|
8573
8973
|
recommendation: "Use `array.toSorted()` (ES2023) instead of `[...array].sort()` so you sort without copying the array first",
|
|
8574
8974
|
create: (context) => ({ CallExpression(node) {
|
|
8575
8975
|
if (!isMemberProperty(node.callee, "sort")) return;
|
|
@@ -8582,9 +8982,9 @@ const jsTosortedImmutable = defineRule({
|
|
|
8582
8982
|
});
|
|
8583
8983
|
//#endregion
|
|
8584
8984
|
//#region src/plugin/rules/react-builtins/jsx-boolean-value.ts
|
|
8585
|
-
const NEVER_MESSAGE$2 = () =>
|
|
8586
|
-
const ALWAYS_MESSAGE$2 = () =>
|
|
8587
|
-
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing.`;
|
|
8985
|
+
const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
8986
|
+
const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
8987
|
+
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
|
|
8588
8988
|
const resolveSettings$35 = (settings) => {
|
|
8589
8989
|
const reactDoctor = settings?.["react-doctor"];
|
|
8590
8990
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
@@ -8600,7 +9000,7 @@ const jsxBooleanValue = defineRule({
|
|
|
8600
9000
|
title: "Inconsistent boolean prop notation",
|
|
8601
9001
|
severity: "warn",
|
|
8602
9002
|
defaultEnabled: false,
|
|
8603
|
-
recommendation: "
|
|
9003
|
+
recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
|
|
8604
9004
|
category: "Architecture",
|
|
8605
9005
|
create: (context) => {
|
|
8606
9006
|
const settings = resolveSettings$35(context.settings);
|
|
@@ -8654,8 +9054,8 @@ const jsxBooleanValue = defineRule({
|
|
|
8654
9054
|
});
|
|
8655
9055
|
//#endregion
|
|
8656
9056
|
//#region src/plugin/rules/react-builtins/jsx-curly-brace-presence.ts
|
|
8657
|
-
const UNNECESSARY_BRACES_MESSAGE = "These curly braces
|
|
8658
|
-
const REQUIRED_BRACES_MESSAGE = "This value needs
|
|
9057
|
+
const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
|
|
9058
|
+
const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
|
|
8659
9059
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
8660
9060
|
const resolveSettings$34 = (settings) => {
|
|
8661
9061
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -8750,7 +9150,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
8750
9150
|
title: "Unnecessary curly braces in JSX",
|
|
8751
9151
|
severity: "warn",
|
|
8752
9152
|
defaultEnabled: false,
|
|
8753
|
-
recommendation: "
|
|
9153
|
+
recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
|
|
8754
9154
|
category: "Architecture",
|
|
8755
9155
|
create: (context) => {
|
|
8756
9156
|
const settings = resolveSettings$34(context.settings);
|
|
@@ -8834,8 +9234,8 @@ const containsJsxElement = (root) => {
|
|
|
8834
9234
|
};
|
|
8835
9235
|
//#endregion
|
|
8836
9236
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
8837
|
-
const JSX_NOT_ALLOWED = (extension) => `This file
|
|
8838
|
-
const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are
|
|
9237
|
+
const JSX_NOT_ALLOWED = (extension) => `This file contains JSX but uses a \`${extension}\` name, so the filename no longer signals JSX to readers or tooling conventions.`;
|
|
9238
|
+
const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are reserved for JSX here, so using that extension without JSX makes file-type conventions less useful.`;
|
|
8839
9239
|
const resolveSettings$33 = (settings) => {
|
|
8840
9240
|
const reactDoctor = settings?.["react-doctor"];
|
|
8841
9241
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
@@ -8855,7 +9255,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
8855
9255
|
title: "JSX in disallowed file extension",
|
|
8856
9256
|
severity: "warn",
|
|
8857
9257
|
defaultEnabled: false,
|
|
8858
|
-
recommendation: "Name files with
|
|
9258
|
+
recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
|
|
8859
9259
|
category: "Architecture",
|
|
8860
9260
|
create: (context) => {
|
|
8861
9261
|
const settings = resolveSettings$33(context.settings);
|
|
@@ -8910,8 +9310,8 @@ const isJsxFragmentElement = (node) => {
|
|
|
8910
9310
|
};
|
|
8911
9311
|
//#endregion
|
|
8912
9312
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
8913
|
-
const SYNTAX_MESSAGE = "
|
|
8914
|
-
const ELEMENT_MESSAGE = "
|
|
9313
|
+
const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
|
|
9314
|
+
const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
|
|
8915
9315
|
const resolveSettings$32 = (settings) => {
|
|
8916
9316
|
const reactDoctor = settings?.["react-doctor"];
|
|
8917
9317
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
@@ -8921,7 +9321,7 @@ const jsxFragments = defineRule({
|
|
|
8921
9321
|
title: "Inconsistent fragment syntax",
|
|
8922
9322
|
severity: "warn",
|
|
8923
9323
|
defaultEnabled: false,
|
|
8924
|
-
recommendation: "
|
|
9324
|
+
recommendation: "Use one fragment style so identical wrappers do not look different across files.",
|
|
8925
9325
|
category: "Architecture",
|
|
8926
9326
|
create: (context) => {
|
|
8927
9327
|
const { mode } = resolveSettings$32(context.settings);
|
|
@@ -8949,8 +9349,8 @@ const jsxFragments = defineRule({
|
|
|
8949
9349
|
});
|
|
8950
9350
|
//#endregion
|
|
8951
9351
|
//#region src/plugin/rules/react-builtins/jsx-handler-names.ts
|
|
8952
|
-
const buildHandlerNameMessage = (handlerName, propKey) => `The handler "${handlerName}"
|
|
8953
|
-
const buildHandlerPropMessage = (propKey, propValue) => `The prop "${propKey}" passes
|
|
9352
|
+
const buildHandlerNameMessage = (handlerName, propKey) => `The handler "${handlerName}" does not match the "${propKey}" event prop convention, so readers cannot trace event flow quickly.`;
|
|
9353
|
+
const buildHandlerPropMessage = (propKey, propValue) => `The prop "${propKey}" passes handler "${propValue}" but is not named like an event prop, so callers cannot tell it fires an event.`;
|
|
8954
9354
|
const DEFAULT_HANDLER_PREFIX = "handle";
|
|
8955
9355
|
const DEFAULT_HANDLER_PROP_PREFIX = "on";
|
|
8956
9356
|
const splitPrefixes = (prefixes) => prefixes.split("|").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
@@ -9041,7 +9441,7 @@ const jsxHandlerNames = defineRule({
|
|
|
9041
9441
|
title: "Inconsistent event handler names",
|
|
9042
9442
|
severity: "warn",
|
|
9043
9443
|
defaultEnabled: false,
|
|
9044
|
-
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers.",
|
|
9444
|
+
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
|
|
9045
9445
|
category: "Architecture",
|
|
9046
9446
|
create: (context) => {
|
|
9047
9447
|
const settings = resolveSettings$31(context.settings);
|
|
@@ -9275,7 +9675,7 @@ const jsxKey = defineRule({
|
|
|
9275
9675
|
id: "jsx-key",
|
|
9276
9676
|
title: "Missing key in list",
|
|
9277
9677
|
severity: "error",
|
|
9278
|
-
recommendation: "Add a `key
|
|
9678
|
+
recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
|
|
9279
9679
|
create: (context) => {
|
|
9280
9680
|
const settings = resolveSettings$30(context.settings);
|
|
9281
9681
|
return {
|
|
@@ -9327,190 +9727,6 @@ const jsxKey = defineRule({
|
|
|
9327
9727
|
}
|
|
9328
9728
|
});
|
|
9329
9729
|
//#endregion
|
|
9330
|
-
//#region src/plugin/utils/find-variable-initializer.ts
|
|
9331
|
-
const FUNCTION_LIKE_TYPES = new Set([
|
|
9332
|
-
"FunctionDeclaration",
|
|
9333
|
-
"FunctionExpression",
|
|
9334
|
-
"ArrowFunctionExpression",
|
|
9335
|
-
"MethodDefinition",
|
|
9336
|
-
"Program"
|
|
9337
|
-
]);
|
|
9338
|
-
const findScopeOwner = (node) => {
|
|
9339
|
-
let ancestor = node;
|
|
9340
|
-
while (ancestor) {
|
|
9341
|
-
if (FUNCTION_LIKE_TYPES.has(ancestor.type)) return ancestor;
|
|
9342
|
-
ancestor = ancestor.parent ?? null;
|
|
9343
|
-
}
|
|
9344
|
-
return null;
|
|
9345
|
-
};
|
|
9346
|
-
const findBlockScopeOwner = (declaratorNode, declarationKind) => {
|
|
9347
|
-
if (declarationKind !== "let" && declarationKind !== "const") return findScopeOwner(declaratorNode);
|
|
9348
|
-
let ancestor = declaratorNode.parent;
|
|
9349
|
-
while (ancestor) {
|
|
9350
|
-
if (ancestor.type === "BlockStatement") {
|
|
9351
|
-
const blockParent = ancestor.parent;
|
|
9352
|
-
if (blockParent && (blockParent.type === "FunctionDeclaration" || blockParent.type === "FunctionExpression" || blockParent.type === "ArrowFunctionExpression" || blockParent.type === "MethodDefinition")) return findScopeOwner(declaratorNode);
|
|
9353
|
-
return ancestor;
|
|
9354
|
-
}
|
|
9355
|
-
if (FUNCTION_LIKE_TYPES.has(ancestor.type)) return ancestor;
|
|
9356
|
-
ancestor = ancestor.parent ?? null;
|
|
9357
|
-
}
|
|
9358
|
-
return null;
|
|
9359
|
-
};
|
|
9360
|
-
const collectFromBindingPattern = (pattern, initializer, scopeOwner, out) => {
|
|
9361
|
-
if (isNodeOfType(pattern, "Identifier")) {
|
|
9362
|
-
const list = out.get(pattern.name) ?? [];
|
|
9363
|
-
list.push({
|
|
9364
|
-
bindingIdentifier: pattern,
|
|
9365
|
-
initializer,
|
|
9366
|
-
scopeOwner
|
|
9367
|
-
});
|
|
9368
|
-
out.set(pattern.name, list);
|
|
9369
|
-
return;
|
|
9370
|
-
}
|
|
9371
|
-
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
9372
|
-
for (const property of pattern.properties) if (isNodeOfType(property, "Property")) {
|
|
9373
|
-
const valueNode = property.value;
|
|
9374
|
-
collectFromBindingPattern(valueNode, isNodeOfType(valueNode, "AssignmentPattern") ? valueNode.right : null, scopeOwner, out);
|
|
9375
|
-
} else if (isNodeOfType(property, "RestElement")) collectFromBindingPattern(property.argument, null, scopeOwner, out);
|
|
9376
|
-
return;
|
|
9377
|
-
}
|
|
9378
|
-
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
9379
|
-
for (const element of pattern.elements) {
|
|
9380
|
-
if (!element) continue;
|
|
9381
|
-
collectFromBindingPattern(element, isNodeOfType(element, "AssignmentPattern") ? element.right ?? null : null, scopeOwner, out);
|
|
9382
|
-
}
|
|
9383
|
-
return;
|
|
9384
|
-
}
|
|
9385
|
-
if (isNodeOfType(pattern, "AssignmentPattern")) {
|
|
9386
|
-
collectFromBindingPattern(pattern.left, pattern.right ?? null, scopeOwner, out);
|
|
9387
|
-
return;
|
|
9388
|
-
}
|
|
9389
|
-
if (isNodeOfType(pattern, "RestElement")) collectFromBindingPattern(pattern.argument, null, scopeOwner, out);
|
|
9390
|
-
};
|
|
9391
|
-
const buildBindingIndex = (root) => {
|
|
9392
|
-
const out = /* @__PURE__ */ new Map();
|
|
9393
|
-
const visit = (node) => {
|
|
9394
|
-
if (isNodeOfType(node, "VariableDeclarator")) {
|
|
9395
|
-
const declaration = node.parent;
|
|
9396
|
-
const scopeOwner = findBlockScopeOwner(node, declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : void 0);
|
|
9397
|
-
if (scopeOwner) collectFromBindingPattern(node.id, node.init ?? null, scopeOwner, out);
|
|
9398
|
-
}
|
|
9399
|
-
if ((isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression")) && node.id) {
|
|
9400
|
-
const enclosing = node.parent ? findScopeOwner(node.parent) : null;
|
|
9401
|
-
if (enclosing) {
|
|
9402
|
-
const list = out.get(node.id.name) ?? [];
|
|
9403
|
-
list.push({
|
|
9404
|
-
bindingIdentifier: node.id,
|
|
9405
|
-
initializer: node,
|
|
9406
|
-
scopeOwner: enclosing
|
|
9407
|
-
});
|
|
9408
|
-
out.set(node.id.name, list);
|
|
9409
|
-
}
|
|
9410
|
-
}
|
|
9411
|
-
if ((isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) && node.id) {
|
|
9412
|
-
const enclosing = node.parent ? findScopeOwner(node.parent) : null;
|
|
9413
|
-
if (enclosing) {
|
|
9414
|
-
const list = out.get(node.id.name) ?? [];
|
|
9415
|
-
list.push({
|
|
9416
|
-
bindingIdentifier: node.id,
|
|
9417
|
-
initializer: node,
|
|
9418
|
-
scopeOwner: enclosing
|
|
9419
|
-
});
|
|
9420
|
-
out.set(node.id.name, list);
|
|
9421
|
-
}
|
|
9422
|
-
}
|
|
9423
|
-
if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) {
|
|
9424
|
-
if (Array.isArray(node.params)) for (const param of node.params) {
|
|
9425
|
-
if (!param) continue;
|
|
9426
|
-
collectFromBindingPattern(param, null, node, out);
|
|
9427
|
-
if (isNodeOfType(param, "AssignmentPattern")) collectFromBindingPattern(param.left ?? null, param.right ?? null, node, out);
|
|
9428
|
-
}
|
|
9429
|
-
}
|
|
9430
|
-
if (isNodeOfType(node, "ImportDeclaration")) {
|
|
9431
|
-
const scopeOwner = findScopeOwner(node);
|
|
9432
|
-
if (scopeOwner && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
|
|
9433
|
-
const local = specifier.local;
|
|
9434
|
-
if (local && isNodeOfType(local, "Identifier")) {
|
|
9435
|
-
const list = out.get(local.name) ?? [];
|
|
9436
|
-
list.push({
|
|
9437
|
-
bindingIdentifier: local,
|
|
9438
|
-
initializer: specifier,
|
|
9439
|
-
scopeOwner
|
|
9440
|
-
});
|
|
9441
|
-
out.set(local.name, list);
|
|
9442
|
-
}
|
|
9443
|
-
}
|
|
9444
|
-
}
|
|
9445
|
-
if (node.type === "TSImportEqualsDeclaration" || node.type === "TSEnumDeclaration" || node.type === "TSModuleDeclaration") {
|
|
9446
|
-
const idNode = node.id;
|
|
9447
|
-
if (idNode && idNode.type === "Identifier") {
|
|
9448
|
-
const idObject = idNode;
|
|
9449
|
-
const scopeOwner = findScopeOwner(node);
|
|
9450
|
-
if (scopeOwner && typeof idObject.name === "string") {
|
|
9451
|
-
const list = out.get(idObject.name) ?? [];
|
|
9452
|
-
list.push({
|
|
9453
|
-
bindingIdentifier: idNode,
|
|
9454
|
-
initializer: null,
|
|
9455
|
-
scopeOwner
|
|
9456
|
-
});
|
|
9457
|
-
out.set(idObject.name, list);
|
|
9458
|
-
}
|
|
9459
|
-
}
|
|
9460
|
-
}
|
|
9461
|
-
const nodeRecord = node;
|
|
9462
|
-
for (const key of Object.keys(nodeRecord)) {
|
|
9463
|
-
if (key === "parent") continue;
|
|
9464
|
-
const child = nodeRecord[key];
|
|
9465
|
-
if (Array.isArray(child)) {
|
|
9466
|
-
for (const item of child) if (isAstNode(item)) visit(item);
|
|
9467
|
-
} else if (isAstNode(child)) visit(child);
|
|
9468
|
-
}
|
|
9469
|
-
};
|
|
9470
|
-
visit(root);
|
|
9471
|
-
return out;
|
|
9472
|
-
};
|
|
9473
|
-
const programRootCache = /* @__PURE__ */ new WeakMap();
|
|
9474
|
-
const getBindingIndex = (referenceNode) => {
|
|
9475
|
-
const programRoot = findProgramRoot(referenceNode);
|
|
9476
|
-
if (!programRoot) return null;
|
|
9477
|
-
let index = programRootCache.get(programRoot);
|
|
9478
|
-
if (!index) {
|
|
9479
|
-
index = buildBindingIndex(programRoot);
|
|
9480
|
-
programRootCache.set(programRoot, index);
|
|
9481
|
-
}
|
|
9482
|
-
return index;
|
|
9483
|
-
};
|
|
9484
|
-
const findVariableInitializer = (referenceNode, bindingName) => {
|
|
9485
|
-
const index = getBindingIndex(referenceNode);
|
|
9486
|
-
if (!index) return null;
|
|
9487
|
-
const candidates = index.get(bindingName);
|
|
9488
|
-
if (!candidates || candidates.length === 0) return null;
|
|
9489
|
-
const referenceAncestors = /* @__PURE__ */ new Set();
|
|
9490
|
-
let walker = referenceNode;
|
|
9491
|
-
while (walker) {
|
|
9492
|
-
referenceAncestors.add(walker);
|
|
9493
|
-
walker = walker.parent ?? null;
|
|
9494
|
-
}
|
|
9495
|
-
let best = null;
|
|
9496
|
-
for (const candidate of candidates) {
|
|
9497
|
-
if (!referenceAncestors.has(candidate.scopeOwner)) continue;
|
|
9498
|
-
if (best === null) {
|
|
9499
|
-
best = candidate;
|
|
9500
|
-
continue;
|
|
9501
|
-
}
|
|
9502
|
-
let cursor = candidate.scopeOwner;
|
|
9503
|
-
while (cursor) {
|
|
9504
|
-
if (cursor === best.scopeOwner) {
|
|
9505
|
-
best = candidate;
|
|
9506
|
-
break;
|
|
9507
|
-
}
|
|
9508
|
-
cursor = cursor.parent ?? null;
|
|
9509
|
-
}
|
|
9510
|
-
}
|
|
9511
|
-
return best;
|
|
9512
|
-
};
|
|
9513
|
-
//#endregion
|
|
9514
9730
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
9515
9731
|
const buildMessage$18 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
9516
9732
|
const DEFAULT_MAX_DEPTH = 14;
|
|
@@ -9625,7 +9841,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
9625
9841
|
id: "jsx-no-comment-textnodes",
|
|
9626
9842
|
title: "Comment rendered as JSX text",
|
|
9627
9843
|
severity: "warn",
|
|
9628
|
-
recommendation: "Wrap JSX comments in `{/* … */}` so
|
|
9844
|
+
recommendation: "Wrap JSX comments in `{/* … */}` so users do not see comment text rendered as children.",
|
|
9629
9845
|
create: (context) => ({ JSXText(node) {
|
|
9630
9846
|
if (!hasCommentLikePattern(node.value)) return;
|
|
9631
9847
|
if (isInsideLiteralTextTag(node)) return;
|
|
@@ -9732,7 +9948,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
9732
9948
|
tags: ["react-jsx-only"],
|
|
9733
9949
|
severity: "warn",
|
|
9734
9950
|
disabledBy: ["react-compiler"],
|
|
9735
|
-
recommendation: "Wrap the context value in `useMemo
|
|
9951
|
+
recommendation: "Wrap the context value in `useMemo` or move it outside the component so consumers do not redraw every render.",
|
|
9736
9952
|
category: "Performance",
|
|
9737
9953
|
create: (context) => {
|
|
9738
9954
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -9773,7 +9989,7 @@ const jsxNoDuplicateProps = defineRule({
|
|
|
9773
9989
|
id: "jsx-no-duplicate-props",
|
|
9774
9990
|
title: "Duplicate props on element",
|
|
9775
9991
|
severity: "error",
|
|
9776
|
-
recommendation: "Remove or rename one of the duplicate props.",
|
|
9992
|
+
recommendation: "Remove or rename one of the duplicate props so the later value does not silently override the earlier one.",
|
|
9777
9993
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
9778
9994
|
const seenPropNames = /* @__PURE__ */ new Set();
|
|
9779
9995
|
for (const attribute of node.attributes) {
|
|
@@ -10090,7 +10306,7 @@ const jsxNoJsxAsProp = defineRule({
|
|
|
10090
10306
|
tags: ["react-jsx-only"],
|
|
10091
10307
|
severity: "warn",
|
|
10092
10308
|
disabledBy: ["react-compiler"],
|
|
10093
|
-
recommendation: "Move the JSX outside the component
|
|
10309
|
+
recommendation: "Move the JSX outside the component or wrap it in `useMemo` so memoized children do not redraw every render.",
|
|
10094
10310
|
category: "Performance",
|
|
10095
10311
|
create: (context) => {
|
|
10096
10312
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -10462,7 +10678,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
10462
10678
|
tags: ["react-jsx-only"],
|
|
10463
10679
|
severity: "warn",
|
|
10464
10680
|
disabledBy: ["react-compiler"],
|
|
10465
|
-
recommendation: "Wrap the array in `useMemo
|
|
10681
|
+
recommendation: "Wrap the array in `useMemo` or move it outside the component so memoized children do not redraw every render.",
|
|
10466
10682
|
category: "Performance",
|
|
10467
10683
|
create: (context) => {
|
|
10468
10684
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -10925,7 +11141,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
10925
11141
|
tags: ["react-jsx-only"],
|
|
10926
11142
|
severity: "warn",
|
|
10927
11143
|
disabledBy: ["react-compiler"],
|
|
10928
|
-
recommendation: "Wrap the callback in `useCallback
|
|
11144
|
+
recommendation: "Wrap the callback in `useCallback` or move it outside the component so memoized children do not redraw every render.",
|
|
10929
11145
|
category: "Performance",
|
|
10930
11146
|
create: (context) => {
|
|
10931
11147
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -11232,7 +11448,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
11232
11448
|
tags: ["react-jsx-only"],
|
|
11233
11449
|
severity: "warn",
|
|
11234
11450
|
disabledBy: ["react-compiler"],
|
|
11235
|
-
recommendation: "Wrap the object in `useMemo
|
|
11451
|
+
recommendation: "Wrap the object in `useMemo` or move it outside the component so memoized children do not redraw every render.",
|
|
11236
11452
|
category: "Performance",
|
|
11237
11453
|
create: (context) => {
|
|
11238
11454
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -11292,7 +11508,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
11292
11508
|
id: "jsx-no-script-url",
|
|
11293
11509
|
title: "javascript: URL in JSX",
|
|
11294
11510
|
severity: "error",
|
|
11295
|
-
recommendation: "
|
|
11511
|
+
recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
|
|
11296
11512
|
category: "Security",
|
|
11297
11513
|
create: (context) => {
|
|
11298
11514
|
const options = resolveSettings$28(context.settings);
|
|
@@ -11343,7 +11559,7 @@ const jsxNoUndef = defineRule({
|
|
|
11343
11559
|
id: "jsx-no-undef",
|
|
11344
11560
|
title: "Undefined JSX component",
|
|
11345
11561
|
severity: "error",
|
|
11346
|
-
recommendation: "Import the component or
|
|
11562
|
+
recommendation: "Import the component or fix the typo so React can resolve the JSX identifier at runtime.",
|
|
11347
11563
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
11348
11564
|
const rootIdentifier = getRootIdentifier(node.name);
|
|
11349
11565
|
if (!rootIdentifier) return;
|
|
@@ -11535,7 +11751,7 @@ const jsxPascalCase = defineRule({
|
|
|
11535
11751
|
severity: "warn",
|
|
11536
11752
|
defaultEnabled: false,
|
|
11537
11753
|
tags: ["test-noise"],
|
|
11538
|
-
recommendation: "Rename custom JSX components to PascalCase.",
|
|
11754
|
+
recommendation: "Rename custom JSX components to PascalCase so React treats them as components, not intrinsic DOM tags.",
|
|
11539
11755
|
category: "Architecture",
|
|
11540
11756
|
create: (context) => {
|
|
11541
11757
|
const settings = resolveSettings$26(context.settings);
|
|
@@ -11599,7 +11815,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
11599
11815
|
id: "jsx-props-no-spread-multi",
|
|
11600
11816
|
title: "Same prop spread multiple times",
|
|
11601
11817
|
severity: "warn",
|
|
11602
|
-
recommendation: "Spread
|
|
11818
|
+
recommendation: "Spread each value at most once so later props cannot silently override earlier props from the same object.",
|
|
11603
11819
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
11604
11820
|
const seenNames = /* @__PURE__ */ new Map();
|
|
11605
11821
|
const reportedNames = /* @__PURE__ */ new Set();
|
|
@@ -11639,12 +11855,12 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
11639
11855
|
title: "Props spread onto element",
|
|
11640
11856
|
severity: "warn",
|
|
11641
11857
|
defaultEnabled: false,
|
|
11642
|
-
recommendation: "List each prop explicitly so consumers can see
|
|
11858
|
+
recommendation: "List each prop explicitly so consumers can see the component API instead of receiving hidden spread props.",
|
|
11643
11859
|
category: "Architecture",
|
|
11644
11860
|
create: (context) => {
|
|
11645
11861
|
const settings = resolveSettings$25(context.settings);
|
|
11646
11862
|
return { JSXOpeningElement(node) {
|
|
11647
|
-
const tagName = flattenJsxName(node.name);
|
|
11863
|
+
const tagName = flattenJsxName$1(node.name);
|
|
11648
11864
|
if (!tagName) return;
|
|
11649
11865
|
const isCustom = isReactComponentName(tagName) || tagName.includes(".");
|
|
11650
11866
|
const isHtml = !isCustom;
|
|
@@ -12020,7 +12236,7 @@ const lang = defineRule({
|
|
|
12020
12236
|
title: "Invalid lang attribute value",
|
|
12021
12237
|
tags: ["react-jsx-only"],
|
|
12022
12238
|
severity: "warn",
|
|
12023
|
-
recommendation: "Use a valid language code
|
|
12239
|
+
recommendation: "Use a valid language code like `en` or `en-US` so screen readers choose the right pronunciation rules.",
|
|
12024
12240
|
category: "Accessibility",
|
|
12025
12241
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
12026
12242
|
if (getElementType(node, context.settings) !== "html") return;
|
|
@@ -12047,7 +12263,7 @@ const lang = defineRule({
|
|
|
12047
12263
|
});
|
|
12048
12264
|
//#endregion
|
|
12049
12265
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
12050
|
-
const MESSAGE$32 = "Deaf
|
|
12266
|
+
const MESSAGE$32 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
12051
12267
|
const DEFAULT_AUDIO = ["audio"];
|
|
12052
12268
|
const DEFAULT_VIDEO = ["video"];
|
|
12053
12269
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -12230,50 +12446,6 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
12230
12446
|
}
|
|
12231
12447
|
});
|
|
12232
12448
|
//#endregion
|
|
12233
|
-
//#region src/plugin/constants/nextjs.ts
|
|
12234
|
-
const PAGE_FILE_PATTERN = /\/page\.(tsx?|jsx?)$/;
|
|
12235
|
-
const PAGE_OR_LAYOUT_FILE_PATTERN = /\/(page|layout)\.(tsx?|jsx?)$/;
|
|
12236
|
-
const INTERNAL_PAGE_PATH_PATTERN = /\/(?:(?:\((?:dashboard|admin|settings|account|internal|manage|console|portal|auth|onboarding|app|ee|protected)\))|(?:dashboard|admin|settings|account|internal|manage|console|portal))\//i;
|
|
12237
|
-
const OG_ROUTE_PATTERN = /\/og\b/i;
|
|
12238
|
-
const PAGES_DIRECTORY_PATTERN = /\/pages\//;
|
|
12239
|
-
const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
|
|
12240
|
-
"redirect",
|
|
12241
|
-
"permanentRedirect",
|
|
12242
|
-
"notFound",
|
|
12243
|
-
"forbidden",
|
|
12244
|
-
"unauthorized"
|
|
12245
|
-
]);
|
|
12246
|
-
const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
|
|
12247
|
-
const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
|
|
12248
|
-
const APP_DIRECTORY_PATTERN = /\/app\//;
|
|
12249
|
-
const ROUTE_HANDLER_FILE_PATTERN = /\/route\.(tsx?|jsx?)$/;
|
|
12250
|
-
const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
|
|
12251
|
-
const MUTATING_ROUTE_SEGMENTS = new Set([
|
|
12252
|
-
"logout",
|
|
12253
|
-
"log-out",
|
|
12254
|
-
"signout",
|
|
12255
|
-
"sign-out",
|
|
12256
|
-
"unsubscribe",
|
|
12257
|
-
"delete",
|
|
12258
|
-
"remove",
|
|
12259
|
-
"revoke",
|
|
12260
|
-
"cancel",
|
|
12261
|
-
"deactivate"
|
|
12262
|
-
]);
|
|
12263
|
-
const ERROR_BOUNDARY_FILE_PATTERN = /\/(error|global-error)\.(tsx?|jsx?)$/;
|
|
12264
|
-
const GLOBAL_ERROR_FILE_PATTERN = /\/global-error\.(tsx?|jsx?)$/;
|
|
12265
|
-
const ROUTE_HANDLER_HTTP_METHODS = new Set([
|
|
12266
|
-
"GET",
|
|
12267
|
-
"POST",
|
|
12268
|
-
"PUT",
|
|
12269
|
-
"PATCH",
|
|
12270
|
-
"DELETE",
|
|
12271
|
-
"OPTIONS",
|
|
12272
|
-
"HEAD"
|
|
12273
|
-
]);
|
|
12274
|
-
const GOOGLE_ANALYTICS_SCRIPT_PATTERN = /google-analytics\.com|googletagmanager\.com\/gtag/;
|
|
12275
|
-
const OG_IMAGE_FILE_PATTERN = /\/(opengraph-image|twitter-image)\d*\.(tsx?|jsx?)$/;
|
|
12276
|
-
//#endregion
|
|
12277
12449
|
//#region src/plugin/rules/nextjs/nextjs-error-boundary-missing-use-client.ts
|
|
12278
12450
|
const nextjsErrorBoundaryMissingUseClient = defineRule({
|
|
12279
12451
|
id: "nextjs-error-boundary-missing-use-client",
|
|
@@ -12333,11 +12505,11 @@ const hasJsxAttribute = (attributes, attributeName) => Boolean(findJsxAttribute(
|
|
|
12333
12505
|
//#region src/plugin/rules/nextjs/nextjs-image-missing-sizes.ts
|
|
12334
12506
|
const nextjsImageMissingSizes = defineRule({
|
|
12335
12507
|
id: "nextjs-image-missing-sizes",
|
|
12336
|
-
title: "
|
|
12508
|
+
title: "next/image fill image is missing sizes",
|
|
12337
12509
|
tags: ["test-noise"],
|
|
12338
12510
|
requires: ["nextjs"],
|
|
12339
12511
|
severity: "warn",
|
|
12340
|
-
recommendation: "Add sizes
|
|
12512
|
+
recommendation: "Add `sizes` matching your layout so `next/image` does not assume the largest candidate and make users download oversized images.",
|
|
12341
12513
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
12342
12514
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "Image") return;
|
|
12343
12515
|
const attributes = node.attributes ?? [];
|
|
@@ -12373,11 +12545,11 @@ const nextjsInlineScriptMissingId = defineRule({
|
|
|
12373
12545
|
//#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
|
|
12374
12546
|
const nextjsMissingMetadata = defineRule({
|
|
12375
12547
|
id: "nextjs-missing-metadata",
|
|
12376
|
-
title: "Page missing metadata",
|
|
12548
|
+
title: "Page missing metadata for search previews",
|
|
12377
12549
|
tags: ["test-noise"],
|
|
12378
12550
|
requires: ["nextjs"],
|
|
12379
12551
|
severity: "warn",
|
|
12380
|
-
recommendation: "Add
|
|
12552
|
+
recommendation: "Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",
|
|
12381
12553
|
create: (context) => ({ Program(programNode) {
|
|
12382
12554
|
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12383
12555
|
if (!PAGE_FILE_PATTERN.test(filename)) return;
|
|
@@ -12390,7 +12562,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
12390
12562
|
return false;
|
|
12391
12563
|
})) context.report({
|
|
12392
12564
|
node: programNode,
|
|
12393
|
-
message: "This page has no metadata, so search engines
|
|
12565
|
+
message: "This page has no metadata, so search engines and social previews get no title or description."
|
|
12394
12566
|
});
|
|
12395
12567
|
} })
|
|
12396
12568
|
});
|
|
@@ -12398,7 +12570,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
12398
12570
|
//#region src/plugin/rules/nextjs/nextjs-no-a-element.ts
|
|
12399
12571
|
const nextjsNoAElement = defineRule({
|
|
12400
12572
|
id: "nextjs-no-a-element",
|
|
12401
|
-
title: "Plain anchor
|
|
12573
|
+
title: "Plain anchor reloads internal Next.js links",
|
|
12402
12574
|
tags: ["test-noise"],
|
|
12403
12575
|
requires: ["nextjs"],
|
|
12404
12576
|
severity: "warn",
|
|
@@ -12412,7 +12584,7 @@ const nextjsNoAElement = defineRule({
|
|
|
12412
12584
|
else if (isNodeOfType(hrefAttribute.value, "JSXExpressionContainer") && isNodeOfType(hrefAttribute.value.expression, "Literal")) hrefValue = hrefAttribute.value.expression.value;
|
|
12413
12585
|
if (typeof hrefValue === "string" && hrefValue.startsWith("/")) context.report({
|
|
12414
12586
|
node,
|
|
12415
|
-
message: "Plain <a> reloads the whole page
|
|
12587
|
+
message: "Plain <a> reloads the whole page for internal links, so Next.js loses client-side navigation and prefetching."
|
|
12416
12588
|
});
|
|
12417
12589
|
} })
|
|
12418
12590
|
});
|
|
@@ -12497,11 +12669,11 @@ const nextjsNoClientSideRedirect = defineRule({
|
|
|
12497
12669
|
//#region src/plugin/rules/nextjs/nextjs-no-css-link.ts
|
|
12498
12670
|
const nextjsNoCssLink = defineRule({
|
|
12499
12671
|
id: "nextjs-no-css-link",
|
|
12500
|
-
title: "
|
|
12672
|
+
title: "Linked stylesheet bypasses Next.js CSS optimization",
|
|
12501
12673
|
tags: ["test-noise"],
|
|
12502
12674
|
requires: ["nextjs"],
|
|
12503
12675
|
severity: "warn",
|
|
12504
|
-
recommendation: "Import CSS directly
|
|
12676
|
+
recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
|
|
12505
12677
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
12506
12678
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
|
|
12507
12679
|
const attributes = node.attributes ?? [];
|
|
@@ -12514,7 +12686,7 @@ const nextjsNoCssLink = defineRule({
|
|
|
12514
12686
|
if (typeof hrefValue === "string" && GOOGLE_FONTS_PATTERN.test(hrefValue)) return;
|
|
12515
12687
|
context.report({
|
|
12516
12688
|
node,
|
|
12517
|
-
message: "This <link rel=\"stylesheet\"> loads unbundled
|
|
12689
|
+
message: "This <link rel=\"stylesheet\"> bypasses Next.js CSS handling, so the CSS loads unbundled and unoptimized."
|
|
12518
12690
|
});
|
|
12519
12691
|
} })
|
|
12520
12692
|
});
|
|
@@ -12538,7 +12710,7 @@ const nextjsNoDefaultExportInRouteHandler = defineRule({
|
|
|
12538
12710
|
tags: ["test-noise"],
|
|
12539
12711
|
requires: ["nextjs"],
|
|
12540
12712
|
severity: "error",
|
|
12541
|
-
recommendation: "Replace `export default` with named HTTP method exports
|
|
12713
|
+
recommendation: "Replace `export default` with named HTTP method exports because Next.js ignores default exports in `route.ts`.",
|
|
12542
12714
|
create: (context) => {
|
|
12543
12715
|
let isAppRouteHandler = false;
|
|
12544
12716
|
let programNode = null;
|
|
@@ -12625,11 +12797,11 @@ const nextjsNoFontLink = defineRule({
|
|
|
12625
12797
|
//#region src/plugin/rules/nextjs/nextjs-no-google-analytics-script.ts
|
|
12626
12798
|
const nextjsNoGoogleAnalyticsScript = defineRule({
|
|
12627
12799
|
id: "nextjs-no-google-analytics-script",
|
|
12628
|
-
title: "Manual Google Analytics script",
|
|
12800
|
+
title: "Manual Google Analytics script blocks optimized loading",
|
|
12629
12801
|
tags: ["test-noise"],
|
|
12630
12802
|
requires: ["nextjs"],
|
|
12631
12803
|
severity: "warn",
|
|
12632
|
-
recommendation: "Use `import { GoogleAnalytics } from '@next/third-parties/google'` for automatic optimization
|
|
12804
|
+
recommendation: "Use `import { GoogleAnalytics } from '@next/third-parties/google'` for automatic optimization and smaller bundles.",
|
|
12633
12805
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
12634
12806
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
12635
12807
|
if (node.name.name !== "script" && node.name.name !== "Script") return;
|
|
@@ -12638,7 +12810,7 @@ const nextjsNoGoogleAnalyticsScript = defineRule({
|
|
|
12638
12810
|
const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
|
|
12639
12811
|
if (typeof srcValue === "string" && GOOGLE_ANALYTICS_SCRIPT_PATTERN.test(srcValue)) context.report({
|
|
12640
12812
|
node,
|
|
12641
|
-
message: "Manual Google Analytics
|
|
12813
|
+
message: "Manual Google Analytics scripts block rendering without Next.js' optimized loading strategy."
|
|
12642
12814
|
});
|
|
12643
12815
|
} })
|
|
12644
12816
|
});
|
|
@@ -12650,7 +12822,7 @@ const nextjsNoHeadImport = defineRule({
|
|
|
12650
12822
|
tags: ["test-noise"],
|
|
12651
12823
|
requires: ["nextjs"],
|
|
12652
12824
|
severity: "error",
|
|
12653
|
-
recommendation: "Use the Metadata API
|
|
12825
|
+
recommendation: "Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",
|
|
12654
12826
|
create: (context) => ({ ImportDeclaration(node) {
|
|
12655
12827
|
if (node.source?.value !== "next/head") return;
|
|
12656
12828
|
const filename = normalizeFilename$1(context.filename ?? "");
|
|
@@ -12665,20 +12837,18 @@ const nextjsNoHeadImport = defineRule({
|
|
|
12665
12837
|
//#region src/plugin/rules/nextjs/nextjs-no-img-element.ts
|
|
12666
12838
|
const nextjsNoImgElement = defineRule({
|
|
12667
12839
|
id: "nextjs-no-img-element",
|
|
12668
|
-
title: "Plain img
|
|
12840
|
+
title: "Plain img ships unoptimized images",
|
|
12669
12841
|
tags: ["test-noise"],
|
|
12670
12842
|
requires: ["nextjs"],
|
|
12671
12843
|
severity: "warn",
|
|
12672
|
-
recommendation: "`
|
|
12844
|
+
recommendation: "Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.",
|
|
12673
12845
|
create: (context) => {
|
|
12674
|
-
|
|
12675
|
-
const isOgRoute = OG_ROUTE_PATTERN.test(filename);
|
|
12676
|
-
const isMetadataImageRoute = isNextjsMetadataImageRouteFilename(filename);
|
|
12846
|
+
if (isGeneratedImageRenderContext(context)) return {};
|
|
12677
12847
|
return { JSXOpeningElement(node) {
|
|
12678
|
-
if (
|
|
12848
|
+
if (isGeneratedImageRenderContext(context, node)) return;
|
|
12679
12849
|
if (isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "img") context.report({
|
|
12680
12850
|
node,
|
|
12681
|
-
message: "Plain <img> ships unoptimized, oversized images
|
|
12851
|
+
message: "Plain <img> ships unoptimized, oversized images."
|
|
12682
12852
|
});
|
|
12683
12853
|
} };
|
|
12684
12854
|
}
|
|
@@ -12687,11 +12857,11 @@ const nextjsNoImgElement = defineRule({
|
|
|
12687
12857
|
//#region src/plugin/rules/nextjs/nextjs-no-native-script.ts
|
|
12688
12858
|
const nextjsNoNativeScript = defineRule({
|
|
12689
12859
|
id: "nextjs-no-native-script",
|
|
12690
|
-
title: "Plain script
|
|
12860
|
+
title: "Plain script can block Next.js rendering",
|
|
12691
12861
|
tags: ["test-noise"],
|
|
12692
12862
|
requires: ["nextjs"],
|
|
12693
12863
|
severity: "warn",
|
|
12694
|
-
recommendation: "`
|
|
12864
|
+
recommendation: "Use `next/script` with `strategy=\"afterInteractive\"` or `\"lazyOnload\"` so third-party scripts do not block rendering.",
|
|
12695
12865
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
12696
12866
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
|
|
12697
12867
|
const typeAttribute = findJsxAttribute(node.attributes ?? [], "type");
|
|
@@ -12699,7 +12869,7 @@ const nextjsNoNativeScript = defineRule({
|
|
|
12699
12869
|
if (typeof typeValue === "string" && !EXECUTABLE_SCRIPT_TYPES.has(typeValue)) return;
|
|
12700
12870
|
context.report({
|
|
12701
12871
|
node,
|
|
12702
|
-
message: "Plain <script>
|
|
12872
|
+
message: "Plain <script> has no Next.js loading strategy, so it can block rendering."
|
|
12703
12873
|
});
|
|
12704
12874
|
} })
|
|
12705
12875
|
});
|
|
@@ -12732,7 +12902,7 @@ const nextjsNoRedirectInTryCatch = defineRule({
|
|
|
12732
12902
|
tags: ["test-noise"],
|
|
12733
12903
|
requires: ["nextjs"],
|
|
12734
12904
|
severity: "warn",
|
|
12735
|
-
recommendation: "Move
|
|
12905
|
+
recommendation: "Move `redirect()` or `notFound()` outside the try block, or rethrow in `catch`, because these APIs throw control-flow errors that catch blocks swallow.",
|
|
12736
12906
|
create: (context) => {
|
|
12737
12907
|
let tryCatchDepth = 0;
|
|
12738
12908
|
return {
|
|
@@ -13883,7 +14053,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
|
|
|
13883
14053
|
tags: ["test-noise"],
|
|
13884
14054
|
requires: ["nextjs"],
|
|
13885
14055
|
severity: "warn",
|
|
13886
|
-
recommendation: "Wrap the component using useSearchParams
|
|
14056
|
+
recommendation: "Wrap the component using `useSearchParams` in `<Suspense>` so the rest of the page can stay statically rendered.",
|
|
13887
14057
|
create: (context) => {
|
|
13888
14058
|
let isPageOrLayoutFile = false;
|
|
13889
14059
|
let hasAncestorLayoutSuspense = false;
|
|
@@ -13921,7 +14091,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
|
|
|
13921
14091
|
if (!componentBody || !astContainsUseSearchParams(componentBody)) return;
|
|
13922
14092
|
context.report({
|
|
13923
14093
|
node,
|
|
13924
|
-
message: `<${node.name.name}> uses useSearchParams()
|
|
14094
|
+
message: `<${node.name.name}> uses useSearchParams() outside <Suspense>, so this page falls back to client-side rendering.`
|
|
13925
14095
|
});
|
|
13926
14096
|
}
|
|
13927
14097
|
};
|
|
@@ -13935,7 +14105,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
13935
14105
|
tags: ["test-noise"],
|
|
13936
14106
|
requires: ["nextjs"],
|
|
13937
14107
|
severity: "warn",
|
|
13938
|
-
recommendation: "Use `import { ImageResponse } from \"next/og\"
|
|
14108
|
+
recommendation: "Use `import { ImageResponse } from \"next/og\"`; do not import `@vercel/og` directly because Next.js already bundles it.",
|
|
13939
14109
|
create: (context) => ({ ImportDeclaration(node) {
|
|
13940
14110
|
if (node.source?.value !== "@vercel/og") return;
|
|
13941
14111
|
context.report({
|
|
@@ -14449,7 +14619,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
14449
14619
|
if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isProp(analysis, argRef))) continue;
|
|
14450
14620
|
context.report({
|
|
14451
14621
|
node: callExpr,
|
|
14452
|
-
message: "
|
|
14622
|
+
message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
|
|
14453
14623
|
});
|
|
14454
14624
|
}
|
|
14455
14625
|
} })
|
|
@@ -14462,7 +14632,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
14462
14632
|
title: "aria-hidden on focusable element",
|
|
14463
14633
|
tags: ["react-jsx-only"],
|
|
14464
14634
|
severity: "warn",
|
|
14465
|
-
recommendation: "Remove `aria-hidden` from focusable elements, or stop them being focusable.",
|
|
14635
|
+
recommendation: "Remove `aria-hidden` from focusable elements, or stop them being focusable, so keyboard users do not land on content screen readers hide.",
|
|
14466
14636
|
category: "Accessibility",
|
|
14467
14637
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
14468
14638
|
const ariaHidden = hasJsxPropIgnoreCase(node.attributes, "aria-hidden");
|
|
@@ -15006,7 +15176,7 @@ const noArrayIndexKey = defineRule({
|
|
|
15006
15176
|
title: "Array index used as a key",
|
|
15007
15177
|
severity: "warn",
|
|
15008
15178
|
defaultEnabled: false,
|
|
15009
|
-
recommendation: "Use a stable `key` from your data
|
|
15179
|
+
recommendation: "Use a stable `key` from your data so reordered items keep the right state and DOM.",
|
|
15010
15180
|
category: "Performance",
|
|
15011
15181
|
create: (context) => ({
|
|
15012
15182
|
JSXOpeningElement(node) {
|
|
@@ -15083,7 +15253,7 @@ const noArrayIndexKey = defineRule({
|
|
|
15083
15253
|
});
|
|
15084
15254
|
//#endregion
|
|
15085
15255
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
15086
|
-
const MESSAGE$28 = "
|
|
15256
|
+
const MESSAGE$28 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
15087
15257
|
const resolveSettings$21 = (settings) => {
|
|
15088
15258
|
const reactDoctor = settings?.["react-doctor"];
|
|
15089
15259
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -15317,7 +15487,7 @@ const noCascadingSetState = defineRule({
|
|
|
15317
15487
|
title: "Multiple setState calls in one effect",
|
|
15318
15488
|
severity: "warn",
|
|
15319
15489
|
tags: ["test-noise"],
|
|
15320
|
-
recommendation: "Combine
|
|
15490
|
+
recommendation: "Combine related updates in `useReducer` so one effect does not redraw the screen once per `setState` call.",
|
|
15321
15491
|
create: (context) => ({ CallExpression(node) {
|
|
15322
15492
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
15323
15493
|
if (isInitOnlyEffect(node)) return;
|
|
@@ -15363,12 +15533,12 @@ const noChainStateUpdates = defineRule({
|
|
|
15363
15533
|
});
|
|
15364
15534
|
//#endregion
|
|
15365
15535
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
15366
|
-
const MESSAGE$27 = "
|
|
15536
|
+
const MESSAGE$27 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
15367
15537
|
const noChildrenProp = defineRule({
|
|
15368
15538
|
id: "no-children-prop",
|
|
15369
15539
|
title: "Children passed as a prop",
|
|
15370
15540
|
severity: "warn",
|
|
15371
|
-
recommendation: "Nest children between the tags
|
|
15541
|
+
recommendation: "Nest children between the tags so the rendered content is visible in JSX and cannot be hidden inside a props object.",
|
|
15372
15542
|
create: (context) => ({
|
|
15373
15543
|
JSXAttribute(node) {
|
|
15374
15544
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -15396,13 +15566,13 @@ const noChildrenProp = defineRule({
|
|
|
15396
15566
|
});
|
|
15397
15567
|
//#endregion
|
|
15398
15568
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
15399
|
-
const MESSAGE$26 = "`React.cloneElement`
|
|
15569
|
+
const MESSAGE$26 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
15400
15570
|
const noCloneElement = defineRule({
|
|
15401
15571
|
id: "no-clone-element",
|
|
15402
|
-
title: "
|
|
15572
|
+
title: "cloneElement makes child props fragile",
|
|
15403
15573
|
severity: "warn",
|
|
15404
15574
|
defaultEnabled: false,
|
|
15405
|
-
recommendation: "Pass children
|
|
15575
|
+
recommendation: "Pass children or render props instead so parent code does not depend on fragile cloned child props.",
|
|
15406
15576
|
category: "Architecture",
|
|
15407
15577
|
create: (context) => ({ CallExpression(node) {
|
|
15408
15578
|
const callee = stripParenExpression(node.callee);
|
|
@@ -15634,7 +15804,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
15634
15804
|
title: "Store created during render",
|
|
15635
15805
|
severity: "error",
|
|
15636
15806
|
category: "Correctness",
|
|
15637
|
-
recommendation: "Create
|
|
15807
|
+
recommendation: "Create stores at module scope so subscribers are not cut off and saved state does not reset every render.",
|
|
15638
15808
|
create: (context) => ({ CallExpression(node) {
|
|
15639
15809
|
const factory = resolveStoreFactoryForCallee(node.callee);
|
|
15640
15810
|
if (!factory) return;
|
|
@@ -15650,10 +15820,10 @@ const noCreateStoreInRender = defineRule({
|
|
|
15650
15820
|
const MESSAGE$24 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
15651
15821
|
const noDanger = defineRule({
|
|
15652
15822
|
id: "no-danger",
|
|
15653
|
-
title: "
|
|
15823
|
+
title: "Raw HTML injection can run unsafe markup",
|
|
15654
15824
|
severity: "warn",
|
|
15655
15825
|
category: "Security",
|
|
15656
|
-
recommendation: "Render trusted content as React children
|
|
15826
|
+
recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
|
|
15657
15827
|
create: (context) => ({
|
|
15658
15828
|
JSXOpeningElement(node) {
|
|
15659
15829
|
const propAttribute = hasJsxProp(node.attributes, "dangerouslySetInnerHTML");
|
|
@@ -15739,7 +15909,7 @@ const noDangerWithChildren = defineRule({
|
|
|
15739
15909
|
id: "no-danger-with-children",
|
|
15740
15910
|
title: "dangerouslySetInnerHTML with children",
|
|
15741
15911
|
severity: "error",
|
|
15742
|
-
recommendation: "Use either `children` or `dangerouslySetInnerHTML
|
|
15912
|
+
recommendation: "Use either `children` or `dangerouslySetInnerHTML` so React does not ignore one source of content.",
|
|
15743
15913
|
category: "Correctness",
|
|
15744
15914
|
create: (context) => ({
|
|
15745
15915
|
JSXElement(node) {
|
|
@@ -15898,7 +16068,7 @@ const noDarkModeGlow = defineRule({
|
|
|
15898
16068
|
if (!hasDarkBackground || !shadowValue || !shadowProperty) return;
|
|
15899
16069
|
if (hasColoredGlowShadow(shadowValue)) context.report({
|
|
15900
16070
|
node: shadowProperty,
|
|
15901
|
-
message: "
|
|
16071
|
+
message: "A strong colored glow on a dark background can feel heavy. Use a subtle, neutral shadow instead."
|
|
15902
16072
|
});
|
|
15903
16073
|
} })
|
|
15904
16074
|
});
|
|
@@ -16274,7 +16444,7 @@ const noDerivedUseState = defineRule({
|
|
|
16274
16444
|
title: "Prop derived into useState",
|
|
16275
16445
|
tags: ["test-noise"],
|
|
16276
16446
|
severity: "warn",
|
|
16277
|
-
recommendation: "
|
|
16447
|
+
recommendation: "Compute the value inline so prop changes do not leave `useState` holding a stale copy.",
|
|
16278
16448
|
create: (context) => {
|
|
16279
16449
|
const propStackTracker = createComponentPropStackTracker();
|
|
16280
16450
|
return {
|
|
@@ -16366,7 +16536,7 @@ const noDidMountSetState = defineRule({
|
|
|
16366
16536
|
//#endregion
|
|
16367
16537
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
16368
16538
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
16369
|
-
const MESSAGE$21 = "
|
|
16539
|
+
const MESSAGE$21 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
16370
16540
|
const resolveSettings$19 = (settings) => {
|
|
16371
16541
|
const reactDoctor = settings?.["react-doctor"];
|
|
16372
16542
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -16606,7 +16776,7 @@ const noDistractingElements = defineRule({
|
|
|
16606
16776
|
title: "Distracting marquee or blink element",
|
|
16607
16777
|
tags: ["react-jsx-only"],
|
|
16608
16778
|
severity: "error",
|
|
16609
|
-
recommendation: "Replace `<marquee>` and `<blink>` with normal
|
|
16779
|
+
recommendation: "Replace `<marquee>` and `<blink>` with normal markup so motion does not distract or disorient users.",
|
|
16610
16780
|
category: "Accessibility",
|
|
16611
16781
|
create: (context) => {
|
|
16612
16782
|
const { distractingTags } = resolveSettings$18(context.settings);
|
|
@@ -16635,7 +16805,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
16635
16805
|
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "startViewTransition") return;
|
|
16636
16806
|
context.report({
|
|
16637
16807
|
node,
|
|
16638
|
-
message: "
|
|
16808
|
+
message: "Calling `document.startViewTransition()` directly can bypass React's `<ViewTransition>` animation lifecycle."
|
|
16639
16809
|
});
|
|
16640
16810
|
} })
|
|
16641
16811
|
});
|
|
@@ -16653,13 +16823,13 @@ const noDynamicImportPath = defineRule({
|
|
|
16653
16823
|
if (source && !isNodeOfType(source, "Literal") && !isNodeOfType(source, "TemplateLiteral")) {
|
|
16654
16824
|
context.report({
|
|
16655
16825
|
node,
|
|
16656
|
-
message: "This
|
|
16826
|
+
message: "This can stay in the main bundle because the bundler cannot code-split a dynamic import path. Use a plain string path instead."
|
|
16657
16827
|
});
|
|
16658
16828
|
return;
|
|
16659
16829
|
}
|
|
16660
16830
|
if (isNodeOfType(source, "TemplateLiteral") && (source.expressions?.length ?? 0) > 0) context.report({
|
|
16661
16831
|
node,
|
|
16662
|
-
message: "This
|
|
16832
|
+
message: "This can stay in the main bundle because the bundler cannot code-split a dynamic import path with `${dynamic_path}`. Use a plain string path instead."
|
|
16663
16833
|
});
|
|
16664
16834
|
},
|
|
16665
16835
|
CallExpression(node) {
|
|
@@ -16950,7 +17120,7 @@ const noEffectEventHandler = defineRule({
|
|
|
16950
17120
|
title: "Effect used as an event handler",
|
|
16951
17121
|
tags: ["test-noise"],
|
|
16952
17122
|
severity: "warn",
|
|
16953
|
-
recommendation: "Move
|
|
17123
|
+
recommendation: "Move event logic into the handler that starts it so the side effect does not run late after an extra render.",
|
|
16954
17124
|
create: (context) => {
|
|
16955
17125
|
const propStackTracker = createComponentPropStackTracker();
|
|
16956
17126
|
return {
|
|
@@ -17127,7 +17297,7 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
17127
17297
|
//#region src/plugin/rules/security/no-eval.ts
|
|
17128
17298
|
const noEval = defineRule({
|
|
17129
17299
|
id: "no-eval",
|
|
17130
|
-
title: "
|
|
17300
|
+
title: "eval() runs untrusted code strings",
|
|
17131
17301
|
severity: "error",
|
|
17132
17302
|
recommendation: "Use `JSON.parse` for data, or rewrite the code so it doesn't build and run code from strings.",
|
|
17133
17303
|
create: (context) => ({
|
|
@@ -18000,14 +18170,14 @@ const noFetchInEffect = defineRule({
|
|
|
18000
18170
|
id: "no-fetch-in-effect",
|
|
18001
18171
|
title: "Data fetching inside an effect",
|
|
18002
18172
|
severity: "warn",
|
|
18003
|
-
recommendation: "Use
|
|
18173
|
+
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
18004
18174
|
create: (context) => ({ CallExpression(node) {
|
|
18005
18175
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
18006
18176
|
const callback = getEffectCallback(node);
|
|
18007
18177
|
if (!callback) return;
|
|
18008
18178
|
if (containsFetchCall(callback)) context.report({
|
|
18009
18179
|
node,
|
|
18010
|
-
message: "fetch() inside useEffect
|
|
18180
|
+
message: "fetch() inside useEffect can race, double-fire, or leak. Use a data-fetching layer or Server Component instead."
|
|
18011
18181
|
});
|
|
18012
18182
|
} })
|
|
18013
18183
|
});
|
|
@@ -18021,9 +18191,9 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
18021
18191
|
const MESSAGE$19 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
18022
18192
|
const noFindDomNode = defineRule({
|
|
18023
18193
|
id: "no-find-dom-node",
|
|
18024
|
-
title: "
|
|
18194
|
+
title: "findDOMNode breaks component encapsulation",
|
|
18025
18195
|
severity: "warn",
|
|
18026
|
-
recommendation: "Use a ref
|
|
18196
|
+
recommendation: "Use a ref to reach DOM nodes because `findDOMNode` was removed in React 19 and can crash the app.",
|
|
18027
18197
|
create: (context) => ({ CallExpression(node) {
|
|
18028
18198
|
const callee = node.callee;
|
|
18029
18199
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
@@ -18060,7 +18230,7 @@ const noFlushSync = defineRule({
|
|
|
18060
18230
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
18061
18231
|
if (getImportedName$1(specifier) === "flushSync") context.report({
|
|
18062
18232
|
node: specifier,
|
|
18063
|
-
message: "
|
|
18233
|
+
message: "`flushSync` forces an immediate update, which skips View Transitions and concurrent rendering."
|
|
18064
18234
|
});
|
|
18065
18235
|
}
|
|
18066
18236
|
} })
|
|
@@ -18164,10 +18334,10 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => containsRend
|
|
|
18164
18334
|
//#region src/plugin/rules/architecture/no-giant-component.ts
|
|
18165
18335
|
const noGiantComponent = defineRule({
|
|
18166
18336
|
id: "no-giant-component",
|
|
18167
|
-
title: "
|
|
18337
|
+
title: "Large component is hard to read and change",
|
|
18168
18338
|
severity: "warn",
|
|
18169
18339
|
tags: ["test-noise", "react-jsx-only"],
|
|
18170
|
-
recommendation: "Pull each section into its own component
|
|
18340
|
+
recommendation: "Pull each section into its own component so the parent is easier to read, test, and change.",
|
|
18171
18341
|
create: (context) => {
|
|
18172
18342
|
const getOversizedComponentLineCount = (bodyNode) => {
|
|
18173
18343
|
if (!bodyNode.loc) return null;
|
|
@@ -18405,11 +18575,11 @@ const noInlineBounceEasing = defineRule({
|
|
|
18405
18575
|
if (!value) continue;
|
|
18406
18576
|
if ((key === "transition" || key === "transitionTimingFunction" || key === "animation" || key === "animationTimingFunction") && isOvershootCubicBezier(value)) context.report({
|
|
18407
18577
|
node: property,
|
|
18408
|
-
message: "
|
|
18578
|
+
message: "This bouncy easing can feel distracting. Use ease-out or cubic-bezier(0.16, 1, 0.3, 1) for a smoother finish."
|
|
18409
18579
|
});
|
|
18410
18580
|
if ((key === "animation" || key === "animationName") && hasBounceAnimationName(value)) context.report({
|
|
18411
18581
|
node: property,
|
|
18412
|
-
message: "
|
|
18582
|
+
message: "This bounce animation can feel distracting. Use a smooth ease-out, like ease-out-quart or expo, for a natural finish."
|
|
18413
18583
|
});
|
|
18414
18584
|
}
|
|
18415
18585
|
},
|
|
@@ -18427,7 +18597,7 @@ const noInlineBounceEasing = defineRule({
|
|
|
18427
18597
|
//#region src/plugin/rules/design/no-inline-exhaustive-style.ts
|
|
18428
18598
|
const noInlineExhaustiveStyle = defineRule({
|
|
18429
18599
|
id: "no-inline-exhaustive-style",
|
|
18430
|
-
title: "
|
|
18600
|
+
title: "Large inline style object rebuilds every render",
|
|
18431
18601
|
severity: "warn",
|
|
18432
18602
|
tags: ["test-noise", "react-jsx-only"],
|
|
18433
18603
|
recommendation: "Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.",
|
|
@@ -18547,7 +18717,7 @@ const noInteractiveElementToNoninteractiveRole = defineRule({
|
|
|
18547
18717
|
//#region src/plugin/rules/react-builtins/no-is-mounted.ts
|
|
18548
18718
|
const noIsMounted = defineRule({
|
|
18549
18719
|
id: "no-is-mounted",
|
|
18550
|
-
title: "
|
|
18720
|
+
title: "isMounted lets async callbacks update after unmount",
|
|
18551
18721
|
severity: "warn",
|
|
18552
18722
|
recommendation: "`isMounted` doesn't work in modern React. Track mount state with a ref, or cancel the async work instead.",
|
|
18553
18723
|
create: (context) => ({ CallExpression(node) {
|
|
@@ -18559,7 +18729,7 @@ const noIsMounted = defineRule({
|
|
|
18559
18729
|
if (ancestor.type === "MethodDefinition" || ancestor.type === "Property") {
|
|
18560
18730
|
context.report({
|
|
18561
18731
|
node,
|
|
18562
|
-
message: "`isMounted` is unreliable in modern React
|
|
18732
|
+
message: "`isMounted` is unreliable in modern React, so async callbacks can update state after unmount."
|
|
18563
18733
|
});
|
|
18564
18734
|
return;
|
|
18565
18735
|
}
|
|
@@ -18664,7 +18834,7 @@ const noLargeAnimatedBlur = defineRule({
|
|
|
18664
18834
|
const blurRadius = Number.parseFloat(match[1]);
|
|
18665
18835
|
if (blurRadius > 10) context.report({
|
|
18666
18836
|
node: property,
|
|
18667
|
-
message: `
|
|
18837
|
+
message: `Large animated blurs can use significant GPU memory on phones because blur(${blurRadius}px) gets heavier as the blur and element grow. Use a smaller blur or a smaller element.`
|
|
18668
18838
|
});
|
|
18669
18839
|
}
|
|
18670
18840
|
} })
|
|
@@ -18758,6 +18928,7 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
18758
18928
|
title: "Legacy class lifecycle methods",
|
|
18759
18929
|
severity: "error",
|
|
18760
18930
|
category: "Correctness",
|
|
18931
|
+
requires: ["react"],
|
|
18761
18932
|
tags: ["migration-hint"],
|
|
18762
18933
|
recommendation: "Move `componentWillMount` work to `componentDidMount`, `componentWillReceiveProps` to `componentDidUpdate` or the static `getDerivedStateFromProps`, and `componentWillUpdate` to `getSnapshotBeforeUpdate` plus `componentDidUpdate`. The `UNSAFE_` prefix only hides the warning. React 19 removes both.",
|
|
18763
18934
|
create: (context) => {
|
|
@@ -18801,6 +18972,7 @@ const noLegacyContextApi = defineRule({
|
|
|
18801
18972
|
title: "Legacy context API",
|
|
18802
18973
|
severity: "error",
|
|
18803
18974
|
category: "Correctness",
|
|
18975
|
+
requires: ["react"],
|
|
18804
18976
|
tags: ["migration-hint"],
|
|
18805
18977
|
recommendation: "Swap `childContextTypes` + `getChildContext` for `const MyContext = createContext(...)` and `<MyContext.Provider value={...}>`. Swap `contextTypes` for `static contextType = MyContext` or `useContext()` in a function component. Move the provider and every consumer together, or some consumers read the wrong context.",
|
|
18806
18978
|
create: (context) => {
|
|
@@ -18908,10 +19080,10 @@ const collectBooleanLikePropsFromBody = (componentBody, propsParamName) => {
|
|
|
18908
19080
|
};
|
|
18909
19081
|
const noManyBooleanProps = defineRule({
|
|
18910
19082
|
id: "no-many-boolean-props",
|
|
18911
|
-
title: "
|
|
19083
|
+
title: "Boolean prop combinations are hard to test",
|
|
18912
19084
|
severity: "warn",
|
|
18913
19085
|
tags: ["test-noise", "react-jsx-only"],
|
|
18914
|
-
recommendation: "Split into smaller components or named variants
|
|
19086
|
+
recommendation: "Split boolean-heavy APIs into smaller components or named variants so combinations stay testable.",
|
|
18915
19087
|
create: (context) => {
|
|
18916
19088
|
const reportIfMany = (booleanLikePropNames, componentName, reportNode) => {
|
|
18917
19089
|
if (booleanLikePropNames.length >= 4) context.report({
|
|
@@ -19042,7 +19214,7 @@ const noMoment = defineRule({
|
|
|
19042
19214
|
});
|
|
19043
19215
|
//#endregion
|
|
19044
19216
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
19045
|
-
const MESSAGE$17 = "This file is harder to
|
|
19217
|
+
const MESSAGE$17 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
19046
19218
|
const resolveSettings$16 = (settings) => {
|
|
19047
19219
|
const reactDoctor = settings?.["react-doctor"];
|
|
19048
19220
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -19339,7 +19511,7 @@ const noMultiComp = defineRule({
|
|
|
19339
19511
|
id: "no-multi-comp",
|
|
19340
19512
|
title: "Multiple components in one file",
|
|
19341
19513
|
severity: "warn",
|
|
19342
|
-
recommendation: "Move secondary components into their own files.",
|
|
19514
|
+
recommendation: "Move secondary components into their own files so each component stays easier to find, test, and change.",
|
|
19343
19515
|
category: "Architecture",
|
|
19344
19516
|
create: (context) => {
|
|
19345
19517
|
const settings = resolveSettings$16(context.settings);
|
|
@@ -19419,11 +19591,11 @@ const noMutableInDeps = defineRule({
|
|
|
19419
19591
|
if (!issue) continue;
|
|
19420
19592
|
if (issue.kind === "ref-current") context.report({
|
|
19421
19593
|
node: element,
|
|
19422
|
-
message: `
|
|
19594
|
+
message: `Changing "${issue.rootName}.current" does not re-render the component, so this dependency will not make the effect run again.`
|
|
19423
19595
|
});
|
|
19424
19596
|
else context.report({
|
|
19425
19597
|
node: element,
|
|
19426
|
-
message: `
|
|
19598
|
+
message: `Values like "${issue.rootName}.*" can change without re-rendering the component, so this dependency will not make the effect run again.`
|
|
19427
19599
|
});
|
|
19428
19600
|
}
|
|
19429
19601
|
});
|
|
@@ -19854,7 +20026,7 @@ const noNamespace = defineRule({
|
|
|
19854
20026
|
id: "no-namespace",
|
|
19855
20027
|
title: "Namespaced JSX element",
|
|
19856
20028
|
severity: "warn",
|
|
19857
|
-
recommendation: "
|
|
20029
|
+
recommendation: "Use a plain component or DOM tag because React cannot render JSX namespaced names like `ns:Foo`.",
|
|
19858
20030
|
create: (context) => ({
|
|
19859
20031
|
JSXOpeningElement(node) {
|
|
19860
20032
|
if (!isNodeOfType(node.name, "JSXNamespacedName")) return;
|
|
@@ -19886,7 +20058,7 @@ const noNestedComponentDefinition = defineRule({
|
|
|
19886
20058
|
tags: ["test-noise", "react-jsx-only"],
|
|
19887
20059
|
severity: "error",
|
|
19888
20060
|
category: "Correctness",
|
|
19889
|
-
recommendation: "Move to a separate file
|
|
20061
|
+
recommendation: "Move it to module scope or a separate file so React does not recreate the component and erase its state on every parent render.",
|
|
19890
20062
|
create: (context) => {
|
|
19891
20063
|
const componentStack = [];
|
|
19892
20064
|
return {
|
|
@@ -19951,7 +20123,7 @@ const noNoninteractiveElementInteractions = defineRule({
|
|
|
19951
20123
|
});
|
|
19952
20124
|
//#endregion
|
|
19953
20125
|
//#region src/plugin/rules/a11y/no-noninteractive-element-to-interactive-role.ts
|
|
19954
|
-
const buildMessage$11 = (tag, role) => `
|
|
20126
|
+
const buildMessage$11 = (tag, role) => `Role \`${role}\` gives \`<${tag}>\` interactive semantics even though the element is noninteractive, so screen reader users get the wrong controls.`;
|
|
19955
20127
|
const DEFAULT_ALLOWED_ROLES = {
|
|
19956
20128
|
ul: [
|
|
19957
20129
|
"menu",
|
|
@@ -20790,13 +20962,13 @@ const noRandomKey = defineRule({
|
|
|
20790
20962
|
if (!freshDescription) return;
|
|
20791
20963
|
context.report({
|
|
20792
20964
|
node: node.value,
|
|
20793
|
-
message: `
|
|
20965
|
+
message: `A changing key makes React rebuild each item, which can reset typed input, focus, and scroll position. Use a stable id from the item instead of \`key={${freshDescription}}\`.`
|
|
20794
20966
|
});
|
|
20795
20967
|
} })
|
|
20796
20968
|
});
|
|
20797
20969
|
//#endregion
|
|
20798
20970
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
20799
|
-
const MESSAGE$14 = "`React.Children`
|
|
20971
|
+
const MESSAGE$14 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
20800
20972
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
20801
20973
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
20802
20974
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -20810,10 +20982,10 @@ const isReactNamespaceMember = (node, contextNode) => {
|
|
|
20810
20982
|
};
|
|
20811
20983
|
const noReactChildren = defineRule({
|
|
20812
20984
|
id: "no-react-children",
|
|
20813
|
-
title: "
|
|
20985
|
+
title: "React.Children is fragile when child shape changes",
|
|
20814
20986
|
severity: "warn",
|
|
20815
20987
|
defaultEnabled: false,
|
|
20816
|
-
recommendation: "Pass children as props or render them directly
|
|
20988
|
+
recommendation: "Pass children as explicit props or render them directly so child shape changes do not break traversal logic.",
|
|
20817
20989
|
category: "Architecture",
|
|
20818
20990
|
create: (context) => ({ CallExpression(node) {
|
|
20819
20991
|
const calleeOuter = stripParenExpression(node.callee);
|
|
@@ -20911,7 +21083,7 @@ const reportTestUtilsImports = (node, context) => {
|
|
|
20911
21083
|
};
|
|
20912
21084
|
const noReactDomDeprecatedApis = defineRule({
|
|
20913
21085
|
id: "no-react-dom-deprecated-apis",
|
|
20914
|
-
title: "Deprecated react-dom APIs",
|
|
21086
|
+
title: "Deprecated react-dom APIs break in React 19",
|
|
20915
21087
|
requires: ["react:18"],
|
|
20916
21088
|
tags: ["test-noise", "migration-hint"],
|
|
20917
21089
|
severity: "warn",
|
|
@@ -20928,7 +21100,7 @@ const noReactDomDeprecatedApis = defineRule({
|
|
|
20928
21100
|
});
|
|
20929
21101
|
const noReact19DeprecatedApis = defineRule({
|
|
20930
21102
|
id: "no-react19-deprecated-apis",
|
|
20931
|
-
title: "
|
|
21103
|
+
title: "React 19 API migration can break callers",
|
|
20932
21104
|
requires: ["react:19"],
|
|
20933
21105
|
tags: ["test-noise", "migration-hint"],
|
|
20934
21106
|
severity: "warn",
|
|
@@ -21036,7 +21208,7 @@ const noRedundantRoles = defineRule({
|
|
|
21036
21208
|
title: "Redundant ARIA role",
|
|
21037
21209
|
tags: ["react-jsx-only"],
|
|
21038
21210
|
severity: "warn",
|
|
21039
|
-
recommendation: "Remove `role` attributes
|
|
21211
|
+
recommendation: "Remove redundant `role` attributes so assistive tech reads the element's native semantics without extra noise.",
|
|
21040
21212
|
category: "Accessibility",
|
|
21041
21213
|
create: (context) => {
|
|
21042
21214
|
const settings = resolveSettings$13(context.settings);
|
|
@@ -21106,7 +21278,7 @@ const noRenderInRender = defineRule({
|
|
|
21106
21278
|
title: "Component rendered by inline function call",
|
|
21107
21279
|
severity: "warn",
|
|
21108
21280
|
tags: ["test-noise"],
|
|
21109
|
-
recommendation: "Make it a named component
|
|
21281
|
+
recommendation: "Make it a named component so React preserves its identity and does not remount its state.",
|
|
21110
21282
|
create: (context) => ({ JSXExpressionContainer(node) {
|
|
21111
21283
|
const expression = node.expression;
|
|
21112
21284
|
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
@@ -21125,7 +21297,7 @@ const noRenderInRender = defineRule({
|
|
|
21125
21297
|
const RENDER_PROP_PATTERN = /^render[A-Z]/;
|
|
21126
21298
|
const noRenderPropChildren = defineRule({
|
|
21127
21299
|
id: "no-render-prop-children",
|
|
21128
|
-
title: "
|
|
21300
|
+
title: "Render-prop slots make this component hard to extend",
|
|
21129
21301
|
tags: ["test-noise"],
|
|
21130
21302
|
severity: "warn",
|
|
21131
21303
|
recommendation: "Swap `renderXxx` props for child components like `<Modal.Header>` or plain `children`, so the parent doesn't control every slot.",
|
|
@@ -21305,12 +21477,22 @@ const PUBLIC_CLIENT_KEY_PATTERNS = [
|
|
|
21305
21477
|
/^public-token-(?:live|test)-/,
|
|
21306
21478
|
/^pk\.eyJ/
|
|
21307
21479
|
];
|
|
21480
|
+
const SECRET_UNAMBIGUOUS_PLACEHOLDER_VALUE_PATTERNS = [
|
|
21481
|
+
/^[\s._\-*\u2022xX]{8,}$/,
|
|
21482
|
+
/(?:\.{3,}|\u2026|[*\u2022]{3,})/,
|
|
21483
|
+
/(?:^|[_\-\s])(?:your|redacted|masked|placeholder|replace[_\-\s]?me|changeme)(?:$|[_\-\s])/i,
|
|
21484
|
+
/<[^>]*(?:auth|credential|key|password|secret|token|your|redacted|placeholder|masked)[^>]*>/i,
|
|
21485
|
+
/\[[^\]]*(?:auth|credential|key|password|secret|token|your|redacted|placeholder|masked)[^\]]*\]/i,
|
|
21486
|
+
/\{[^}]*(?:auth|credential|key|password|secret|token|your|redacted|placeholder|masked)[^}]*\}/i
|
|
21487
|
+
];
|
|
21488
|
+
const SECRET_CONTEXTUAL_PLACEHOLDER_VALUE_PATTERNS = [/(?:^|[_\-\s])(?:example|sample|dummy)(?:$|[_\-\s])/i];
|
|
21489
|
+
const SECRET_PLACEHOLDER_CONTEXT_PATTERN = /(?:placeholder|example|sample|dummy|masked|redacted|mask)/i;
|
|
21308
21490
|
const SECRET_VARIABLE_PATTERN = /(?:api_?key|secret|token|password|credential|auth)/i;
|
|
21309
21491
|
const SECRET_TOOLING_FILE_PATTERN = /(?:^|\/)[^/]+\.config\.[cm]?[jt]s$/;
|
|
21310
21492
|
const SECRET_TOOLING_RC_FILE_PATTERN = /(?:^|\/)(?:\.[a-z-]+rc|[a-z-]+\.rc)\.[cm]?[jt]s$/;
|
|
21311
21493
|
const SECRET_TEST_FILE_PATTERN = /(?:^|\/)[^/]+\.(?:test|spec|stories|story|fixture|fixtures)\.[cm]?[jt]sx?$/;
|
|
21312
21494
|
const SECRET_SERVER_FILE_SUFFIX_PATTERN = /(?:^|\/)[^/]+\.server\.[cm]?[jt]sx?$/;
|
|
21313
|
-
const SECRET_SERVER_ENTRY_FILE_PATTERN = /(?:^|\/)(?:middleware|route)\.[cm]?[jt]sx?$/;
|
|
21495
|
+
const SECRET_SERVER_ENTRY_FILE_PATTERN = /(?:^|\/)(?:middleware|proxy|route)\.[cm]?[jt]sx?$/;
|
|
21314
21496
|
const SECRET_NEXT_PAGES_API_FILE_PATTERN = /(?:^|\/)pages\/api\/.+\.[cm]?[jt]sx?$/;
|
|
21315
21497
|
const SECRET_CLIENT_FILE_SUFFIX_PATTERN = /(?:^|\/)[^/]+\.(?:client|browser|web)\.[cm]?[jt]sx?$/;
|
|
21316
21498
|
const SECRET_CLIENT_ENTRY_FILE_PATTERN = /(?:^|\/)(?:src\/)?(?:main|index|[Aa]pp|client)\.[cm]?[jt]sx?$/;
|
|
@@ -21590,6 +21772,15 @@ const isInsideServerOnlyScope = (node) => {
|
|
|
21590
21772
|
return false;
|
|
21591
21773
|
};
|
|
21592
21774
|
//#endregion
|
|
21775
|
+
//#region src/plugin/utils/is-placeholder-secret-value.ts
|
|
21776
|
+
const isPlaceholderSecretValue = (literalValue, options) => {
|
|
21777
|
+
const trimmedValue = literalValue.trim();
|
|
21778
|
+
if (trimmedValue.length === 0) return false;
|
|
21779
|
+
if (SECRET_UNAMBIGUOUS_PLACEHOLDER_VALUE_PATTERNS.some((pattern) => pattern.test(trimmedValue))) return true;
|
|
21780
|
+
if (!options.allowContextualExamples) return false;
|
|
21781
|
+
return SECRET_CONTEXTUAL_PLACEHOLDER_VALUE_PATTERNS.some((pattern) => pattern.test(trimmedValue));
|
|
21782
|
+
};
|
|
21783
|
+
//#endregion
|
|
21593
21784
|
//#region src/plugin/rules/security/no-secrets-in-client-code.ts
|
|
21594
21785
|
const noSecretsInClientCode = defineRule({
|
|
21595
21786
|
id: "no-secrets-in-client-code",
|
|
@@ -21618,21 +21809,28 @@ const noSecretsInClientCode = defineRule({
|
|
|
21618
21809
|
if (!isNodeOfType(node.init, "Literal") || typeof node.init.value !== "string") return;
|
|
21619
21810
|
const variableName = node.id.name;
|
|
21620
21811
|
const literalValue = node.init.value;
|
|
21812
|
+
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
21813
|
+
const hasPlaceholderContext = SECRET_PLACEHOLDER_CONTEXT_PATTERN.test(variableName) || componentOrHookName !== null && SECRET_PLACEHOLDER_CONTEXT_PATTERN.test(componentOrHookName);
|
|
21814
|
+
const isUnambiguousPlaceholderValue = isPlaceholderSecretValue(literalValue, { allowContextualExamples: false });
|
|
21815
|
+
const isPlaceholderValueForVariableHeuristic = isPlaceholderSecretValue(literalValue, { allowContextualExamples: hasPlaceholderContext });
|
|
21621
21816
|
if (PUBLIC_CLIENT_KEY_PATTERNS.some((pattern) => pattern.test(literalValue))) return;
|
|
21817
|
+
if (SECRET_PATTERNS.some((pattern) => pattern.test(literalValue))) {
|
|
21818
|
+
if (!isUnambiguousPlaceholderValue) context.report({
|
|
21819
|
+
node,
|
|
21820
|
+
message: "This hardcoded secret is a security vulnerability: it ships to the browser where anyone can read it."
|
|
21821
|
+
});
|
|
21822
|
+
return;
|
|
21823
|
+
}
|
|
21622
21824
|
const isServerOnlyScope = isInsideServerOnlyScope(node);
|
|
21623
21825
|
const trailingSuffix = getIdentifierTrailingWord(variableName);
|
|
21624
21826
|
const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
|
|
21625
|
-
if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && literalValue.length > 24) {
|
|
21827
|
+
if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && !isPlaceholderValueForVariableHeuristic && literalValue.length > 24) {
|
|
21626
21828
|
context.report({
|
|
21627
21829
|
node,
|
|
21628
21830
|
message: `Hardcoding "${variableName}" in client code is a security vulnerability: the secret ships to the browser where anyone can read it.`
|
|
21629
21831
|
});
|
|
21630
21832
|
return;
|
|
21631
21833
|
}
|
|
21632
|
-
if (SECRET_PATTERNS.some((pattern) => pattern.test(literalValue))) context.report({
|
|
21633
|
-
node,
|
|
21634
|
-
message: "This hardcoded secret is a security vulnerability: it ships to the browser where anyone can read it."
|
|
21635
|
-
});
|
|
21636
21834
|
}
|
|
21637
21835
|
};
|
|
21638
21836
|
}
|
|
@@ -21975,7 +22173,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
21975
22173
|
reportedStateNames.add(stateName);
|
|
21976
22174
|
context.report({
|
|
21977
22175
|
node: setterCall,
|
|
21978
|
-
message: `${setterCall.callee.name}()
|
|
22176
|
+
message: `${setterCall.callee.name}() updates \`${stateName}\`, which is also in this effect's dependency list. Guard the update or move the derivation out of the effect.`
|
|
21979
22177
|
});
|
|
21980
22178
|
}
|
|
21981
22179
|
}
|
|
@@ -22007,13 +22205,13 @@ const getParentComponent = (node) => {
|
|
|
22007
22205
|
};
|
|
22008
22206
|
//#endregion
|
|
22009
22207
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
22010
|
-
const MESSAGE$12 = "
|
|
22208
|
+
const MESSAGE$12 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
22011
22209
|
const noSetState = defineRule({
|
|
22012
22210
|
id: "no-set-state",
|
|
22013
|
-
title: "
|
|
22211
|
+
title: "Local class state forbidden",
|
|
22014
22212
|
severity: "warn",
|
|
22015
22213
|
defaultEnabled: false,
|
|
22016
|
-
recommendation: "Lift state up or use an external store
|
|
22214
|
+
recommendation: "Lift state up or use an external store so class-local state does not hide ownership.",
|
|
22017
22215
|
category: "Architecture",
|
|
22018
22216
|
create: (context) => ({ CallExpression(node) {
|
|
22019
22217
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
@@ -22055,7 +22253,7 @@ const noSetStateInRender = defineRule({
|
|
|
22055
22253
|
const setterIdentifierName = setterCall.callee.name;
|
|
22056
22254
|
context.report({
|
|
22057
22255
|
node: setterCall,
|
|
22058
|
-
message: `${setterIdentifierName}()
|
|
22256
|
+
message: `${setterIdentifierName}() triggers another render while rendering. Move it to an effect or event handler, or compute the value during render.`
|
|
22059
22257
|
});
|
|
22060
22258
|
}
|
|
22061
22259
|
};
|
|
@@ -22289,9 +22487,9 @@ const resolveSettings$11 = (settings) => {
|
|
|
22289
22487
|
};
|
|
22290
22488
|
const noStringRefs = defineRule({
|
|
22291
22489
|
id: "no-string-refs",
|
|
22292
|
-
title: "
|
|
22490
|
+
title: "String refs are legacy and fragile",
|
|
22293
22491
|
severity: "warn",
|
|
22294
|
-
recommendation: "Use a callback ref
|
|
22492
|
+
recommendation: "Use a callback ref or `useRef` so ref ownership is explicit and not tied to legacy string lookup.",
|
|
22295
22493
|
create: (context) => {
|
|
22296
22494
|
const { noTemplateLiterals = false } = resolveSettings$11(context.settings);
|
|
22297
22495
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
@@ -22370,7 +22568,7 @@ const noThisInSfc = defineRule({
|
|
|
22370
22568
|
id: "no-this-in-sfc",
|
|
22371
22569
|
title: "this used in function component",
|
|
22372
22570
|
severity: "warn",
|
|
22373
|
-
recommendation: "Read from the `props` argument
|
|
22571
|
+
recommendation: "Read from the `props` argument because function components do not have a React instance `this`.",
|
|
22374
22572
|
create: (context) => {
|
|
22375
22573
|
const configured = (context.settings?.react)?.createClass;
|
|
22376
22574
|
const customClassFactoryNames = /* @__PURE__ */ new Set();
|
|
@@ -22514,10 +22712,10 @@ const noUncontrolledInput = defineRule({
|
|
|
22514
22712
|
const hasAllowedPartner = VALUE_PARTNER_ATTRIBUTES.some((partnerAttributeName) => findJsxAttribute(attributes, partnerAttributeName));
|
|
22515
22713
|
if (isNodeOfType(valueAttribute.value, "JSXExpressionContainer") && isNodeOfType(valueAttribute.value.expression, "Identifier") && undefinedInitialStateNames.has(valueAttribute.value.expression.name)) {
|
|
22516
22714
|
const stateName = valueAttribute.value.expression.name;
|
|
22517
|
-
const partnerHint = hasAllowedPartner ? "Give useState a starting value" : "Give useState a starting value
|
|
22715
|
+
const partnerHint = hasAllowedPartner ? "Give useState a starting value" : "Give useState a starting value and add onChange (or readOnly)";
|
|
22518
22716
|
context.report({
|
|
22519
22717
|
node: child,
|
|
22520
|
-
message: `
|
|
22718
|
+
message: `This can trigger a console warning and reset the field because "${stateName}" starts undefined, so <${tagName} value={${stateName}}> flips from uncontrolled to controlled. ${partnerHint} (e.g. \`useState("")\`).`
|
|
22521
22719
|
});
|
|
22522
22720
|
return;
|
|
22523
22721
|
}
|
|
@@ -22579,7 +22777,7 @@ const noUnescapedEntities = defineRule({
|
|
|
22579
22777
|
title: "Unescaped entities in JSX",
|
|
22580
22778
|
severity: "warn",
|
|
22581
22779
|
defaultEnabled: false,
|
|
22582
|
-
recommendation: "Replace bare `'` / `\"` / `>` / `}` characters
|
|
22780
|
+
recommendation: "Replace bare `'` / `\"` / `>` / `}` characters with HTML entities so literal UI text is encoded consistently.",
|
|
22583
22781
|
create: (context) => ({ JSXText(node) {
|
|
22584
22782
|
const value = node.value;
|
|
22585
22783
|
for (const character of value) if (character in ESCAPED_VERSIONS) {
|
|
@@ -23514,11 +23712,11 @@ const noUnknownProperty = defineRule({
|
|
|
23514
23712
|
id: "no-unknown-property",
|
|
23515
23713
|
title: "Unknown DOM property",
|
|
23516
23714
|
severity: "warn",
|
|
23517
|
-
recommendation: "Use the prop name React expects, like `className`, `htmlFor`, or `tabIndex
|
|
23715
|
+
recommendation: "Use the prop name React expects, like `className`, `htmlFor`, or `tabIndex`, so the attribute is applied correctly.",
|
|
23518
23716
|
create: (context) => {
|
|
23519
23717
|
const { ignore = [], requireDataLowercase = false } = resolveSettings$10(context.settings);
|
|
23520
23718
|
const ignoreSet = new Set(ignore);
|
|
23521
|
-
if (
|
|
23719
|
+
if (isGeneratedImageRenderContext(context)) ignoreSet.add("tw");
|
|
23522
23720
|
let fileIsNonReactJsx = false;
|
|
23523
23721
|
return {
|
|
23524
23722
|
Program(node) {
|
|
@@ -23553,6 +23751,7 @@ const noUnknownProperty = defineRule({
|
|
|
23553
23751
|
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
23554
23752
|
const actualName = getJsxAttributeName(attribute.name);
|
|
23555
23753
|
if (!actualName) continue;
|
|
23754
|
+
if (actualName === "tw" && isGeneratedImageRenderContext(context, node)) continue;
|
|
23556
23755
|
if (ignoreSet.has(actualName)) continue;
|
|
23557
23756
|
if (isValidDataAttribute(actualName)) {
|
|
23558
23757
|
if (requireDataLowercase && hasUppercaseChar(actualName)) context.report({
|
|
@@ -23599,7 +23798,7 @@ const UNSAFE_ALIASES = new Set([
|
|
|
23599
23798
|
"componentWillReceiveProps",
|
|
23600
23799
|
"componentWillUpdate"
|
|
23601
23800
|
]);
|
|
23602
|
-
const buildMessage$6 = (methodName) => `\`${methodName}\`
|
|
23801
|
+
const buildMessage$6 = (methodName) => `\`${methodName}\` runs during unsafe legacy render timing and is deprecated, so React may double-invoke or remove it.`;
|
|
23603
23802
|
const resolveSettings$9 = (settings) => {
|
|
23604
23803
|
const reactDoctor = settings?.["react-doctor"];
|
|
23605
23804
|
return { checkAliases: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noUnsafe ?? {} : {}).checkAliases ?? false };
|
|
@@ -23632,7 +23831,7 @@ const noUnsafe = defineRule({
|
|
|
23632
23831
|
id: "no-unsafe",
|
|
23633
23832
|
title: "Unsafe legacy lifecycle method",
|
|
23634
23833
|
severity: "warn",
|
|
23635
|
-
recommendation: "
|
|
23834
|
+
recommendation: "Move setup to `constructor` or `componentDidMount`, prop-derived state to `getDerivedStateFromProps`, and update side effects to `componentDidUpdate` so React does not rely on deprecated unsafe lifecycles.",
|
|
23636
23835
|
create: (context) => {
|
|
23637
23836
|
const { checkAliases } = resolveSettings$9(context.settings);
|
|
23638
23837
|
const flagsUnsafePrefix = isReactVersionAtLeast(getConfiguredReactMajorMinor(context.settings), 16, 3);
|
|
@@ -23888,7 +24087,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
23888
24087
|
id: "no-unstable-nested-components",
|
|
23889
24088
|
title: "Component defined inside a component",
|
|
23890
24089
|
severity: "warn",
|
|
23891
|
-
recommendation: "Move nested components to
|
|
24090
|
+
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
23892
24091
|
category: "Performance",
|
|
23893
24092
|
create: (context) => {
|
|
23894
24093
|
const settings = resolveSettings$8(context.settings);
|
|
@@ -24062,7 +24261,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
24062
24261
|
//#endregion
|
|
24063
24262
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
24064
24263
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
24065
|
-
const MESSAGE$9 = "
|
|
24264
|
+
const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
24066
24265
|
const resolveSettings$7 = (settings) => {
|
|
24067
24266
|
const reactDoctor = settings?.["react-doctor"];
|
|
24068
24267
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -24085,7 +24284,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
24085
24284
|
id: "no-will-update-set-state",
|
|
24086
24285
|
title: "setState in componentWillUpdate",
|
|
24087
24286
|
severity: "warn",
|
|
24088
|
-
recommendation: "
|
|
24287
|
+
recommendation: "Avoid setState in componentWillUpdate because it can loop forever; derive state before render or move guarded updates to componentDidUpdate.",
|
|
24089
24288
|
create: (context) => {
|
|
24090
24289
|
const { mode } = resolveSettings$7(context.settings);
|
|
24091
24290
|
const activeLifecycleNames = isReactBelow16_3(context.settings) ? new Set(["componentWillUpdate"]) : LIFECYCLE_NAMES;
|
|
@@ -24119,7 +24318,7 @@ const noZIndex9999 = defineRule({
|
|
|
24119
24318
|
const zValue = getStylePropertyNumberValue(property);
|
|
24120
24319
|
if (zValue !== null && Math.abs(zValue) >= 1e3) context.report({
|
|
24121
24320
|
node: property,
|
|
24122
|
-
message: `z-index ${zValue} is
|
|
24321
|
+
message: `z-index ${zValue} is unusually high and can hide a layering bug instead of fixing it. Use a small set scale, like 1 to 50.`
|
|
24123
24322
|
});
|
|
24124
24323
|
}
|
|
24125
24324
|
},
|
|
@@ -24302,12 +24501,12 @@ const UTILITY_FILE_BASENAMES = new Set([
|
|
|
24302
24501
|
]);
|
|
24303
24502
|
//#endregion
|
|
24304
24503
|
//#region src/plugin/rules/react-builtins/only-export-components.ts
|
|
24305
|
-
const NAMED_EXPORT_MESSAGE = "Fast Refresh
|
|
24306
|
-
const ANONYMOUS_MESSAGE = "Fast Refresh can't track
|
|
24307
|
-
const EXPORT_ALL_MESSAGE = "`export *` hides what's exported, so Fast Refresh
|
|
24308
|
-
const REACT_CONTEXT_MESSAGE = "
|
|
24309
|
-
const LOCAL_COMPONENT_MESSAGE = "Fast Refresh skips
|
|
24310
|
-
const NO_EXPORT_MESSAGE = "Fast Refresh can't track
|
|
24504
|
+
const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh can't safely preserve component state.";
|
|
24505
|
+
const ANONYMOUS_MESSAGE = "This component is unnamed, so Fast Refresh can't track it and falls back to a full reload.";
|
|
24506
|
+
const EXPORT_ALL_MESSAGE = "`export *` hides what's exported, so Fast Refresh can't safely preserve component state.";
|
|
24507
|
+
const REACT_CONTEXT_MESSAGE = "This file exports a context with components, so Fast Refresh can't safely preserve component state.";
|
|
24508
|
+
const LOCAL_COMPONENT_MESSAGE = "This component is not exported, so Fast Refresh skips it and local edits can full-reload.";
|
|
24509
|
+
const NO_EXPORT_MESSAGE = "This file exports nothing, so Fast Refresh can't track the component and local edits can full-reload.";
|
|
24311
24510
|
const DEFAULT_REACT_HOCS = [
|
|
24312
24511
|
"memo",
|
|
24313
24512
|
"forwardRef",
|
|
@@ -24460,7 +24659,7 @@ const onlyExportComponents = defineRule({
|
|
|
24460
24659
|
id: "only-export-components",
|
|
24461
24660
|
title: "Non-component export in component file",
|
|
24462
24661
|
severity: "warn",
|
|
24463
|
-
recommendation: "Move non-component exports out of files
|
|
24662
|
+
recommendation: "Move non-component exports out of component files so Fast Refresh can preserve component state instead of full-reloading.",
|
|
24464
24663
|
category: "Architecture",
|
|
24465
24664
|
create: (context) => {
|
|
24466
24665
|
const settings = resolveSettings$6(context.settings);
|
|
@@ -24681,10 +24880,10 @@ const isChildrenMemberExpression = (node) => {
|
|
|
24681
24880
|
};
|
|
24682
24881
|
const preactNoChildrenLength = defineRule({
|
|
24683
24882
|
id: "preact-no-children-length",
|
|
24684
|
-
title: "Array methods on
|
|
24883
|
+
title: "Array methods on Preact children can crash",
|
|
24685
24884
|
requires: ["preact"],
|
|
24686
24885
|
severity: "warn",
|
|
24687
|
-
recommendation: "Wrap with `toChildArray(children)`
|
|
24886
|
+
recommendation: "Wrap with `toChildArray(children)` because Preact's `props.children` is not always an array and array methods can crash.",
|
|
24688
24887
|
create: (context) => ({ MemberExpression(node) {
|
|
24689
24888
|
if (node.computed) return;
|
|
24690
24889
|
if (!isNodeOfType(node.property, "Identifier")) return;
|
|
@@ -24718,10 +24917,10 @@ const REACT_HOOK_NAMES = new Set([
|
|
|
24718
24917
|
const buildMessage$4 = (importedNames) => `Your users hit \`__H\` undefined errors because importing ${importedNames.map((innerName) => `\`${innerName}\``).join(", ")} from \`react\` in a pure-Preact project loads a second copy of the hook state, so import from \`preact/hooks\` (or \`preact/compat\`) instead.`;
|
|
24719
24918
|
const preactNoReactHooksImport = defineRule({
|
|
24720
24919
|
id: "preact-no-react-hooks-import",
|
|
24721
|
-
title: "
|
|
24920
|
+
title: "React hook imports break pure Preact hook state",
|
|
24722
24921
|
requires: ["pure-preact"],
|
|
24723
24922
|
severity: "warn",
|
|
24724
|
-
recommendation: "
|
|
24923
|
+
recommendation: "Import hooks from `preact/hooks` so they share Preact's renderer state instead of loading a second hook implementation.",
|
|
24725
24924
|
create: (context) => ({ ImportDeclaration(node) {
|
|
24726
24925
|
const source = node.source;
|
|
24727
24926
|
if (!isNodeOfType(source, "Literal") || source.value !== "react") return;
|
|
@@ -24781,7 +24980,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
24781
24980
|
title: "render() reads props from arguments",
|
|
24782
24981
|
requires: ["preact"],
|
|
24783
24982
|
severity: "warn",
|
|
24784
|
-
recommendation: "Read
|
|
24983
|
+
recommendation: "Read from `this.props` and `this.state` because `preact/compat` uses React's parameterless `render()` and positional props/state become undefined.",
|
|
24785
24984
|
create: (context) => ({ MethodDefinition(node) {
|
|
24786
24985
|
if (!isInstanceMethodNamedRender(node)) return;
|
|
24787
24986
|
if (!isInsideEs6Component$1(node)) return;
|
|
@@ -24803,7 +25002,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
24803
25002
|
title: "onDoubleClick instead of onDblClick",
|
|
24804
25003
|
requires: ["pure-preact"],
|
|
24805
25004
|
severity: "warn",
|
|
24806
|
-
recommendation: "Rename
|
|
25005
|
+
recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
|
|
24807
25006
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
24808
25007
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
24809
25008
|
const tagName = node.name.name;
|
|
@@ -24875,8 +25074,8 @@ const preferDynamicImport = defineRule({
|
|
|
24875
25074
|
});
|
|
24876
25075
|
//#endregion
|
|
24877
25076
|
//#region src/plugin/rules/react-builtins/prefer-es6-class.ts
|
|
24878
|
-
const ALWAYS_MESSAGE$1 = "`createReactClass` is legacy
|
|
24879
|
-
const NEVER_MESSAGE$1 = "This component is
|
|
25077
|
+
const ALWAYS_MESSAGE$1 = "`createReactClass` is legacy and adds a dependency, so this component diverges from modern React class syntax.";
|
|
25078
|
+
const NEVER_MESSAGE$1 = "This component uses an ES6 class where `createReactClass` is configured, so component style is inconsistent across the codebase.";
|
|
24880
25079
|
const resolveSettings$5 = (settings) => {
|
|
24881
25080
|
const reactDoctor = settings?.["react-doctor"];
|
|
24882
25081
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
@@ -24887,7 +25086,7 @@ const preferEs6Class = defineRule({
|
|
|
24887
25086
|
title: "createClass instead of ES6 class",
|
|
24888
25087
|
severity: "warn",
|
|
24889
25088
|
defaultEnabled: false,
|
|
24890
|
-
recommendation: "Pick one component style
|
|
25089
|
+
recommendation: "Pick one component style so readers do not have to switch between legacy `createReactClass` patterns and modern class components.",
|
|
24891
25090
|
category: "Architecture",
|
|
24892
25091
|
create: (context) => {
|
|
24893
25092
|
const { mode = "always" } = resolveSettings$5(context.settings);
|
|
@@ -25046,7 +25245,7 @@ const preferExplicitVariants = defineRule({
|
|
|
25046
25245
|
});
|
|
25047
25246
|
//#endregion
|
|
25048
25247
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
25049
|
-
const MESSAGE$7 = "This class component
|
|
25248
|
+
const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
25050
25249
|
const resolveSettings$4 = (settings) => {
|
|
25051
25250
|
const reactDoctor = settings?.["react-doctor"];
|
|
25052
25251
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -25072,7 +25271,7 @@ const preferFunctionComponent = defineRule({
|
|
|
25072
25271
|
title: "Class component instead of function",
|
|
25073
25272
|
severity: "warn",
|
|
25074
25273
|
defaultEnabled: false,
|
|
25075
|
-
recommendation: "
|
|
25274
|
+
recommendation: "Rewrite the class component as a function component so state and effects use modern hook patterns instead of class lifecycles.",
|
|
25076
25275
|
category: "Architecture",
|
|
25077
25276
|
create: (context) => {
|
|
25078
25277
|
const settings = resolveSettings$4(context.settings);
|
|
@@ -25174,12 +25373,12 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
25174
25373
|
const child = nodeRecord[key];
|
|
25175
25374
|
if (Array.isArray(child)) for (const item of child) {
|
|
25176
25375
|
if (!isAstNode(item)) continue;
|
|
25177
|
-
if (FUNCTION_LIKE_TYPES
|
|
25376
|
+
if (FUNCTION_LIKE_TYPES.has(item.type)) continue;
|
|
25178
25377
|
visit(item);
|
|
25179
25378
|
if (returnsObject) return;
|
|
25180
25379
|
}
|
|
25181
25380
|
else if (isAstNode(child)) {
|
|
25182
|
-
if (FUNCTION_LIKE_TYPES
|
|
25381
|
+
if (FUNCTION_LIKE_TYPES.has(child.type)) continue;
|
|
25183
25382
|
visit(child);
|
|
25184
25383
|
}
|
|
25185
25384
|
}
|
|
@@ -25424,7 +25623,7 @@ const preferTagOverRole = defineRule({
|
|
|
25424
25623
|
title: "Role used instead of HTML tag",
|
|
25425
25624
|
tags: ["react-jsx-only"],
|
|
25426
25625
|
severity: "warn",
|
|
25427
|
-
recommendation: "
|
|
25626
|
+
recommendation: "Use the matching HTML element when one exists so browsers and assistive tech get native semantics.",
|
|
25428
25627
|
category: "Accessibility",
|
|
25429
25628
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
25430
25629
|
const tag = getElementType(node, context.settings);
|
|
@@ -25730,7 +25929,7 @@ const preferUseReducer = defineRule({
|
|
|
25730
25929
|
title: "Many related useState calls",
|
|
25731
25930
|
tags: ["test-noise"],
|
|
25732
25931
|
severity: "warn",
|
|
25733
|
-
recommendation: "Group related state
|
|
25932
|
+
recommendation: "Group related state in `useReducer` so one logical update does not fan out into separate renders.",
|
|
25734
25933
|
create: (context) => {
|
|
25735
25934
|
const reportExcessiveUseState = (body, componentName) => {
|
|
25736
25935
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
@@ -25762,7 +25961,7 @@ const preferUseReducer = defineRule({
|
|
|
25762
25961
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
25763
25962
|
const queryDestructureResult = defineRule({
|
|
25764
25963
|
id: "query-destructure-result",
|
|
25765
|
-
title: "
|
|
25964
|
+
title: "Whole query result subscribes to every field",
|
|
25766
25965
|
tags: ["test-noise"],
|
|
25767
25966
|
requires: ["tanstack-query"],
|
|
25768
25967
|
severity: "error",
|
|
@@ -25815,7 +26014,7 @@ const queryNoQueryInEffect = defineRule({
|
|
|
25815
26014
|
tags: ["test-noise"],
|
|
25816
26015
|
requires: ["tanstack-query"],
|
|
25817
26016
|
severity: "warn",
|
|
25818
|
-
recommendation: "
|
|
26017
|
+
recommendation: "Use `queryKey` changes or `enabled` so React Query schedules the fetch once instead of refetching again from `useEffect`.",
|
|
25819
26018
|
create: (context) => ({ CallExpression(node) {
|
|
25820
26019
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
25821
26020
|
const callback = getEffectCallback(node);
|
|
@@ -26138,7 +26337,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
26138
26337
|
severity: "warn",
|
|
26139
26338
|
category: "Performance",
|
|
26140
26339
|
disabledBy: ["react-compiler"],
|
|
26141
|
-
recommendation: "Select the raw slice and
|
|
26340
|
+
recommendation: "Select the raw slice and memoize derivation so Redux actions do not rebuild a collection and redraw this component.",
|
|
26142
26341
|
create: (context) => {
|
|
26143
26342
|
let aliases = /* @__PURE__ */ new Set();
|
|
26144
26343
|
return {
|
|
@@ -26198,7 +26397,7 @@ const reduxUseselectorReturnsNewCollection = defineRule({
|
|
|
26198
26397
|
severity: "warn",
|
|
26199
26398
|
category: "Performance",
|
|
26200
26399
|
disabledBy: ["react-compiler"],
|
|
26201
|
-
recommendation: "Return a
|
|
26400
|
+
recommendation: "Return a stable selected value, split selectors, or pass `shallowEqual` so every Redux action does not redraw this component.",
|
|
26202
26401
|
create: (context) => {
|
|
26203
26402
|
let aliases = /* @__PURE__ */ new Set();
|
|
26204
26403
|
return {
|
|
@@ -26224,7 +26423,7 @@ const renderingAnimateSvgWrapper = defineRule({
|
|
|
26224
26423
|
title: "Animating an SVG directly",
|
|
26225
26424
|
tags: ["test-noise"],
|
|
26226
26425
|
severity: "warn",
|
|
26227
|
-
recommendation: "Wrap the SVG
|
|
26426
|
+
recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
|
|
26228
26427
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
26229
26428
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
|
|
26230
26429
|
if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
|
|
@@ -26374,7 +26573,7 @@ const renderingHydrationMismatchTime = defineRule({
|
|
|
26374
26573
|
if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
|
|
26375
26574
|
context.report({
|
|
26376
26575
|
node,
|
|
26377
|
-
message: `This
|
|
26576
|
+
message: `This can cause a hydration mismatch because ${matched.display} in JSX gives a different value on the server than in the browser. Move it into useEffect+useState to run only in the browser, or add suppressHydrationWarning to the parent if it's on purpose.`
|
|
26378
26577
|
});
|
|
26379
26578
|
return;
|
|
26380
26579
|
}
|
|
@@ -26383,7 +26582,7 @@ const renderingHydrationMismatchTime = defineRule({
|
|
|
26383
26582
|
if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
|
|
26384
26583
|
context.report({
|
|
26385
26584
|
node: child,
|
|
26386
|
-
message: `This
|
|
26585
|
+
message: `This can cause a hydration mismatch because ${pattern.display} reached from JSX gives a different value on the server than in the browser. Move it into useEffect+useState to run only in the browser, or add suppressHydrationWarning to the parent if it's on purpose.`
|
|
26387
26586
|
});
|
|
26388
26587
|
return;
|
|
26389
26588
|
}
|
|
@@ -26601,12 +26800,12 @@ const functionBodyHasReturnWithValue = (functionNode) => {
|
|
|
26601
26800
|
const child = nodeRecord[key];
|
|
26602
26801
|
if (Array.isArray(child)) for (const item of child) {
|
|
26603
26802
|
if (!isAstNode(item)) continue;
|
|
26604
|
-
if (FUNCTION_LIKE_TYPES
|
|
26803
|
+
if (FUNCTION_LIKE_TYPES.has(item.type)) continue;
|
|
26605
26804
|
visit(item);
|
|
26606
26805
|
if (didFindReturn) return;
|
|
26607
26806
|
}
|
|
26608
26807
|
else if (isAstNode(child)) {
|
|
26609
|
-
if (FUNCTION_LIKE_TYPES
|
|
26808
|
+
if (FUNCTION_LIKE_TYPES.has(child.type)) continue;
|
|
26610
26809
|
visit(child);
|
|
26611
26810
|
}
|
|
26612
26811
|
}
|
|
@@ -26662,7 +26861,7 @@ const requireRenderReturn = defineRule({
|
|
|
26662
26861
|
id: "require-render-return",
|
|
26663
26862
|
title: "Render method does not return",
|
|
26664
26863
|
severity: "error",
|
|
26665
|
-
recommendation: "Return JSX
|
|
26864
|
+
recommendation: "Return JSX or `null` from `render` so the component intentionally shows something or nothing.",
|
|
26666
26865
|
create: (context) => {
|
|
26667
26866
|
const checkFunction = (functionNode) => {
|
|
26668
26867
|
const host = resolveRenderHost(functionNode);
|
|
@@ -26984,7 +27183,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
26984
27183
|
tags: ["test-noise"],
|
|
26985
27184
|
severity: "warn",
|
|
26986
27185
|
category: "Performance",
|
|
26987
|
-
recommendation: "
|
|
27186
|
+
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
26988
27187
|
create: (context) => ({ CallExpression(node) {
|
|
26989
27188
|
if (!isHookCall$1(node, "useRef") || !node.arguments?.length) return;
|
|
26990
27189
|
const initializer = node.arguments[0];
|
|
@@ -27011,7 +27210,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
27011
27210
|
tags: ["test-noise"],
|
|
27012
27211
|
severity: "warn",
|
|
27013
27212
|
category: "Performance",
|
|
27014
|
-
recommendation: "Wrap in an arrow function so
|
|
27213
|
+
recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
|
|
27015
27214
|
create: (context) => ({ CallExpression(node) {
|
|
27016
27215
|
if (!isHookCall$1(node, "useState") || !node.arguments?.length) return;
|
|
27017
27216
|
const initializer = node.arguments[0];
|
|
@@ -27294,7 +27493,7 @@ const rerenderTransitionsScroll = defineRule({
|
|
|
27294
27493
|
}
|
|
27295
27494
|
context.report({
|
|
27296
27495
|
node: setStateCall,
|
|
27297
|
-
message: `This
|
|
27496
|
+
message: `This can make scrolling stutter because setState in a "${eventName}" handler redraws on every event. Wrap it in startTransition, use useDeferredValue, or keep the value in a ref and throttle with requestAnimationFrame.`
|
|
27298
27497
|
});
|
|
27299
27498
|
} })
|
|
27300
27499
|
});
|
|
@@ -27314,7 +27513,7 @@ const rnAnimateLayoutProperty = defineRetiredRule({
|
|
|
27314
27513
|
tags: ["test-noise"],
|
|
27315
27514
|
requires: ["react-native"],
|
|
27316
27515
|
severity: "warn",
|
|
27317
|
-
recommendation: "Reanimated useAnimatedStyle
|
|
27516
|
+
recommendation: "Retired: Reanimated `useAnimatedStyle` can safely drive layout-affecting properties on the UI thread, so this pattern should not be flagged."
|
|
27318
27517
|
});
|
|
27319
27518
|
//#endregion
|
|
27320
27519
|
//#region src/plugin/rules/react-native/rn-animation-reaction-as-derived.ts
|
|
@@ -27345,7 +27544,7 @@ const rnAnimationReactionAsDerived = defineRule({
|
|
|
27345
27544
|
if (!isValueAssignment && !isSetCall) return;
|
|
27346
27545
|
context.report({
|
|
27347
27546
|
node,
|
|
27348
|
-
message: "
|
|
27547
|
+
message: "This useAnimatedReaction only copies one shared value into another, so it can miss Reanimated's derived-value dependency tracking."
|
|
27349
27548
|
});
|
|
27350
27549
|
} })
|
|
27351
27550
|
});
|
|
@@ -27362,17 +27561,17 @@ const JS_BOTTOM_SHEET_PACKAGES = new Set([
|
|
|
27362
27561
|
]);
|
|
27363
27562
|
const rnBottomSheetPreferNative = defineRule({
|
|
27364
27563
|
id: "rn-bottom-sheet-prefer-native",
|
|
27365
|
-
title: "JS bottom sheet
|
|
27564
|
+
title: "JS bottom sheet misses native sheet behavior",
|
|
27366
27565
|
tags: ["test-noise"],
|
|
27367
27566
|
requires: ["react-native"],
|
|
27368
27567
|
severity: "warn",
|
|
27369
|
-
recommendation: "On RN v7+, use `<Modal presentationStyle=\"formSheet\">`
|
|
27568
|
+
recommendation: "On RN v7+, use `<Modal presentationStyle=\"formSheet\">` so the sheet uses platform-native gestures, detents, accessibility, and presentation behavior.",
|
|
27370
27569
|
create: (context) => ({ ImportDeclaration(node) {
|
|
27371
27570
|
const source = node.source?.value;
|
|
27372
27571
|
if (typeof source !== "string" || !JS_BOTTOM_SHEET_PACKAGES.has(source)) return;
|
|
27373
27572
|
context.report({
|
|
27374
27573
|
node,
|
|
27375
|
-
message: `
|
|
27574
|
+
message: `Users get JS-driven sheet gestures and presentation with ${source}, instead of the platform-native formSheet behavior.`
|
|
27376
27575
|
});
|
|
27377
27576
|
} })
|
|
27378
27577
|
});
|
|
@@ -27458,13 +27657,13 @@ const rnDetoxMissingAwait = defineRule({
|
|
|
27458
27657
|
if (root.calleeName === "waitFor") {
|
|
27459
27658
|
context.report({
|
|
27460
27659
|
node,
|
|
27461
|
-
message: "This Detox `waitFor
|
|
27660
|
+
message: "This Detox `waitFor` chain isn't awaited, so the test can continue before the condition settles. Prepend `await`."
|
|
27462
27661
|
});
|
|
27463
27662
|
return;
|
|
27464
27663
|
}
|
|
27465
27664
|
if (root.calleeName === "expect" && isDetoxExpectSubject(root.rootCall)) context.report({
|
|
27466
27665
|
node,
|
|
27467
|
-
message: "This Detox `expect(element
|
|
27666
|
+
message: "This Detox `expect(element)` assertion isn't awaited, so the test can pass or fail before the assertion settles. Prepend `await`."
|
|
27468
27667
|
});
|
|
27469
27668
|
} };
|
|
27470
27669
|
}
|
|
@@ -28095,7 +28294,7 @@ const rnNoLegacyExpoPackages = defineRule({
|
|
|
28095
28294
|
tags: ["test-noise"],
|
|
28096
28295
|
requires: ["react-native"],
|
|
28097
28296
|
severity: "warn",
|
|
28098
|
-
recommendation: "
|
|
28297
|
+
recommendation: "Switch to the maintained replacement package so users are not stuck with unfixed bugs in deprecated Expo packages.",
|
|
28099
28298
|
create: (context) => ({ ImportDeclaration(node) {
|
|
28100
28299
|
const source = node.source?.value;
|
|
28101
28300
|
if (typeof source !== "string") return;
|
|
@@ -28177,7 +28376,7 @@ const rnNoNonNativeNavigator = defineRule({
|
|
|
28177
28376
|
if (!NON_NATIVE_NAVIGATOR_PACKAGES.get(source)) return;
|
|
28178
28377
|
context.report({
|
|
28179
28378
|
node,
|
|
28180
|
-
message: `
|
|
28379
|
+
message: `Users get JS-driven transitions and gestures from ${source}, instead of platform-native navigation behavior.`
|
|
28181
28380
|
});
|
|
28182
28381
|
} })
|
|
28183
28382
|
});
|
|
@@ -28356,7 +28555,7 @@ const isExpoUiNamespaceImport = (contextNode, localName) => {
|
|
|
28356
28555
|
};
|
|
28357
28556
|
const isExpoUiComponentElement = (openingElement, contextNode, componentName) => {
|
|
28358
28557
|
if (!openingElement.name) return false;
|
|
28359
|
-
const dottedName = flattenJsxName(openingElement.name);
|
|
28558
|
+
const dottedName = flattenJsxName$1(openingElement.name);
|
|
28360
28559
|
if (!dottedName) return false;
|
|
28361
28560
|
const [rootLocalName, secondName] = dottedName.split(".");
|
|
28362
28561
|
if (isNamedImportOf(contextNode, rootLocalName, componentName)) return true;
|
|
@@ -28446,7 +28645,7 @@ const collectTopLevelReturnExpressions = (functionNode) => {
|
|
|
28446
28645
|
if (!block || !isNodeOfType(block, "BlockStatement")) return [];
|
|
28447
28646
|
const returnExpressions = [];
|
|
28448
28647
|
const visit = (node) => {
|
|
28449
|
-
if (FUNCTION_LIKE_TYPES
|
|
28648
|
+
if (FUNCTION_LIKE_TYPES.has(node.type)) return;
|
|
28450
28649
|
if (isNodeOfType(node, "ReturnStatement") && node.argument) returnExpressions.push(node.argument);
|
|
28451
28650
|
const nodeRecord = node;
|
|
28452
28651
|
for (const fieldName of Object.keys(nodeRecord)) {
|
|
@@ -28483,7 +28682,7 @@ const collectReturnedJsxElements = (expression) => {
|
|
|
28483
28682
|
};
|
|
28484
28683
|
const rnNoRenderitemKey = defineRule({
|
|
28485
28684
|
id: "rn-no-renderitem-key",
|
|
28486
|
-
title: "
|
|
28685
|
+
title: "renderItem key is ignored by React Native lists",
|
|
28487
28686
|
tags: ["test-noise"],
|
|
28488
28687
|
requires: ["react-native"],
|
|
28489
28688
|
severity: "warn",
|
|
@@ -28624,7 +28823,7 @@ const rnNoSetNativeProps = defineRule({
|
|
|
28624
28823
|
//#region src/plugin/rules/react-native/rn-no-single-element-style-array.ts
|
|
28625
28824
|
const rnNoSingleElementStyleArray = defineRule({
|
|
28626
28825
|
id: "rn-no-single-element-style-array",
|
|
28627
|
-
title: "Single-element style array",
|
|
28826
|
+
title: "Single-element style array adds wasted allocation",
|
|
28628
28827
|
tags: ["test-noise"],
|
|
28629
28828
|
requires: ["react-native"],
|
|
28630
28829
|
severity: "warn",
|
|
@@ -28647,11 +28846,11 @@ const rnNoSingleElementStyleArray = defineRule({
|
|
|
28647
28846
|
//#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
|
|
28648
28847
|
const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
28649
28848
|
id: "rn-prefer-content-inset-adjustment",
|
|
28650
|
-
title: "Manual safe-area
|
|
28849
|
+
title: "Manual safe-area insets can duplicate offsets",
|
|
28651
28850
|
tags: ["test-noise"],
|
|
28652
28851
|
requires: ["react-native"],
|
|
28653
28852
|
severity: "warn",
|
|
28654
|
-
recommendation: "
|
|
28853
|
+
recommendation: "Retired: SafeAreaView wrappers are valid; prefer native content inset adjustment only when manual inset plumbing causes scroll jumps or duplicated safe-area offsets."
|
|
28655
28854
|
});
|
|
28656
28855
|
//#endregion
|
|
28657
28856
|
//#region src/react-native-dependency-names.ts
|
|
@@ -28836,7 +29035,7 @@ const rnPreferPressable = defineRule({
|
|
|
28836
29035
|
tags: ["test-noise"],
|
|
28837
29036
|
requires: ["react-native"],
|
|
28838
29037
|
severity: "warn",
|
|
28839
|
-
recommendation: "Use `<Pressable>`
|
|
29038
|
+
recommendation: "Use `<Pressable>` because Touchable* components are frozen and lack Pressable's state-based feedback and accessibility behavior.",
|
|
28840
29039
|
create: (context) => ({ ImportDeclaration(node) {
|
|
28841
29040
|
const source = node.source?.value;
|
|
28842
29041
|
if (typeof source !== "string" || !TOUCHABLE_SOURCES.has(source)) return;
|
|
@@ -29272,7 +29471,7 @@ const roleHasRequiredAriaProps = defineRule({
|
|
|
29272
29471
|
title: "Role missing required ARIA props",
|
|
29273
29472
|
tags: ["react-jsx-only"],
|
|
29274
29473
|
severity: "error",
|
|
29275
|
-
recommendation: "Add every required `aria-*` attribute
|
|
29474
|
+
recommendation: "Add every required `aria-*` attribute so assistive tech can expose the role's state correctly.",
|
|
29276
29475
|
category: "Accessibility",
|
|
29277
29476
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
29278
29477
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
@@ -32518,16 +32717,16 @@ const roleSupportsAriaProps = defineRule({
|
|
|
32518
32717
|
});
|
|
32519
32718
|
//#endregion
|
|
32520
32719
|
//#region src/plugin/rules/react-builtins/rules-of-hooks.ts
|
|
32521
|
-
const buildTopLevelMessage = (hookName) => `\`${hookName}\`
|
|
32522
|
-
const buildNonComponentMessage = (hookName, functionName) => `\`${hookName}\`
|
|
32523
|
-
const buildConditionalMessage = (hookName) => `\`${hookName}\`
|
|
32524
|
-
const buildLoopMessage = (hookName) => `\`${hookName}\`
|
|
32525
|
-
const buildAsyncMessage = (hookName) => `\`${hookName}\`
|
|
32526
|
-
const buildClassComponentMessage = (hookName) => `\`${hookName}\`
|
|
32527
|
-
const buildTryMessage = (hookName) => `\`${hookName}\`
|
|
32720
|
+
const buildTopLevelMessage = (hookName) => `\`${hookName}\` can only run inside a React component or custom Hook because React needs that render scope to track Hook state.`;
|
|
32721
|
+
const buildNonComponentMessage = (hookName, functionName) => `\`${hookName}\` runs inside \`${functionName}\`, which is not a component or Hook, so React cannot attach Hook state to a render.`;
|
|
32722
|
+
const buildConditionalMessage = (hookName) => `\`${hookName}\` changes Hook order between renders when called conditionally, so React can attach state to the wrong Hook.`;
|
|
32723
|
+
const buildLoopMessage = (hookName) => `\`${hookName}\` can run a different number of times inside a loop, so React can attach state to the wrong Hook.`;
|
|
32724
|
+
const buildAsyncMessage = (hookName) => `\`${hookName}\` runs inside an async function, so React cannot guarantee the same Hook order during render.`;
|
|
32725
|
+
const buildClassComponentMessage = (hookName) => `\`${hookName}\` cannot run in a class component because Hooks require a function component or custom Hook render scope.`;
|
|
32726
|
+
const buildTryMessage = (hookName) => `\`${hookName}\` can be skipped by try/catch/finally control flow, so React can attach state to the wrong Hook.`;
|
|
32528
32727
|
const buildEffectEventCallMessage = (bindingName) => `\`${bindingName}\` comes from useEffectEvent, so it only works when called from Effects in the same component.`;
|
|
32529
32728
|
const buildEffectEventAssignmentMessage = (bindingName) => `${buildEffectEventCallMessage(bindingName)} It also breaks if saved in a variable or passed around.`;
|
|
32530
|
-
const buildEffectEventPassedDownMessage = () => `A function from useEffectEvent breaks
|
|
32729
|
+
const buildEffectEventPassedDownMessage = () => `A function from useEffectEvent only works inside Effects in the same component, so passing it around breaks the event/dependency split.`;
|
|
32531
32730
|
const ASCII_UPPERCASE_A = 65;
|
|
32532
32731
|
const ASCII_UPPERCASE_Z = 90;
|
|
32533
32732
|
const EFFECT_HOOK_NAMES = new Set([
|
|
@@ -32780,7 +32979,7 @@ const rulesOfHooks = defineRule({
|
|
|
32780
32979
|
title: "Hook called conditionally",
|
|
32781
32980
|
severity: "error",
|
|
32782
32981
|
tags: ["test-noise"],
|
|
32783
|
-
recommendation: "Call hooks at the top level of a React function component or
|
|
32982
|
+
recommendation: "Call hooks at the top level of a React function component or custom Hook so React sees the same hook order on every render.",
|
|
32784
32983
|
category: "Correctness",
|
|
32785
32984
|
create: (context) => {
|
|
32786
32985
|
const settings = resolveSettings$3(context.settings);
|
|
@@ -32908,13 +33107,13 @@ const rulesOfHooks = defineRule({
|
|
|
32908
33107
|
});
|
|
32909
33108
|
//#endregion
|
|
32910
33109
|
//#region src/plugin/rules/a11y/scope.ts
|
|
32911
|
-
const MESSAGE$3 = "
|
|
33110
|
+
const MESSAGE$3 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
|
|
32912
33111
|
const scope = defineRule({
|
|
32913
33112
|
id: "scope",
|
|
32914
33113
|
title: "scope attribute on non-th element",
|
|
32915
33114
|
tags: ["react-jsx-only"],
|
|
32916
33115
|
severity: "warn",
|
|
32917
|
-
recommendation: "
|
|
33116
|
+
recommendation: "Remove `scope` from this element or move it to the related `<th>` cell.",
|
|
32918
33117
|
category: "Accessibility",
|
|
32919
33118
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
32920
33119
|
const scopeAttribute = hasJsxProp(node.attributes, "scope");
|
|
@@ -32929,7 +33128,7 @@ const scope = defineRule({
|
|
|
32929
33128
|
});
|
|
32930
33129
|
//#endregion
|
|
32931
33130
|
//#region src/plugin/rules/react-builtins/self-closing-comp.ts
|
|
32932
|
-
const MESSAGE$2 = "This tag has no children.";
|
|
33131
|
+
const MESSAGE$2 = "This tag has no children, so the closing tag adds noise without changing output.";
|
|
32933
33132
|
const resolveSettings$2 = (settings) => {
|
|
32934
33133
|
const reactDoctor = settings?.["react-doctor"];
|
|
32935
33134
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
|
|
@@ -32948,7 +33147,7 @@ const selfClosingComp = defineRule({
|
|
|
32948
33147
|
title: "Element not self-closing",
|
|
32949
33148
|
severity: "warn",
|
|
32950
33149
|
defaultEnabled: false,
|
|
32951
|
-
recommendation: "Use
|
|
33150
|
+
recommendation: "Use `<X />` for childless elements so empty closing tags do not add noise.",
|
|
32952
33151
|
category: "Architecture",
|
|
32953
33152
|
create: (context) => {
|
|
32954
33153
|
const settings = resolveSettings$2(context.settings);
|
|
@@ -33145,9 +33344,9 @@ const getCandidateFromDefaultDeclaration = (node) => {
|
|
|
33145
33344
|
};
|
|
33146
33345
|
const serverAuthActions = defineRule({
|
|
33147
33346
|
id: "server-auth-actions",
|
|
33148
|
-
title: "
|
|
33347
|
+
title: "Unauthenticated server action can be called directly",
|
|
33149
33348
|
severity: "error",
|
|
33150
|
-
recommendation: "
|
|
33349
|
+
recommendation: "Check auth before touching data because exported server actions can be called directly by unauthenticated clients.",
|
|
33151
33350
|
create: (context) => {
|
|
33152
33351
|
let fileHasUseServerDirective = false;
|
|
33153
33352
|
const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
|
|
@@ -33285,7 +33484,7 @@ const objectExpressionHasNextRevalidate = (objectExpression) => {
|
|
|
33285
33484
|
}
|
|
33286
33485
|
return false;
|
|
33287
33486
|
};
|
|
33288
|
-
const APP_ROUTER_FILE_PATTERN =
|
|
33487
|
+
const APP_ROUTER_FILE_PATTERN = new RegExp(`/app/(?:[^/]+/)*(?:route|page|layout|template|loading|error|default)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
33289
33488
|
const NON_PROJECT_PATH_PATTERN = /\/(?:node_modules|dist|build|\.next)\//;
|
|
33290
33489
|
const serverFetchWithoutRevalidate = defineRule({
|
|
33291
33490
|
id: "server-fetch-without-revalidate",
|
|
@@ -33528,8 +33727,8 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
33528
33727
|
});
|
|
33529
33728
|
//#endregion
|
|
33530
33729
|
//#region src/plugin/rules/react-builtins/state-in-constructor.ts
|
|
33531
|
-
const ALWAYS_MESSAGE = "This
|
|
33532
|
-
const NEVER_MESSAGE = "This
|
|
33730
|
+
const ALWAYS_MESSAGE = "This class uses a state field instead of the configured constructor pattern, so state setup is inconsistent across the codebase.";
|
|
33731
|
+
const NEVER_MESSAGE = "This class sets state in the constructor instead of the configured class-field pattern, so state setup is inconsistent across the codebase.";
|
|
33533
33732
|
const resolveSettings$1 = (settings) => {
|
|
33534
33733
|
const reactDoctor = settings?.["react-doctor"];
|
|
33535
33734
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stateInConstructor ?? {} : {}).mode ?? "always" };
|
|
@@ -33561,7 +33760,7 @@ const stateInConstructor = defineRule({
|
|
|
33561
33760
|
title: "State initialized in constructor",
|
|
33562
33761
|
severity: "warn",
|
|
33563
33762
|
defaultEnabled: false,
|
|
33564
|
-
recommendation: "
|
|
33763
|
+
recommendation: "Use one class-state setup pattern so readers know where initial state lives.",
|
|
33565
33764
|
category: "Architecture",
|
|
33566
33765
|
create: (context) => {
|
|
33567
33766
|
const { mode } = resolveSettings$1(context.settings);
|
|
@@ -33664,7 +33863,7 @@ const stylePropObject = defineRule({
|
|
|
33664
33863
|
id: "style-prop-object",
|
|
33665
33864
|
title: "Style prop is not an object",
|
|
33666
33865
|
severity: "warn",
|
|
33667
|
-
recommendation: "Pass
|
|
33866
|
+
recommendation: "Pass `style` as an object so React can apply CSS properties instead of ignoring a string style value.",
|
|
33668
33867
|
category: "Correctness",
|
|
33669
33868
|
create: (context) => {
|
|
33670
33869
|
const { allow } = resolveSettings(context.settings);
|
|
@@ -34010,11 +34209,11 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
34010
34209
|
//#region src/plugin/rules/tanstack-start/tanstack-start-no-anchor-element.ts
|
|
34011
34210
|
const tanstackStartNoAnchorElement = defineRule({
|
|
34012
34211
|
id: "tanstack-start-no-anchor-element",
|
|
34013
|
-
title: "Plain anchor
|
|
34212
|
+
title: "Plain anchor reloads TanStack Router navigation",
|
|
34014
34213
|
tags: ["test-noise"],
|
|
34015
34214
|
requires: ["tanstack-start"],
|
|
34016
34215
|
severity: "warn",
|
|
34017
|
-
recommendation: "`
|
|
34216
|
+
recommendation: "Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",
|
|
34018
34217
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
34019
34218
|
const filename = normalizeFilename$1(context.filename ?? "");
|
|
34020
34219
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
@@ -34027,7 +34226,7 @@ const tanstackStartNoAnchorElement = defineRule({
|
|
|
34027
34226
|
else if (isNodeOfType(hrefAttribute.value, "JSXExpressionContainer") && isNodeOfType(hrefAttribute.value.expression, "Literal")) hrefValue = hrefAttribute.value.expression.value;
|
|
34028
34227
|
if (typeof hrefValue === "string" && hrefValue.startsWith("/")) context.report({
|
|
34029
34228
|
node,
|
|
34030
|
-
message: "Plain <a> reloads the whole page
|
|
34229
|
+
message: "Plain <a> reloads the whole page for internal navigation, so TanStack Router loses client state and preloading."
|
|
34031
34230
|
});
|
|
34032
34231
|
} })
|
|
34033
34232
|
});
|
|
@@ -34099,7 +34298,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
34099
34298
|
if (deferredCallbackDepth > 0 || eventHandlerDepth > 0) return;
|
|
34100
34299
|
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "navigate" && (node.arguments?.length ?? 0) > 0) context.report({
|
|
34101
34300
|
node,
|
|
34102
|
-
message: "navigate() during render
|
|
34301
|
+
message: "navigate() runs during render here, so server and browser output can diverge during hydration."
|
|
34103
34302
|
});
|
|
34104
34303
|
},
|
|
34105
34304
|
"CallExpression:exit"(node) {
|
|
@@ -34183,7 +34382,7 @@ const tanstackStartNoUseServerInHandler = defineRule({
|
|
|
34183
34382
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
34184
34383
|
if (body.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && (statement.directive === "use server" || isNodeOfType(statement.expression, "Literal") && statement.expression.value === "use server"))) context.report({
|
|
34185
34384
|
node: handlerFunction,
|
|
34186
|
-
message: "\"use server\" inside a createServerFn handler
|
|
34385
|
+
message: "\"use server\" inside a createServerFn handler duplicates TanStack Start's server boundary, so the route can fail to compile."
|
|
34187
34386
|
});
|
|
34188
34387
|
} })
|
|
34189
34388
|
});
|
|
@@ -34257,11 +34456,11 @@ const tanstackStartRedirectInTryCatch = defineRule({
|
|
|
34257
34456
|
//#region src/plugin/rules/tanstack-start/tanstack-start-route-property-order.ts
|
|
34258
34457
|
const tanstackStartRoutePropertyOrder = defineRule({
|
|
34259
34458
|
id: "tanstack-start-route-property-order",
|
|
34260
|
-
title: "
|
|
34459
|
+
title: "Route property order breaks type inference",
|
|
34261
34460
|
tags: ["test-noise"],
|
|
34262
34461
|
requires: ["tanstack-start"],
|
|
34263
34462
|
severity: "error",
|
|
34264
|
-
recommendation: "Follow the order
|
|
34463
|
+
recommendation: "Follow the route property order because TanStack Router's type inference depends on earlier properties feeding later ones.",
|
|
34265
34464
|
create: (context) => ({ CallExpression(node) {
|
|
34266
34465
|
const optionsObject = getRouteOptionsObject(node);
|
|
34267
34466
|
if (!optionsObject) return;
|
|
@@ -34291,7 +34490,7 @@ const tanstackStartRoutePropertyOrder = defineRule({
|
|
|
34291
34490
|
//#region src/plugin/rules/tanstack-start/tanstack-start-server-fn-method-order.ts
|
|
34292
34491
|
const tanstackStartServerFnMethodOrder = defineRule({
|
|
34293
34492
|
id: "tanstack-start-server-fn-method-order",
|
|
34294
|
-
title: "
|
|
34493
|
+
title: "Server function method order breaks type inference",
|
|
34295
34494
|
tags: ["test-noise"],
|
|
34296
34495
|
requires: ["tanstack-start"],
|
|
34297
34496
|
severity: "error",
|
|
@@ -34372,7 +34571,7 @@ const useLazyMotion = defineRule({
|
|
|
34372
34571
|
return getImportedName$1(specifier) === "motion";
|
|
34373
34572
|
})) context.report({
|
|
34374
34573
|
node,
|
|
34375
|
-
message: "Importing \"motion\" ships about 30 kb of extra code
|
|
34574
|
+
message: "Importing \"motion\" ships about 30 kb of extra code and slows page load. Use \"m\" with LazyMotion instead."
|
|
34376
34575
|
});
|
|
34377
34576
|
} })
|
|
34378
34577
|
});
|
|
@@ -34409,7 +34608,7 @@ const voidDomElementsNoChildren = defineRule({
|
|
|
34409
34608
|
id: "void-dom-elements-no-children",
|
|
34410
34609
|
title: "Children on a void element",
|
|
34411
34610
|
severity: "warn",
|
|
34412
|
-
recommendation: "Remove the children
|
|
34611
|
+
recommendation: "Remove the children or use a non-void tag so React does not drop content the element cannot render.",
|
|
34413
34612
|
create: (context) => ({
|
|
34414
34613
|
JSXElement(node) {
|
|
34415
34614
|
const openingElement = node.openingElement;
|
|
@@ -34557,7 +34756,7 @@ const DEPRECATED_ZOD_ERROR_MEMBERS = new Set([
|
|
|
34557
34756
|
"formErrors",
|
|
34558
34757
|
"format"
|
|
34559
34758
|
]);
|
|
34560
|
-
const ZOD_ERROR_API_MESSAGE = "
|
|
34759
|
+
const ZOD_ERROR_API_MESSAGE = "This ZodError API was removed in Zod 4, so error handling can break during the upgrade.";
|
|
34561
34760
|
const isZodErrorReference = (node) => {
|
|
34562
34761
|
const inner = stripParenExpression(node);
|
|
34563
34762
|
if (isNodeOfType(inner, "Identifier")) return getZodNamedImport(inner) === "ZodError";
|
|
@@ -34589,7 +34788,7 @@ const isReceiverOfDeprecatedZodErrorMember = (callExpression) => {
|
|
|
34589
34788
|
};
|
|
34590
34789
|
const zodV4NoDeprecatedErrorApis = defineRule({
|
|
34591
34790
|
id: "zod-v4-no-deprecated-error-apis",
|
|
34592
|
-
title: "
|
|
34791
|
+
title: "Zod 3 error API breaks in Zod 4",
|
|
34593
34792
|
requires: ["zod:4"],
|
|
34594
34793
|
tags: ["migration-hint"],
|
|
34595
34794
|
severity: "warn",
|
|
@@ -34678,7 +34877,7 @@ const parseCallUsesErrorMap = (callExpression) => {
|
|
|
34678
34877
|
};
|
|
34679
34878
|
const zodV4NoDeprecatedErrorCustomization = defineRule({
|
|
34680
34879
|
id: "zod-v4-no-deprecated-error-customization",
|
|
34681
|
-
title: "
|
|
34880
|
+
title: "Zod 3 error customization breaks in Zod 4",
|
|
34682
34881
|
requires: ["zod:4"],
|
|
34683
34882
|
tags: ["migration-hint"],
|
|
34684
34883
|
severity: "warn",
|
|
@@ -34687,7 +34886,7 @@ const zodV4NoDeprecatedErrorCustomization = defineRule({
|
|
|
34687
34886
|
if (!factoryUsesDeprecatedErrorParameter(node) && !parseCallUsesErrorMap(node)) return;
|
|
34688
34887
|
context.report({
|
|
34689
34888
|
node,
|
|
34690
|
-
message: "Zod
|
|
34889
|
+
message: "This Zod 3 error-customization form is not compatible with Zod 4, so custom messages can stop applying during the upgrade."
|
|
34691
34890
|
});
|
|
34692
34891
|
} })
|
|
34693
34892
|
});
|
|
@@ -34747,7 +34946,7 @@ const LITERAL_FACTORY = new Set(["literal"]);
|
|
|
34747
34946
|
const reportSchemaMigration = (context, node) => {
|
|
34748
34947
|
context.report({
|
|
34749
34948
|
node,
|
|
34750
|
-
message: "Zod
|
|
34949
|
+
message: "This Zod 3 schema API changed in Zod 4, so this schema can fail after the upgrade."
|
|
34751
34950
|
});
|
|
34752
34951
|
};
|
|
34753
34952
|
const isCallToDeprecatedTopLevelFactory = (callExpression) => isZodFactoryCall(callExpression, DEPRECATED_TOP_LEVEL_FACTORIES);
|
|
@@ -34799,7 +34998,7 @@ const isZodNamespaceImportMemberCreate = (memberExpression) => {
|
|
|
34799
34998
|
};
|
|
34800
34999
|
const zodV4NoDeprecatedSchemaApis = defineRule({
|
|
34801
35000
|
id: "zod-v4-no-deprecated-schema-apis",
|
|
34802
|
-
title: "
|
|
35001
|
+
title: "Zod 3 schema API breaks in Zod 4",
|
|
34803
35002
|
requires: ["zod:4"],
|
|
34804
35003
|
tags: ["migration-hint"],
|
|
34805
35004
|
severity: "warn",
|
|
@@ -34852,7 +35051,7 @@ const zodV4PreferTopLevelStringFormats = defineRule({
|
|
|
34852
35051
|
if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
|
|
34853
35052
|
context.report({
|
|
34854
35053
|
node,
|
|
34855
|
-
message: "
|
|
35054
|
+
message: "This `z.string().<format>()` check is deprecated in Zod 4, so it can break during the upgrade."
|
|
34856
35055
|
});
|
|
34857
35056
|
} })
|
|
34858
35057
|
});
|
|
@@ -34867,7 +35066,8 @@ const reactDoctorRules = [
|
|
|
34867
35066
|
rule: {
|
|
34868
35067
|
...activityWrapsEffectHeavySubtree,
|
|
34869
35068
|
framework: "global",
|
|
34870
|
-
category: "Bugs"
|
|
35069
|
+
category: "Bugs",
|
|
35070
|
+
requires: [...new Set(["react", ...activityWrapsEffectHeavySubtree.requires ?? []])]
|
|
34871
35071
|
}
|
|
34872
35072
|
},
|
|
34873
35073
|
{
|
|
@@ -34878,7 +35078,8 @@ const reactDoctorRules = [
|
|
|
34878
35078
|
rule: {
|
|
34879
35079
|
...advancedEventHandlerRefs,
|
|
34880
35080
|
framework: "global",
|
|
34881
|
-
category: "Performance"
|
|
35081
|
+
category: "Performance",
|
|
35082
|
+
requires: [...new Set(["react", ...advancedEventHandlerRefs.requires ?? []])]
|
|
34882
35083
|
}
|
|
34883
35084
|
},
|
|
34884
35085
|
{
|
|
@@ -34889,7 +35090,8 @@ const reactDoctorRules = [
|
|
|
34889
35090
|
rule: {
|
|
34890
35091
|
...altText,
|
|
34891
35092
|
framework: "global",
|
|
34892
|
-
category: "Accessibility"
|
|
35093
|
+
category: "Accessibility",
|
|
35094
|
+
requires: [...new Set(["react", ...altText.requires ?? []])]
|
|
34893
35095
|
}
|
|
34894
35096
|
},
|
|
34895
35097
|
{
|
|
@@ -34900,7 +35102,8 @@ const reactDoctorRules = [
|
|
|
34900
35102
|
rule: {
|
|
34901
35103
|
...anchorAmbiguousText,
|
|
34902
35104
|
framework: "global",
|
|
34903
|
-
category: "Accessibility"
|
|
35105
|
+
category: "Accessibility",
|
|
35106
|
+
requires: [...new Set(["react", ...anchorAmbiguousText.requires ?? []])]
|
|
34904
35107
|
}
|
|
34905
35108
|
},
|
|
34906
35109
|
{
|
|
@@ -34911,7 +35114,8 @@ const reactDoctorRules = [
|
|
|
34911
35114
|
rule: {
|
|
34912
35115
|
...anchorHasContent,
|
|
34913
35116
|
framework: "global",
|
|
34914
|
-
category: "Accessibility"
|
|
35117
|
+
category: "Accessibility",
|
|
35118
|
+
requires: [...new Set(["react", ...anchorHasContent.requires ?? []])]
|
|
34915
35119
|
}
|
|
34916
35120
|
},
|
|
34917
35121
|
{
|
|
@@ -34922,7 +35126,8 @@ const reactDoctorRules = [
|
|
|
34922
35126
|
rule: {
|
|
34923
35127
|
...anchorIsValid,
|
|
34924
35128
|
framework: "global",
|
|
34925
|
-
category: "Accessibility"
|
|
35129
|
+
category: "Accessibility",
|
|
35130
|
+
requires: [...new Set(["react", ...anchorIsValid.requires ?? []])]
|
|
34926
35131
|
}
|
|
34927
35132
|
},
|
|
34928
35133
|
{
|
|
@@ -34933,7 +35138,8 @@ const reactDoctorRules = [
|
|
|
34933
35138
|
rule: {
|
|
34934
35139
|
...ariaActivedescendantHasTabindex,
|
|
34935
35140
|
framework: "global",
|
|
34936
|
-
category: "Accessibility"
|
|
35141
|
+
category: "Accessibility",
|
|
35142
|
+
requires: [...new Set(["react", ...ariaActivedescendantHasTabindex.requires ?? []])]
|
|
34937
35143
|
}
|
|
34938
35144
|
},
|
|
34939
35145
|
{
|
|
@@ -34944,7 +35150,8 @@ const reactDoctorRules = [
|
|
|
34944
35150
|
rule: {
|
|
34945
35151
|
...ariaProps,
|
|
34946
35152
|
framework: "global",
|
|
34947
|
-
category: "Accessibility"
|
|
35153
|
+
category: "Accessibility",
|
|
35154
|
+
requires: [...new Set(["react", ...ariaProps.requires ?? []])]
|
|
34948
35155
|
}
|
|
34949
35156
|
},
|
|
34950
35157
|
{
|
|
@@ -34955,7 +35162,8 @@ const reactDoctorRules = [
|
|
|
34955
35162
|
rule: {
|
|
34956
35163
|
...ariaProptypes,
|
|
34957
35164
|
framework: "global",
|
|
34958
|
-
category: "Accessibility"
|
|
35165
|
+
category: "Accessibility",
|
|
35166
|
+
requires: [...new Set(["react", ...ariaProptypes.requires ?? []])]
|
|
34959
35167
|
}
|
|
34960
35168
|
},
|
|
34961
35169
|
{
|
|
@@ -34966,7 +35174,8 @@ const reactDoctorRules = [
|
|
|
34966
35174
|
rule: {
|
|
34967
35175
|
...ariaRole,
|
|
34968
35176
|
framework: "global",
|
|
34969
|
-
category: "Accessibility"
|
|
35177
|
+
category: "Accessibility",
|
|
35178
|
+
requires: [...new Set(["react", ...ariaRole.requires ?? []])]
|
|
34970
35179
|
}
|
|
34971
35180
|
},
|
|
34972
35181
|
{
|
|
@@ -34977,7 +35186,8 @@ const reactDoctorRules = [
|
|
|
34977
35186
|
rule: {
|
|
34978
35187
|
...ariaUnsupportedElements,
|
|
34979
35188
|
framework: "global",
|
|
34980
|
-
category: "Accessibility"
|
|
35189
|
+
category: "Accessibility",
|
|
35190
|
+
requires: [...new Set(["react", ...ariaUnsupportedElements.requires ?? []])]
|
|
34981
35191
|
}
|
|
34982
35192
|
},
|
|
34983
35193
|
{
|
|
@@ -34999,7 +35209,8 @@ const reactDoctorRules = [
|
|
|
34999
35209
|
rule: {
|
|
35000
35210
|
...asyncDeferAwait,
|
|
35001
35211
|
framework: "global",
|
|
35002
|
-
category: "Performance"
|
|
35212
|
+
category: "Performance",
|
|
35213
|
+
requires: [...new Set(["react", ...asyncDeferAwait.requires ?? []])]
|
|
35003
35214
|
}
|
|
35004
35215
|
},
|
|
35005
35216
|
{
|
|
@@ -35021,7 +35232,8 @@ const reactDoctorRules = [
|
|
|
35021
35232
|
rule: {
|
|
35022
35233
|
...autocompleteValid,
|
|
35023
35234
|
framework: "global",
|
|
35024
|
-
category: "Accessibility"
|
|
35235
|
+
category: "Accessibility",
|
|
35236
|
+
requires: [...new Set(["react", ...autocompleteValid.requires ?? []])]
|
|
35025
35237
|
}
|
|
35026
35238
|
},
|
|
35027
35239
|
{
|
|
@@ -35032,7 +35244,8 @@ const reactDoctorRules = [
|
|
|
35032
35244
|
rule: {
|
|
35033
35245
|
...buttonHasType,
|
|
35034
35246
|
framework: "global",
|
|
35035
|
-
category: "Bugs"
|
|
35247
|
+
category: "Bugs",
|
|
35248
|
+
requires: [...new Set(["react", ...buttonHasType.requires ?? []])]
|
|
35036
35249
|
}
|
|
35037
35250
|
},
|
|
35038
35251
|
{
|
|
@@ -35043,7 +35256,8 @@ const reactDoctorRules = [
|
|
|
35043
35256
|
rule: {
|
|
35044
35257
|
...checkedRequiresOnchangeOrReadonly,
|
|
35045
35258
|
framework: "global",
|
|
35046
|
-
category: "Bugs"
|
|
35259
|
+
category: "Bugs",
|
|
35260
|
+
requires: [...new Set(["react", ...checkedRequiresOnchangeOrReadonly.requires ?? []])]
|
|
35047
35261
|
}
|
|
35048
35262
|
},
|
|
35049
35263
|
{
|
|
@@ -35054,7 +35268,8 @@ const reactDoctorRules = [
|
|
|
35054
35268
|
rule: {
|
|
35055
35269
|
...clickEventsHaveKeyEvents,
|
|
35056
35270
|
framework: "global",
|
|
35057
|
-
category: "Accessibility"
|
|
35271
|
+
category: "Accessibility",
|
|
35272
|
+
requires: [...new Set(["react", ...clickEventsHaveKeyEvents.requires ?? []])]
|
|
35058
35273
|
}
|
|
35059
35274
|
},
|
|
35060
35275
|
{
|
|
@@ -35065,7 +35280,8 @@ const reactDoctorRules = [
|
|
|
35065
35280
|
rule: {
|
|
35066
35281
|
...clientLocalstorageNoVersion,
|
|
35067
35282
|
framework: "global",
|
|
35068
|
-
category: "Bugs"
|
|
35283
|
+
category: "Bugs",
|
|
35284
|
+
requires: [...new Set(["react", ...clientLocalstorageNoVersion.requires ?? []])]
|
|
35069
35285
|
}
|
|
35070
35286
|
},
|
|
35071
35287
|
{
|
|
@@ -35076,7 +35292,8 @@ const reactDoctorRules = [
|
|
|
35076
35292
|
rule: {
|
|
35077
35293
|
...clientPassiveEventListeners,
|
|
35078
35294
|
framework: "global",
|
|
35079
|
-
category: "Performance"
|
|
35295
|
+
category: "Performance",
|
|
35296
|
+
requires: [...new Set(["react", ...clientPassiveEventListeners.requires ?? []])]
|
|
35080
35297
|
}
|
|
35081
35298
|
},
|
|
35082
35299
|
{
|
|
@@ -35087,7 +35304,8 @@ const reactDoctorRules = [
|
|
|
35087
35304
|
rule: {
|
|
35088
35305
|
...controlHasAssociatedLabel,
|
|
35089
35306
|
framework: "global",
|
|
35090
|
-
category: "Accessibility"
|
|
35307
|
+
category: "Accessibility",
|
|
35308
|
+
requires: [...new Set(["react", ...controlHasAssociatedLabel.requires ?? []])]
|
|
35091
35309
|
}
|
|
35092
35310
|
},
|
|
35093
35311
|
{
|
|
@@ -35098,7 +35316,8 @@ const reactDoctorRules = [
|
|
|
35098
35316
|
rule: {
|
|
35099
35317
|
...noEmDashInJsxText,
|
|
35100
35318
|
framework: "global",
|
|
35101
|
-
category: "Maintainability"
|
|
35319
|
+
category: "Maintainability",
|
|
35320
|
+
requires: [...new Set(["react", ...noEmDashInJsxText.requires ?? []])]
|
|
35102
35321
|
}
|
|
35103
35322
|
},
|
|
35104
35323
|
{
|
|
@@ -35109,7 +35328,8 @@ const reactDoctorRules = [
|
|
|
35109
35328
|
rule: {
|
|
35110
35329
|
...noRedundantPaddingAxes,
|
|
35111
35330
|
framework: "global",
|
|
35112
|
-
category: "Maintainability"
|
|
35331
|
+
category: "Maintainability",
|
|
35332
|
+
requires: [...new Set(["react", ...noRedundantPaddingAxes.requires ?? []])]
|
|
35113
35333
|
}
|
|
35114
35334
|
},
|
|
35115
35335
|
{
|
|
@@ -35120,7 +35340,8 @@ const reactDoctorRules = [
|
|
|
35120
35340
|
rule: {
|
|
35121
35341
|
...noRedundantSizeAxes,
|
|
35122
35342
|
framework: "global",
|
|
35123
|
-
category: "Maintainability"
|
|
35343
|
+
category: "Maintainability",
|
|
35344
|
+
requires: [...new Set(["react", ...noRedundantSizeAxes.requires ?? []])]
|
|
35124
35345
|
}
|
|
35125
35346
|
},
|
|
35126
35347
|
{
|
|
@@ -35131,7 +35352,8 @@ const reactDoctorRules = [
|
|
|
35131
35352
|
rule: {
|
|
35132
35353
|
...noSpaceOnFlexChildren,
|
|
35133
35354
|
framework: "global",
|
|
35134
|
-
category: "Maintainability"
|
|
35355
|
+
category: "Maintainability",
|
|
35356
|
+
requires: [...new Set(["react", ...noSpaceOnFlexChildren.requires ?? []])]
|
|
35135
35357
|
}
|
|
35136
35358
|
},
|
|
35137
35359
|
{
|
|
@@ -35142,7 +35364,8 @@ const reactDoctorRules = [
|
|
|
35142
35364
|
rule: {
|
|
35143
35365
|
...noThreePeriodEllipsis,
|
|
35144
35366
|
framework: "global",
|
|
35145
|
-
category: "Maintainability"
|
|
35367
|
+
category: "Maintainability",
|
|
35368
|
+
requires: [...new Set(["react", ...noThreePeriodEllipsis.requires ?? []])]
|
|
35146
35369
|
}
|
|
35147
35370
|
},
|
|
35148
35371
|
{
|
|
@@ -35153,7 +35376,8 @@ const reactDoctorRules = [
|
|
|
35153
35376
|
rule: {
|
|
35154
35377
|
...noVagueButtonLabel,
|
|
35155
35378
|
framework: "global",
|
|
35156
|
-
category: "Accessibility"
|
|
35379
|
+
category: "Accessibility",
|
|
35380
|
+
requires: [...new Set(["react", ...noVagueButtonLabel.requires ?? []])]
|
|
35157
35381
|
}
|
|
35158
35382
|
},
|
|
35159
35383
|
{
|
|
@@ -35164,7 +35388,8 @@ const reactDoctorRules = [
|
|
|
35164
35388
|
rule: {
|
|
35165
35389
|
...displayName,
|
|
35166
35390
|
framework: "global",
|
|
35167
|
-
category: "Maintainability"
|
|
35391
|
+
category: "Maintainability",
|
|
35392
|
+
requires: [...new Set(["react", ...displayName.requires ?? []])]
|
|
35168
35393
|
}
|
|
35169
35394
|
},
|
|
35170
35395
|
{
|
|
@@ -35175,7 +35400,8 @@ const reactDoctorRules = [
|
|
|
35175
35400
|
rule: {
|
|
35176
35401
|
...effectNeedsCleanup,
|
|
35177
35402
|
framework: "global",
|
|
35178
|
-
category: "Bugs"
|
|
35403
|
+
category: "Bugs",
|
|
35404
|
+
requires: [...new Set(["react", ...effectNeedsCleanup.requires ?? []])]
|
|
35179
35405
|
}
|
|
35180
35406
|
},
|
|
35181
35407
|
{
|
|
@@ -35186,7 +35412,8 @@ const reactDoctorRules = [
|
|
|
35186
35412
|
rule: {
|
|
35187
35413
|
...exhaustiveDeps,
|
|
35188
35414
|
framework: "global",
|
|
35189
|
-
category: "Bugs"
|
|
35415
|
+
category: "Bugs",
|
|
35416
|
+
requires: [...new Set(["react", ...exhaustiveDeps.requires ?? []])]
|
|
35190
35417
|
}
|
|
35191
35418
|
},
|
|
35192
35419
|
{
|
|
@@ -35209,7 +35436,8 @@ const reactDoctorRules = [
|
|
|
35209
35436
|
rule: {
|
|
35210
35437
|
...forbidComponentProps,
|
|
35211
35438
|
framework: "global",
|
|
35212
|
-
category: "Maintainability"
|
|
35439
|
+
category: "Maintainability",
|
|
35440
|
+
requires: [...new Set(["react", ...forbidComponentProps.requires ?? []])]
|
|
35213
35441
|
}
|
|
35214
35442
|
},
|
|
35215
35443
|
{
|
|
@@ -35220,7 +35448,8 @@ const reactDoctorRules = [
|
|
|
35220
35448
|
rule: {
|
|
35221
35449
|
...forbidDomProps,
|
|
35222
35450
|
framework: "global",
|
|
35223
|
-
category: "Maintainability"
|
|
35451
|
+
category: "Maintainability",
|
|
35452
|
+
requires: [...new Set(["react", ...forbidDomProps.requires ?? []])]
|
|
35224
35453
|
}
|
|
35225
35454
|
},
|
|
35226
35455
|
{
|
|
@@ -35231,7 +35460,8 @@ const reactDoctorRules = [
|
|
|
35231
35460
|
rule: {
|
|
35232
35461
|
...forbidElements,
|
|
35233
35462
|
framework: "global",
|
|
35234
|
-
category: "Maintainability"
|
|
35463
|
+
category: "Maintainability",
|
|
35464
|
+
requires: [...new Set(["react", ...forbidElements.requires ?? []])]
|
|
35235
35465
|
}
|
|
35236
35466
|
},
|
|
35237
35467
|
{
|
|
@@ -35242,7 +35472,8 @@ const reactDoctorRules = [
|
|
|
35242
35472
|
rule: {
|
|
35243
35473
|
...forwardRefUsesRef,
|
|
35244
35474
|
framework: "global",
|
|
35245
|
-
category: "Maintainability"
|
|
35475
|
+
category: "Maintainability",
|
|
35476
|
+
requires: [...new Set(["react", ...forwardRefUsesRef.requires ?? []])]
|
|
35246
35477
|
}
|
|
35247
35478
|
},
|
|
35248
35479
|
{
|
|
@@ -35253,7 +35484,8 @@ const reactDoctorRules = [
|
|
|
35253
35484
|
rule: {
|
|
35254
35485
|
...headingHasContent,
|
|
35255
35486
|
framework: "global",
|
|
35256
|
-
category: "Accessibility"
|
|
35487
|
+
category: "Accessibility",
|
|
35488
|
+
requires: [...new Set(["react", ...headingHasContent.requires ?? []])]
|
|
35257
35489
|
}
|
|
35258
35490
|
},
|
|
35259
35491
|
{
|
|
@@ -35264,7 +35496,8 @@ const reactDoctorRules = [
|
|
|
35264
35496
|
rule: {
|
|
35265
35497
|
...hookUseState,
|
|
35266
35498
|
framework: "global",
|
|
35267
|
-
category: "Maintainability"
|
|
35499
|
+
category: "Maintainability",
|
|
35500
|
+
requires: [...new Set(["react", ...hookUseState.requires ?? []])]
|
|
35268
35501
|
}
|
|
35269
35502
|
},
|
|
35270
35503
|
{
|
|
@@ -35275,7 +35508,8 @@ const reactDoctorRules = [
|
|
|
35275
35508
|
rule: {
|
|
35276
35509
|
...hooksNoNanInDeps,
|
|
35277
35510
|
framework: "global",
|
|
35278
|
-
category: "Bugs"
|
|
35511
|
+
category: "Bugs",
|
|
35512
|
+
requires: [...new Set(["react", ...hooksNoNanInDeps.requires ?? []])]
|
|
35279
35513
|
}
|
|
35280
35514
|
},
|
|
35281
35515
|
{
|
|
@@ -35286,7 +35520,8 @@ const reactDoctorRules = [
|
|
|
35286
35520
|
rule: {
|
|
35287
35521
|
...htmlHasLang,
|
|
35288
35522
|
framework: "global",
|
|
35289
|
-
category: "Accessibility"
|
|
35523
|
+
category: "Accessibility",
|
|
35524
|
+
requires: [...new Set(["react", ...htmlHasLang.requires ?? []])]
|
|
35290
35525
|
}
|
|
35291
35526
|
},
|
|
35292
35527
|
{
|
|
@@ -35330,7 +35565,8 @@ const reactDoctorRules = [
|
|
|
35330
35565
|
rule: {
|
|
35331
35566
|
...iframeHasTitle,
|
|
35332
35567
|
framework: "global",
|
|
35333
|
-
category: "Accessibility"
|
|
35568
|
+
category: "Accessibility",
|
|
35569
|
+
requires: [...new Set(["react", ...iframeHasTitle.requires ?? []])]
|
|
35334
35570
|
}
|
|
35335
35571
|
},
|
|
35336
35572
|
{
|
|
@@ -35341,7 +35577,8 @@ const reactDoctorRules = [
|
|
|
35341
35577
|
rule: {
|
|
35342
35578
|
...iframeMissingSandbox,
|
|
35343
35579
|
framework: "global",
|
|
35344
|
-
category: "Security"
|
|
35580
|
+
category: "Security",
|
|
35581
|
+
requires: [...new Set(["react", ...iframeMissingSandbox.requires ?? []])]
|
|
35345
35582
|
}
|
|
35346
35583
|
},
|
|
35347
35584
|
{
|
|
@@ -35352,7 +35589,8 @@ const reactDoctorRules = [
|
|
|
35352
35589
|
rule: {
|
|
35353
35590
|
...imgRedundantAlt,
|
|
35354
35591
|
framework: "global",
|
|
35355
|
-
category: "Accessibility"
|
|
35592
|
+
category: "Accessibility",
|
|
35593
|
+
requires: [...new Set(["react", ...imgRedundantAlt.requires ?? []])]
|
|
35356
35594
|
}
|
|
35357
35595
|
},
|
|
35358
35596
|
{
|
|
@@ -35363,7 +35601,8 @@ const reactDoctorRules = [
|
|
|
35363
35601
|
rule: {
|
|
35364
35602
|
...interactiveSupportsFocus,
|
|
35365
35603
|
framework: "global",
|
|
35366
|
-
category: "Accessibility"
|
|
35604
|
+
category: "Accessibility",
|
|
35605
|
+
requires: [...new Set(["react", ...interactiveSupportsFocus.requires ?? []])]
|
|
35367
35606
|
}
|
|
35368
35607
|
},
|
|
35369
35608
|
{
|
|
@@ -35374,7 +35613,8 @@ const reactDoctorRules = [
|
|
|
35374
35613
|
rule: {
|
|
35375
35614
|
...jotaiDerivedAtomReturnsFreshObject,
|
|
35376
35615
|
framework: "global",
|
|
35377
|
-
category: "Bugs"
|
|
35616
|
+
category: "Bugs",
|
|
35617
|
+
requires: [...new Set(["react", ...jotaiDerivedAtomReturnsFreshObject.requires ?? []])]
|
|
35378
35618
|
}
|
|
35379
35619
|
},
|
|
35380
35620
|
{
|
|
@@ -35385,7 +35625,8 @@ const reactDoctorRules = [
|
|
|
35385
35625
|
rule: {
|
|
35386
35626
|
...jotaiSelectAtomInRenderBody,
|
|
35387
35627
|
framework: "global",
|
|
35388
|
-
category: "Bugs"
|
|
35628
|
+
category: "Bugs",
|
|
35629
|
+
requires: [...new Set(["react", ...jotaiSelectAtomInRenderBody.requires ?? []])]
|
|
35389
35630
|
}
|
|
35390
35631
|
},
|
|
35391
35632
|
{
|
|
@@ -35396,7 +35637,8 @@ const reactDoctorRules = [
|
|
|
35396
35637
|
rule: {
|
|
35397
35638
|
...jotaiTqUseRawQueryAtom,
|
|
35398
35639
|
framework: "global",
|
|
35399
|
-
category: "Bugs"
|
|
35640
|
+
category: "Bugs",
|
|
35641
|
+
requires: [...new Set(["react", ...jotaiTqUseRawQueryAtom.requires ?? []])]
|
|
35400
35642
|
}
|
|
35401
35643
|
},
|
|
35402
35644
|
{
|
|
@@ -35561,7 +35803,8 @@ const reactDoctorRules = [
|
|
|
35561
35803
|
rule: {
|
|
35562
35804
|
...jsxBooleanValue,
|
|
35563
35805
|
framework: "global",
|
|
35564
|
-
category: "Maintainability"
|
|
35806
|
+
category: "Maintainability",
|
|
35807
|
+
requires: [...new Set(["react", ...jsxBooleanValue.requires ?? []])]
|
|
35565
35808
|
}
|
|
35566
35809
|
},
|
|
35567
35810
|
{
|
|
@@ -35572,7 +35815,8 @@ const reactDoctorRules = [
|
|
|
35572
35815
|
rule: {
|
|
35573
35816
|
...jsxCurlyBracePresence,
|
|
35574
35817
|
framework: "global",
|
|
35575
|
-
category: "Maintainability"
|
|
35818
|
+
category: "Maintainability",
|
|
35819
|
+
requires: [...new Set(["react", ...jsxCurlyBracePresence.requires ?? []])]
|
|
35576
35820
|
}
|
|
35577
35821
|
},
|
|
35578
35822
|
{
|
|
@@ -35583,7 +35827,8 @@ const reactDoctorRules = [
|
|
|
35583
35827
|
rule: {
|
|
35584
35828
|
...jsxFilenameExtension,
|
|
35585
35829
|
framework: "global",
|
|
35586
|
-
category: "Maintainability"
|
|
35830
|
+
category: "Maintainability",
|
|
35831
|
+
requires: [...new Set(["react", ...jsxFilenameExtension.requires ?? []])]
|
|
35587
35832
|
}
|
|
35588
35833
|
},
|
|
35589
35834
|
{
|
|
@@ -35594,7 +35839,8 @@ const reactDoctorRules = [
|
|
|
35594
35839
|
rule: {
|
|
35595
35840
|
...jsxFragments,
|
|
35596
35841
|
framework: "global",
|
|
35597
|
-
category: "Maintainability"
|
|
35842
|
+
category: "Maintainability",
|
|
35843
|
+
requires: [...new Set(["react", ...jsxFragments.requires ?? []])]
|
|
35598
35844
|
}
|
|
35599
35845
|
},
|
|
35600
35846
|
{
|
|
@@ -35605,7 +35851,8 @@ const reactDoctorRules = [
|
|
|
35605
35851
|
rule: {
|
|
35606
35852
|
...jsxHandlerNames,
|
|
35607
35853
|
framework: "global",
|
|
35608
|
-
category: "Maintainability"
|
|
35854
|
+
category: "Maintainability",
|
|
35855
|
+
requires: [...new Set(["react", ...jsxHandlerNames.requires ?? []])]
|
|
35609
35856
|
}
|
|
35610
35857
|
},
|
|
35611
35858
|
{
|
|
@@ -35616,7 +35863,8 @@ const reactDoctorRules = [
|
|
|
35616
35863
|
rule: {
|
|
35617
35864
|
...jsxKey,
|
|
35618
35865
|
framework: "global",
|
|
35619
|
-
category: "Bugs"
|
|
35866
|
+
category: "Bugs",
|
|
35867
|
+
requires: [...new Set(["react", ...jsxKey.requires ?? []])]
|
|
35620
35868
|
}
|
|
35621
35869
|
},
|
|
35622
35870
|
{
|
|
@@ -35627,7 +35875,8 @@ const reactDoctorRules = [
|
|
|
35627
35875
|
rule: {
|
|
35628
35876
|
...jsxMaxDepth,
|
|
35629
35877
|
framework: "global",
|
|
35630
|
-
category: "Maintainability"
|
|
35878
|
+
category: "Maintainability",
|
|
35879
|
+
requires: [...new Set(["react", ...jsxMaxDepth.requires ?? []])]
|
|
35631
35880
|
}
|
|
35632
35881
|
},
|
|
35633
35882
|
{
|
|
@@ -35638,7 +35887,8 @@ const reactDoctorRules = [
|
|
|
35638
35887
|
rule: {
|
|
35639
35888
|
...jsxNoCommentTextnodes,
|
|
35640
35889
|
framework: "global",
|
|
35641
|
-
category: "Bugs"
|
|
35890
|
+
category: "Bugs",
|
|
35891
|
+
requires: [...new Set(["react", ...jsxNoCommentTextnodes.requires ?? []])]
|
|
35642
35892
|
}
|
|
35643
35893
|
},
|
|
35644
35894
|
{
|
|
@@ -35649,7 +35899,8 @@ const reactDoctorRules = [
|
|
|
35649
35899
|
rule: {
|
|
35650
35900
|
...jsxNoConstructedContextValues,
|
|
35651
35901
|
framework: "global",
|
|
35652
|
-
category: "Performance"
|
|
35902
|
+
category: "Performance",
|
|
35903
|
+
requires: [...new Set(["react", ...jsxNoConstructedContextValues.requires ?? []])]
|
|
35653
35904
|
}
|
|
35654
35905
|
},
|
|
35655
35906
|
{
|
|
@@ -35660,7 +35911,8 @@ const reactDoctorRules = [
|
|
|
35660
35911
|
rule: {
|
|
35661
35912
|
...jsxNoDuplicateProps,
|
|
35662
35913
|
framework: "global",
|
|
35663
|
-
category: "Bugs"
|
|
35914
|
+
category: "Bugs",
|
|
35915
|
+
requires: [...new Set(["react", ...jsxNoDuplicateProps.requires ?? []])]
|
|
35664
35916
|
}
|
|
35665
35917
|
},
|
|
35666
35918
|
{
|
|
@@ -35671,7 +35923,8 @@ const reactDoctorRules = [
|
|
|
35671
35923
|
rule: {
|
|
35672
35924
|
...jsxNoJsxAsProp,
|
|
35673
35925
|
framework: "global",
|
|
35674
|
-
category: "Performance"
|
|
35926
|
+
category: "Performance",
|
|
35927
|
+
requires: [...new Set(["react", ...jsxNoJsxAsProp.requires ?? []])]
|
|
35675
35928
|
}
|
|
35676
35929
|
},
|
|
35677
35930
|
{
|
|
@@ -35682,7 +35935,8 @@ const reactDoctorRules = [
|
|
|
35682
35935
|
rule: {
|
|
35683
35936
|
...jsxNoNewArrayAsProp,
|
|
35684
35937
|
framework: "global",
|
|
35685
|
-
category: "Performance"
|
|
35938
|
+
category: "Performance",
|
|
35939
|
+
requires: [...new Set(["react", ...jsxNoNewArrayAsProp.requires ?? []])]
|
|
35686
35940
|
}
|
|
35687
35941
|
},
|
|
35688
35942
|
{
|
|
@@ -35693,7 +35947,8 @@ const reactDoctorRules = [
|
|
|
35693
35947
|
rule: {
|
|
35694
35948
|
...jsxNoNewFunctionAsProp,
|
|
35695
35949
|
framework: "global",
|
|
35696
|
-
category: "Performance"
|
|
35950
|
+
category: "Performance",
|
|
35951
|
+
requires: [...new Set(["react", ...jsxNoNewFunctionAsProp.requires ?? []])]
|
|
35697
35952
|
}
|
|
35698
35953
|
},
|
|
35699
35954
|
{
|
|
@@ -35704,7 +35959,8 @@ const reactDoctorRules = [
|
|
|
35704
35959
|
rule: {
|
|
35705
35960
|
...jsxNoNewObjectAsProp,
|
|
35706
35961
|
framework: "global",
|
|
35707
|
-
category: "Performance"
|
|
35962
|
+
category: "Performance",
|
|
35963
|
+
requires: [...new Set(["react", ...jsxNoNewObjectAsProp.requires ?? []])]
|
|
35708
35964
|
}
|
|
35709
35965
|
},
|
|
35710
35966
|
{
|
|
@@ -35715,7 +35971,8 @@ const reactDoctorRules = [
|
|
|
35715
35971
|
rule: {
|
|
35716
35972
|
...jsxNoScriptUrl,
|
|
35717
35973
|
framework: "global",
|
|
35718
|
-
category: "Security"
|
|
35974
|
+
category: "Security",
|
|
35975
|
+
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
35719
35976
|
}
|
|
35720
35977
|
},
|
|
35721
35978
|
{
|
|
@@ -35726,7 +35983,8 @@ const reactDoctorRules = [
|
|
|
35726
35983
|
rule: {
|
|
35727
35984
|
...jsxNoUndef,
|
|
35728
35985
|
framework: "global",
|
|
35729
|
-
category: "Bugs"
|
|
35986
|
+
category: "Bugs",
|
|
35987
|
+
requires: [...new Set(["react", ...jsxNoUndef.requires ?? []])]
|
|
35730
35988
|
}
|
|
35731
35989
|
},
|
|
35732
35990
|
{
|
|
@@ -35737,7 +35995,8 @@ const reactDoctorRules = [
|
|
|
35737
35995
|
rule: {
|
|
35738
35996
|
...jsxNoUselessFragment,
|
|
35739
35997
|
framework: "global",
|
|
35740
|
-
category: "Maintainability"
|
|
35998
|
+
category: "Maintainability",
|
|
35999
|
+
requires: [...new Set(["react", ...jsxNoUselessFragment.requires ?? []])]
|
|
35741
36000
|
}
|
|
35742
36001
|
},
|
|
35743
36002
|
{
|
|
@@ -35748,7 +36007,8 @@ const reactDoctorRules = [
|
|
|
35748
36007
|
rule: {
|
|
35749
36008
|
...jsxPascalCase,
|
|
35750
36009
|
framework: "global",
|
|
35751
|
-
category: "Maintainability"
|
|
36010
|
+
category: "Maintainability",
|
|
36011
|
+
requires: [...new Set(["react", ...jsxPascalCase.requires ?? []])]
|
|
35752
36012
|
}
|
|
35753
36013
|
},
|
|
35754
36014
|
{
|
|
@@ -35759,7 +36019,8 @@ const reactDoctorRules = [
|
|
|
35759
36019
|
rule: {
|
|
35760
36020
|
...jsxPropsNoSpreadMulti,
|
|
35761
36021
|
framework: "global",
|
|
35762
|
-
category: "Bugs"
|
|
36022
|
+
category: "Bugs",
|
|
36023
|
+
requires: [...new Set(["react", ...jsxPropsNoSpreadMulti.requires ?? []])]
|
|
35763
36024
|
}
|
|
35764
36025
|
},
|
|
35765
36026
|
{
|
|
@@ -35770,7 +36031,8 @@ const reactDoctorRules = [
|
|
|
35770
36031
|
rule: {
|
|
35771
36032
|
...jsxPropsNoSpreading,
|
|
35772
36033
|
framework: "global",
|
|
35773
|
-
category: "Maintainability"
|
|
36034
|
+
category: "Maintainability",
|
|
36035
|
+
requires: [...new Set(["react", ...jsxPropsNoSpreading.requires ?? []])]
|
|
35774
36036
|
}
|
|
35775
36037
|
},
|
|
35776
36038
|
{
|
|
@@ -35781,7 +36043,8 @@ const reactDoctorRules = [
|
|
|
35781
36043
|
rule: {
|
|
35782
36044
|
...labelHasAssociatedControl,
|
|
35783
36045
|
framework: "global",
|
|
35784
|
-
category: "Accessibility"
|
|
36046
|
+
category: "Accessibility",
|
|
36047
|
+
requires: [...new Set(["react", ...labelHasAssociatedControl.requires ?? []])]
|
|
35785
36048
|
}
|
|
35786
36049
|
},
|
|
35787
36050
|
{
|
|
@@ -35792,7 +36055,8 @@ const reactDoctorRules = [
|
|
|
35792
36055
|
rule: {
|
|
35793
36056
|
...lang,
|
|
35794
36057
|
framework: "global",
|
|
35795
|
-
category: "Accessibility"
|
|
36058
|
+
category: "Accessibility",
|
|
36059
|
+
requires: [...new Set(["react", ...lang.requires ?? []])]
|
|
35796
36060
|
}
|
|
35797
36061
|
},
|
|
35798
36062
|
{
|
|
@@ -35803,7 +36067,8 @@ const reactDoctorRules = [
|
|
|
35803
36067
|
rule: {
|
|
35804
36068
|
...mediaHasCaption,
|
|
35805
36069
|
framework: "global",
|
|
35806
|
-
category: "Accessibility"
|
|
36070
|
+
category: "Accessibility",
|
|
36071
|
+
requires: [...new Set(["react", ...mediaHasCaption.requires ?? []])]
|
|
35807
36072
|
}
|
|
35808
36073
|
},
|
|
35809
36074
|
{
|
|
@@ -35814,7 +36079,8 @@ const reactDoctorRules = [
|
|
|
35814
36079
|
rule: {
|
|
35815
36080
|
...mouseEventsHaveKeyEvents,
|
|
35816
36081
|
framework: "global",
|
|
35817
|
-
category: "Accessibility"
|
|
36082
|
+
category: "Accessibility",
|
|
36083
|
+
requires: [...new Set(["react", ...mouseEventsHaveKeyEvents.requires ?? []])]
|
|
35818
36084
|
}
|
|
35819
36085
|
},
|
|
35820
36086
|
{
|
|
@@ -36078,7 +36344,8 @@ const reactDoctorRules = [
|
|
|
36078
36344
|
rule: {
|
|
36079
36345
|
...noAccessKey,
|
|
36080
36346
|
framework: "global",
|
|
36081
|
-
category: "Accessibility"
|
|
36347
|
+
category: "Accessibility",
|
|
36348
|
+
requires: [...new Set(["react", ...noAccessKey.requires ?? []])]
|
|
36082
36349
|
}
|
|
36083
36350
|
},
|
|
36084
36351
|
{
|
|
@@ -36089,7 +36356,8 @@ const reactDoctorRules = [
|
|
|
36089
36356
|
rule: {
|
|
36090
36357
|
...noAdjustStateOnPropChange,
|
|
36091
36358
|
framework: "global",
|
|
36092
|
-
category: "Bugs"
|
|
36359
|
+
category: "Bugs",
|
|
36360
|
+
requires: [...new Set(["react", ...noAdjustStateOnPropChange.requires ?? []])]
|
|
36093
36361
|
}
|
|
36094
36362
|
},
|
|
36095
36363
|
{
|
|
@@ -36100,7 +36368,8 @@ const reactDoctorRules = [
|
|
|
36100
36368
|
rule: {
|
|
36101
36369
|
...noAriaHiddenOnFocusable,
|
|
36102
36370
|
framework: "global",
|
|
36103
|
-
category: "Accessibility"
|
|
36371
|
+
category: "Accessibility",
|
|
36372
|
+
requires: [...new Set(["react", ...noAriaHiddenOnFocusable.requires ?? []])]
|
|
36104
36373
|
}
|
|
36105
36374
|
},
|
|
36106
36375
|
{
|
|
@@ -36122,7 +36391,8 @@ const reactDoctorRules = [
|
|
|
36122
36391
|
rule: {
|
|
36123
36392
|
...noArrayIndexKey,
|
|
36124
36393
|
framework: "global",
|
|
36125
|
-
category: "Performance"
|
|
36394
|
+
category: "Performance",
|
|
36395
|
+
requires: [...new Set(["react", ...noArrayIndexKey.requires ?? []])]
|
|
36126
36396
|
}
|
|
36127
36397
|
},
|
|
36128
36398
|
{
|
|
@@ -36133,7 +36403,8 @@ const reactDoctorRules = [
|
|
|
36133
36403
|
rule: {
|
|
36134
36404
|
...noAutofocus,
|
|
36135
36405
|
framework: "global",
|
|
36136
|
-
category: "Accessibility"
|
|
36406
|
+
category: "Accessibility",
|
|
36407
|
+
requires: [...new Set(["react", ...noAutofocus.requires ?? []])]
|
|
36137
36408
|
}
|
|
36138
36409
|
},
|
|
36139
36410
|
{
|
|
@@ -36155,7 +36426,8 @@ const reactDoctorRules = [
|
|
|
36155
36426
|
rule: {
|
|
36156
36427
|
...noCascadingSetState,
|
|
36157
36428
|
framework: "global",
|
|
36158
|
-
category: "Bugs"
|
|
36429
|
+
category: "Bugs",
|
|
36430
|
+
requires: [...new Set(["react", ...noCascadingSetState.requires ?? []])]
|
|
36159
36431
|
}
|
|
36160
36432
|
},
|
|
36161
36433
|
{
|
|
@@ -36166,7 +36438,8 @@ const reactDoctorRules = [
|
|
|
36166
36438
|
rule: {
|
|
36167
36439
|
...noChainStateUpdates,
|
|
36168
36440
|
framework: "global",
|
|
36169
|
-
category: "Bugs"
|
|
36441
|
+
category: "Bugs",
|
|
36442
|
+
requires: [...new Set(["react", ...noChainStateUpdates.requires ?? []])]
|
|
36170
36443
|
}
|
|
36171
36444
|
},
|
|
36172
36445
|
{
|
|
@@ -36177,7 +36450,8 @@ const reactDoctorRules = [
|
|
|
36177
36450
|
rule: {
|
|
36178
36451
|
...noChildrenProp,
|
|
36179
36452
|
framework: "global",
|
|
36180
|
-
category: "Bugs"
|
|
36453
|
+
category: "Bugs",
|
|
36454
|
+
requires: [...new Set(["react", ...noChildrenProp.requires ?? []])]
|
|
36181
36455
|
}
|
|
36182
36456
|
},
|
|
36183
36457
|
{
|
|
@@ -36188,7 +36462,8 @@ const reactDoctorRules = [
|
|
|
36188
36462
|
rule: {
|
|
36189
36463
|
...noCloneElement,
|
|
36190
36464
|
framework: "global",
|
|
36191
|
-
category: "Maintainability"
|
|
36465
|
+
category: "Maintainability",
|
|
36466
|
+
requires: [...new Set(["react", ...noCloneElement.requires ?? []])]
|
|
36192
36467
|
}
|
|
36193
36468
|
},
|
|
36194
36469
|
{
|
|
@@ -36199,7 +36474,8 @@ const reactDoctorRules = [
|
|
|
36199
36474
|
rule: {
|
|
36200
36475
|
...noCreateContextInRender,
|
|
36201
36476
|
framework: "global",
|
|
36202
|
-
category: "Bugs"
|
|
36477
|
+
category: "Bugs",
|
|
36478
|
+
requires: [...new Set(["react", ...noCreateContextInRender.requires ?? []])]
|
|
36203
36479
|
}
|
|
36204
36480
|
},
|
|
36205
36481
|
{
|
|
@@ -36210,7 +36486,8 @@ const reactDoctorRules = [
|
|
|
36210
36486
|
rule: {
|
|
36211
36487
|
...noCreateStoreInRender,
|
|
36212
36488
|
framework: "global",
|
|
36213
|
-
category: "Bugs"
|
|
36489
|
+
category: "Bugs",
|
|
36490
|
+
requires: [...new Set(["react", ...noCreateStoreInRender.requires ?? []])]
|
|
36214
36491
|
}
|
|
36215
36492
|
},
|
|
36216
36493
|
{
|
|
@@ -36221,7 +36498,8 @@ const reactDoctorRules = [
|
|
|
36221
36498
|
rule: {
|
|
36222
36499
|
...noDanger,
|
|
36223
36500
|
framework: "global",
|
|
36224
|
-
category: "Security"
|
|
36501
|
+
category: "Security",
|
|
36502
|
+
requires: [...new Set(["react", ...noDanger.requires ?? []])]
|
|
36225
36503
|
}
|
|
36226
36504
|
},
|
|
36227
36505
|
{
|
|
@@ -36232,7 +36510,8 @@ const reactDoctorRules = [
|
|
|
36232
36510
|
rule: {
|
|
36233
36511
|
...noDangerWithChildren,
|
|
36234
36512
|
framework: "global",
|
|
36235
|
-
category: "Bugs"
|
|
36513
|
+
category: "Bugs",
|
|
36514
|
+
requires: [...new Set(["react", ...noDangerWithChildren.requires ?? []])]
|
|
36236
36515
|
}
|
|
36237
36516
|
},
|
|
36238
36517
|
{
|
|
@@ -36265,7 +36544,8 @@ const reactDoctorRules = [
|
|
|
36265
36544
|
rule: {
|
|
36266
36545
|
...noDerivedState,
|
|
36267
36546
|
framework: "global",
|
|
36268
|
-
category: "Bugs"
|
|
36547
|
+
category: "Bugs",
|
|
36548
|
+
requires: [...new Set(["react", ...noDerivedState.requires ?? []])]
|
|
36269
36549
|
}
|
|
36270
36550
|
},
|
|
36271
36551
|
{
|
|
@@ -36276,7 +36556,8 @@ const reactDoctorRules = [
|
|
|
36276
36556
|
rule: {
|
|
36277
36557
|
...noDerivedStateEffect,
|
|
36278
36558
|
framework: "global",
|
|
36279
|
-
category: "Bugs"
|
|
36559
|
+
category: "Bugs",
|
|
36560
|
+
requires: [...new Set(["react", ...noDerivedStateEffect.requires ?? []])]
|
|
36280
36561
|
}
|
|
36281
36562
|
},
|
|
36282
36563
|
{
|
|
@@ -36287,7 +36568,8 @@ const reactDoctorRules = [
|
|
|
36287
36568
|
rule: {
|
|
36288
36569
|
...noDerivedUseState,
|
|
36289
36570
|
framework: "global",
|
|
36290
|
-
category: "Bugs"
|
|
36571
|
+
category: "Bugs",
|
|
36572
|
+
requires: [...new Set(["react", ...noDerivedUseState.requires ?? []])]
|
|
36291
36573
|
}
|
|
36292
36574
|
},
|
|
36293
36575
|
{
|
|
@@ -36298,7 +36580,8 @@ const reactDoctorRules = [
|
|
|
36298
36580
|
rule: {
|
|
36299
36581
|
...noDidMountSetState,
|
|
36300
36582
|
framework: "global",
|
|
36301
|
-
category: "Bugs"
|
|
36583
|
+
category: "Bugs",
|
|
36584
|
+
requires: [...new Set(["react", ...noDidMountSetState.requires ?? []])]
|
|
36302
36585
|
}
|
|
36303
36586
|
},
|
|
36304
36587
|
{
|
|
@@ -36309,7 +36592,8 @@ const reactDoctorRules = [
|
|
|
36309
36592
|
rule: {
|
|
36310
36593
|
...noDidUpdateSetState,
|
|
36311
36594
|
framework: "global",
|
|
36312
|
-
category: "Bugs"
|
|
36595
|
+
category: "Bugs",
|
|
36596
|
+
requires: [...new Set(["react", ...noDidUpdateSetState.requires ?? []])]
|
|
36313
36597
|
}
|
|
36314
36598
|
},
|
|
36315
36599
|
{
|
|
@@ -36320,7 +36604,8 @@ const reactDoctorRules = [
|
|
|
36320
36604
|
rule: {
|
|
36321
36605
|
...noDirectMutationState,
|
|
36322
36606
|
framework: "global",
|
|
36323
|
-
category: "Bugs"
|
|
36607
|
+
category: "Bugs",
|
|
36608
|
+
requires: [...new Set(["react", ...noDirectMutationState.requires ?? []])]
|
|
36324
36609
|
}
|
|
36325
36610
|
},
|
|
36326
36611
|
{
|
|
@@ -36331,7 +36616,8 @@ const reactDoctorRules = [
|
|
|
36331
36616
|
rule: {
|
|
36332
36617
|
...noDirectStateMutation,
|
|
36333
36618
|
framework: "global",
|
|
36334
|
-
category: "Bugs"
|
|
36619
|
+
category: "Bugs",
|
|
36620
|
+
requires: [...new Set(["react", ...noDirectStateMutation.requires ?? []])]
|
|
36335
36621
|
}
|
|
36336
36622
|
},
|
|
36337
36623
|
{
|
|
@@ -36353,7 +36639,8 @@ const reactDoctorRules = [
|
|
|
36353
36639
|
rule: {
|
|
36354
36640
|
...noDistractingElements,
|
|
36355
36641
|
framework: "global",
|
|
36356
|
-
category: "Accessibility"
|
|
36642
|
+
category: "Accessibility",
|
|
36643
|
+
requires: [...new Set(["react", ...noDistractingElements.requires ?? []])]
|
|
36357
36644
|
}
|
|
36358
36645
|
},
|
|
36359
36646
|
{
|
|
@@ -36364,7 +36651,8 @@ const reactDoctorRules = [
|
|
|
36364
36651
|
rule: {
|
|
36365
36652
|
...noDocumentStartViewTransition,
|
|
36366
36653
|
framework: "global",
|
|
36367
|
-
category: "Bugs"
|
|
36654
|
+
category: "Bugs",
|
|
36655
|
+
requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
|
|
36368
36656
|
}
|
|
36369
36657
|
},
|
|
36370
36658
|
{
|
|
@@ -36386,7 +36674,8 @@ const reactDoctorRules = [
|
|
|
36386
36674
|
rule: {
|
|
36387
36675
|
...noEffectChain,
|
|
36388
36676
|
framework: "global",
|
|
36389
|
-
category: "Bugs"
|
|
36677
|
+
category: "Bugs",
|
|
36678
|
+
requires: [...new Set(["react", ...noEffectChain.requires ?? []])]
|
|
36390
36679
|
}
|
|
36391
36680
|
},
|
|
36392
36681
|
{
|
|
@@ -36397,7 +36686,8 @@ const reactDoctorRules = [
|
|
|
36397
36686
|
rule: {
|
|
36398
36687
|
...noEffectEventHandler,
|
|
36399
36688
|
framework: "global",
|
|
36400
|
-
category: "Bugs"
|
|
36689
|
+
category: "Bugs",
|
|
36690
|
+
requires: [...new Set(["react", ...noEffectEventHandler.requires ?? []])]
|
|
36401
36691
|
}
|
|
36402
36692
|
},
|
|
36403
36693
|
{
|
|
@@ -36408,7 +36698,8 @@ const reactDoctorRules = [
|
|
|
36408
36698
|
rule: {
|
|
36409
36699
|
...noEffectEventInDeps,
|
|
36410
36700
|
framework: "global",
|
|
36411
|
-
category: "Bugs"
|
|
36701
|
+
category: "Bugs",
|
|
36702
|
+
requires: [...new Set(["react", ...noEffectEventInDeps.requires ?? []])]
|
|
36412
36703
|
}
|
|
36413
36704
|
},
|
|
36414
36705
|
{
|
|
@@ -36419,7 +36710,8 @@ const reactDoctorRules = [
|
|
|
36419
36710
|
rule: {
|
|
36420
36711
|
...noEffectWithFreshDeps,
|
|
36421
36712
|
framework: "global",
|
|
36422
|
-
category: "Bugs"
|
|
36713
|
+
category: "Bugs",
|
|
36714
|
+
requires: [...new Set(["react", ...noEffectWithFreshDeps.requires ?? []])]
|
|
36423
36715
|
}
|
|
36424
36716
|
},
|
|
36425
36717
|
{
|
|
@@ -36441,7 +36733,8 @@ const reactDoctorRules = [
|
|
|
36441
36733
|
rule: {
|
|
36442
36734
|
...noEventHandler,
|
|
36443
36735
|
framework: "global",
|
|
36444
|
-
category: "Bugs"
|
|
36736
|
+
category: "Bugs",
|
|
36737
|
+
requires: [...new Set(["react", ...noEventHandler.requires ?? []])]
|
|
36445
36738
|
}
|
|
36446
36739
|
},
|
|
36447
36740
|
{
|
|
@@ -36452,7 +36745,8 @@ const reactDoctorRules = [
|
|
|
36452
36745
|
rule: {
|
|
36453
36746
|
...noEventTriggerState,
|
|
36454
36747
|
framework: "global",
|
|
36455
|
-
category: "Bugs"
|
|
36748
|
+
category: "Bugs",
|
|
36749
|
+
requires: [...new Set(["react", ...noEventTriggerState.requires ?? []])]
|
|
36456
36750
|
}
|
|
36457
36751
|
},
|
|
36458
36752
|
{
|
|
@@ -36463,7 +36757,8 @@ const reactDoctorRules = [
|
|
|
36463
36757
|
rule: {
|
|
36464
36758
|
...noFetchInEffect,
|
|
36465
36759
|
framework: "global",
|
|
36466
|
-
category: "Bugs"
|
|
36760
|
+
category: "Bugs",
|
|
36761
|
+
requires: [...new Set(["react", ...noFetchInEffect.requires ?? []])]
|
|
36467
36762
|
}
|
|
36468
36763
|
},
|
|
36469
36764
|
{
|
|
@@ -36474,7 +36769,8 @@ const reactDoctorRules = [
|
|
|
36474
36769
|
rule: {
|
|
36475
36770
|
...noFindDomNode,
|
|
36476
36771
|
framework: "global",
|
|
36477
|
-
category: "Bugs"
|
|
36772
|
+
category: "Bugs",
|
|
36773
|
+
requires: [...new Set(["react", ...noFindDomNode.requires ?? []])]
|
|
36478
36774
|
}
|
|
36479
36775
|
},
|
|
36480
36776
|
{
|
|
@@ -36485,7 +36781,8 @@ const reactDoctorRules = [
|
|
|
36485
36781
|
rule: {
|
|
36486
36782
|
...noFlushSync,
|
|
36487
36783
|
framework: "global",
|
|
36488
|
-
category: "Performance"
|
|
36784
|
+
category: "Performance",
|
|
36785
|
+
requires: [...new Set(["react", ...noFlushSync.requires ?? []])]
|
|
36489
36786
|
}
|
|
36490
36787
|
},
|
|
36491
36788
|
{
|
|
@@ -36529,7 +36826,8 @@ const reactDoctorRules = [
|
|
|
36529
36826
|
rule: {
|
|
36530
36827
|
...noGlobalCssVariableAnimation,
|
|
36531
36828
|
framework: "global",
|
|
36532
|
-
category: "Performance"
|
|
36829
|
+
category: "Performance",
|
|
36830
|
+
requires: [...new Set(["react", ...noGlobalCssVariableAnimation.requires ?? []])]
|
|
36533
36831
|
}
|
|
36534
36832
|
},
|
|
36535
36833
|
{
|
|
@@ -36562,7 +36860,8 @@ const reactDoctorRules = [
|
|
|
36562
36860
|
rule: {
|
|
36563
36861
|
...noInitializeState,
|
|
36564
36862
|
framework: "global",
|
|
36565
|
-
category: "Bugs"
|
|
36863
|
+
category: "Bugs",
|
|
36864
|
+
requires: [...new Set(["react", ...noInitializeState.requires ?? []])]
|
|
36566
36865
|
}
|
|
36567
36866
|
},
|
|
36568
36867
|
{
|
|
@@ -36595,7 +36894,8 @@ const reactDoctorRules = [
|
|
|
36595
36894
|
rule: {
|
|
36596
36895
|
...noInlinePropOnMemoComponent,
|
|
36597
36896
|
framework: "global",
|
|
36598
|
-
category: "Performance"
|
|
36897
|
+
category: "Performance",
|
|
36898
|
+
requires: [...new Set(["react", ...noInlinePropOnMemoComponent.requires ?? []])]
|
|
36599
36899
|
}
|
|
36600
36900
|
},
|
|
36601
36901
|
{
|
|
@@ -36606,7 +36906,8 @@ const reactDoctorRules = [
|
|
|
36606
36906
|
rule: {
|
|
36607
36907
|
...noInteractiveElementToNoninteractiveRole,
|
|
36608
36908
|
framework: "global",
|
|
36609
|
-
category: "Accessibility"
|
|
36909
|
+
category: "Accessibility",
|
|
36910
|
+
requires: [...new Set(["react", ...noInteractiveElementToNoninteractiveRole.requires ?? []])]
|
|
36610
36911
|
}
|
|
36611
36912
|
},
|
|
36612
36913
|
{
|
|
@@ -36617,7 +36918,8 @@ const reactDoctorRules = [
|
|
|
36617
36918
|
rule: {
|
|
36618
36919
|
...noIsMounted,
|
|
36619
36920
|
framework: "global",
|
|
36620
|
-
category: "Bugs"
|
|
36921
|
+
category: "Bugs",
|
|
36922
|
+
requires: [...new Set(["react", ...noIsMounted.requires ?? []])]
|
|
36621
36923
|
}
|
|
36622
36924
|
},
|
|
36623
36925
|
{
|
|
@@ -36650,7 +36952,8 @@ const reactDoctorRules = [
|
|
|
36650
36952
|
rule: {
|
|
36651
36953
|
...noLargeAnimatedBlur,
|
|
36652
36954
|
framework: "global",
|
|
36653
|
-
category: "Performance"
|
|
36955
|
+
category: "Performance",
|
|
36956
|
+
requires: [...new Set(["react", ...noLargeAnimatedBlur.requires ?? []])]
|
|
36654
36957
|
}
|
|
36655
36958
|
},
|
|
36656
36959
|
{
|
|
@@ -36661,7 +36964,8 @@ const reactDoctorRules = [
|
|
|
36661
36964
|
rule: {
|
|
36662
36965
|
...noLayoutPropertyAnimation,
|
|
36663
36966
|
framework: "global",
|
|
36664
|
-
category: "Performance"
|
|
36967
|
+
category: "Performance",
|
|
36968
|
+
requires: [...new Set(["react", ...noLayoutPropertyAnimation.requires ?? []])]
|
|
36665
36969
|
}
|
|
36666
36970
|
},
|
|
36667
36971
|
{
|
|
@@ -36727,7 +37031,8 @@ const reactDoctorRules = [
|
|
|
36727
37031
|
rule: {
|
|
36728
37032
|
...noMirrorPropEffect,
|
|
36729
37033
|
framework: "global",
|
|
36730
|
-
category: "Bugs"
|
|
37034
|
+
category: "Bugs",
|
|
37035
|
+
requires: [...new Set(["react", ...noMirrorPropEffect.requires ?? []])]
|
|
36731
37036
|
}
|
|
36732
37037
|
},
|
|
36733
37038
|
{
|
|
@@ -36749,7 +37054,8 @@ const reactDoctorRules = [
|
|
|
36749
37054
|
rule: {
|
|
36750
37055
|
...noMultiComp,
|
|
36751
37056
|
framework: "global",
|
|
36752
|
-
category: "Maintainability"
|
|
37057
|
+
category: "Maintainability",
|
|
37058
|
+
requires: [...new Set(["react", ...noMultiComp.requires ?? []])]
|
|
36753
37059
|
}
|
|
36754
37060
|
},
|
|
36755
37061
|
{
|
|
@@ -36760,7 +37066,8 @@ const reactDoctorRules = [
|
|
|
36760
37066
|
rule: {
|
|
36761
37067
|
...noMutableInDeps,
|
|
36762
37068
|
framework: "global",
|
|
36763
|
-
category: "Bugs"
|
|
37069
|
+
category: "Bugs",
|
|
37070
|
+
requires: [...new Set(["react", ...noMutableInDeps.requires ?? []])]
|
|
36764
37071
|
}
|
|
36765
37072
|
},
|
|
36766
37073
|
{
|
|
@@ -36771,7 +37078,8 @@ const reactDoctorRules = [
|
|
|
36771
37078
|
rule: {
|
|
36772
37079
|
...noMutatingReducerState,
|
|
36773
37080
|
framework: "global",
|
|
36774
|
-
category: "Bugs"
|
|
37081
|
+
category: "Bugs",
|
|
37082
|
+
requires: [...new Set(["react", ...noMutatingReducerState.requires ?? []])]
|
|
36775
37083
|
}
|
|
36776
37084
|
},
|
|
36777
37085
|
{
|
|
@@ -36782,7 +37090,8 @@ const reactDoctorRules = [
|
|
|
36782
37090
|
rule: {
|
|
36783
37091
|
...noNamespace,
|
|
36784
37092
|
framework: "global",
|
|
36785
|
-
category: "Bugs"
|
|
37093
|
+
category: "Bugs",
|
|
37094
|
+
requires: [...new Set(["react", ...noNamespace.requires ?? []])]
|
|
36786
37095
|
}
|
|
36787
37096
|
},
|
|
36788
37097
|
{
|
|
@@ -36804,7 +37113,8 @@ const reactDoctorRules = [
|
|
|
36804
37113
|
rule: {
|
|
36805
37114
|
...noNoninteractiveElementInteractions,
|
|
36806
37115
|
framework: "global",
|
|
36807
|
-
category: "Accessibility"
|
|
37116
|
+
category: "Accessibility",
|
|
37117
|
+
requires: [...new Set(["react", ...noNoninteractiveElementInteractions.requires ?? []])]
|
|
36808
37118
|
}
|
|
36809
37119
|
},
|
|
36810
37120
|
{
|
|
@@ -36815,7 +37125,8 @@ const reactDoctorRules = [
|
|
|
36815
37125
|
rule: {
|
|
36816
37126
|
...noNoninteractiveElementToInteractiveRole,
|
|
36817
37127
|
framework: "global",
|
|
36818
|
-
category: "Accessibility"
|
|
37128
|
+
category: "Accessibility",
|
|
37129
|
+
requires: [...new Set(["react", ...noNoninteractiveElementToInteractiveRole.requires ?? []])]
|
|
36819
37130
|
}
|
|
36820
37131
|
},
|
|
36821
37132
|
{
|
|
@@ -36826,7 +37137,8 @@ const reactDoctorRules = [
|
|
|
36826
37137
|
rule: {
|
|
36827
37138
|
...noNoninteractiveTabindex,
|
|
36828
37139
|
framework: "global",
|
|
36829
|
-
category: "Accessibility"
|
|
37140
|
+
category: "Accessibility",
|
|
37141
|
+
requires: [...new Set(["react", ...noNoninteractiveTabindex.requires ?? []])]
|
|
36830
37142
|
}
|
|
36831
37143
|
},
|
|
36832
37144
|
{
|
|
@@ -36848,7 +37160,8 @@ const reactDoctorRules = [
|
|
|
36848
37160
|
rule: {
|
|
36849
37161
|
...noPassDataToParent,
|
|
36850
37162
|
framework: "global",
|
|
36851
|
-
category: "Bugs"
|
|
37163
|
+
category: "Bugs",
|
|
37164
|
+
requires: [...new Set(["react", ...noPassDataToParent.requires ?? []])]
|
|
36852
37165
|
}
|
|
36853
37166
|
},
|
|
36854
37167
|
{
|
|
@@ -36859,7 +37172,8 @@ const reactDoctorRules = [
|
|
|
36859
37172
|
rule: {
|
|
36860
37173
|
...noPassLiveStateToParent,
|
|
36861
37174
|
framework: "global",
|
|
36862
|
-
category: "Bugs"
|
|
37175
|
+
category: "Bugs",
|
|
37176
|
+
requires: [...new Set(["react", ...noPassLiveStateToParent.requires ?? []])]
|
|
36863
37177
|
}
|
|
36864
37178
|
},
|
|
36865
37179
|
{
|
|
@@ -36870,7 +37184,8 @@ const reactDoctorRules = [
|
|
|
36870
37184
|
rule: {
|
|
36871
37185
|
...noPermanentWillChange,
|
|
36872
37186
|
framework: "global",
|
|
36873
|
-
category: "Performance"
|
|
37187
|
+
category: "Performance",
|
|
37188
|
+
requires: [...new Set(["react", ...noPermanentWillChange.requires ?? []])]
|
|
36874
37189
|
}
|
|
36875
37190
|
},
|
|
36876
37191
|
{
|
|
@@ -36903,7 +37218,8 @@ const reactDoctorRules = [
|
|
|
36903
37218
|
rule: {
|
|
36904
37219
|
...noPropCallbackInEffect,
|
|
36905
37220
|
framework: "global",
|
|
36906
|
-
category: "Bugs"
|
|
37221
|
+
category: "Bugs",
|
|
37222
|
+
requires: [...new Set(["react", ...noPropCallbackInEffect.requires ?? []])]
|
|
36907
37223
|
}
|
|
36908
37224
|
},
|
|
36909
37225
|
{
|
|
@@ -36947,7 +37263,8 @@ const reactDoctorRules = [
|
|
|
36947
37263
|
rule: {
|
|
36948
37264
|
...noReactChildren,
|
|
36949
37265
|
framework: "global",
|
|
36950
|
-
category: "Maintainability"
|
|
37266
|
+
category: "Maintainability",
|
|
37267
|
+
requires: [...new Set(["react", ...noReactChildren.requires ?? []])]
|
|
36951
37268
|
}
|
|
36952
37269
|
},
|
|
36953
37270
|
{
|
|
@@ -36980,7 +37297,8 @@ const reactDoctorRules = [
|
|
|
36980
37297
|
rule: {
|
|
36981
37298
|
...noRedundantRoles,
|
|
36982
37299
|
framework: "global",
|
|
36983
|
-
category: "Accessibility"
|
|
37300
|
+
category: "Accessibility",
|
|
37301
|
+
requires: [...new Set(["react", ...noRedundantRoles.requires ?? []])]
|
|
36984
37302
|
}
|
|
36985
37303
|
},
|
|
36986
37304
|
{
|
|
@@ -36991,7 +37309,8 @@ const reactDoctorRules = [
|
|
|
36991
37309
|
rule: {
|
|
36992
37310
|
...noRedundantShouldComponentUpdate,
|
|
36993
37311
|
framework: "global",
|
|
36994
|
-
category: "Maintainability"
|
|
37312
|
+
category: "Maintainability",
|
|
37313
|
+
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
36995
37314
|
}
|
|
36996
37315
|
},
|
|
36997
37316
|
{
|
|
@@ -37024,7 +37343,8 @@ const reactDoctorRules = [
|
|
|
37024
37343
|
rule: {
|
|
37025
37344
|
...noRenderReturnValue,
|
|
37026
37345
|
framework: "global",
|
|
37027
|
-
category: "Bugs"
|
|
37346
|
+
category: "Bugs",
|
|
37347
|
+
requires: [...new Set(["react", ...noRenderReturnValue.requires ?? []])]
|
|
37028
37348
|
}
|
|
37029
37349
|
},
|
|
37030
37350
|
{
|
|
@@ -37035,7 +37355,8 @@ const reactDoctorRules = [
|
|
|
37035
37355
|
rule: {
|
|
37036
37356
|
...noResetAllStateOnPropChange,
|
|
37037
37357
|
framework: "global",
|
|
37038
|
-
category: "Bugs"
|
|
37358
|
+
category: "Bugs",
|
|
37359
|
+
requires: [...new Set(["react", ...noResetAllStateOnPropChange.requires ?? []])]
|
|
37039
37360
|
}
|
|
37040
37361
|
},
|
|
37041
37362
|
{
|
|
@@ -37046,7 +37367,8 @@ const reactDoctorRules = [
|
|
|
37046
37367
|
rule: {
|
|
37047
37368
|
...noScaleFromZero,
|
|
37048
37369
|
framework: "global",
|
|
37049
|
-
category: "Performance"
|
|
37370
|
+
category: "Performance",
|
|
37371
|
+
requires: [...new Set(["react", ...noScaleFromZero.requires ?? []])]
|
|
37050
37372
|
}
|
|
37051
37373
|
},
|
|
37052
37374
|
{
|
|
@@ -37068,7 +37390,8 @@ const reactDoctorRules = [
|
|
|
37068
37390
|
rule: {
|
|
37069
37391
|
...noSelfUpdatingEffect,
|
|
37070
37392
|
framework: "global",
|
|
37071
|
-
category: "Bugs"
|
|
37393
|
+
category: "Bugs",
|
|
37394
|
+
requires: [...new Set(["react", ...noSelfUpdatingEffect.requires ?? []])]
|
|
37072
37395
|
}
|
|
37073
37396
|
},
|
|
37074
37397
|
{
|
|
@@ -37079,7 +37402,8 @@ const reactDoctorRules = [
|
|
|
37079
37402
|
rule: {
|
|
37080
37403
|
...noSetState,
|
|
37081
37404
|
framework: "global",
|
|
37082
|
-
category: "Maintainability"
|
|
37405
|
+
category: "Maintainability",
|
|
37406
|
+
requires: [...new Set(["react", ...noSetState.requires ?? []])]
|
|
37083
37407
|
}
|
|
37084
37408
|
},
|
|
37085
37409
|
{
|
|
@@ -37090,7 +37414,8 @@ const reactDoctorRules = [
|
|
|
37090
37414
|
rule: {
|
|
37091
37415
|
...noSetStateInRender,
|
|
37092
37416
|
framework: "global",
|
|
37093
|
-
category: "Bugs"
|
|
37417
|
+
category: "Bugs",
|
|
37418
|
+
requires: [...new Set(["react", ...noSetStateInRender.requires ?? []])]
|
|
37094
37419
|
}
|
|
37095
37420
|
},
|
|
37096
37421
|
{
|
|
@@ -37112,7 +37437,8 @@ const reactDoctorRules = [
|
|
|
37112
37437
|
rule: {
|
|
37113
37438
|
...noStaticElementInteractions,
|
|
37114
37439
|
framework: "global",
|
|
37115
|
-
category: "Accessibility"
|
|
37440
|
+
category: "Accessibility",
|
|
37441
|
+
requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
|
|
37116
37442
|
}
|
|
37117
37443
|
},
|
|
37118
37444
|
{
|
|
@@ -37123,7 +37449,8 @@ const reactDoctorRules = [
|
|
|
37123
37449
|
rule: {
|
|
37124
37450
|
...noStringRefs,
|
|
37125
37451
|
framework: "global",
|
|
37126
|
-
category: "Bugs"
|
|
37452
|
+
category: "Bugs",
|
|
37453
|
+
requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
|
|
37127
37454
|
}
|
|
37128
37455
|
},
|
|
37129
37456
|
{
|
|
@@ -37134,7 +37461,8 @@ const reactDoctorRules = [
|
|
|
37134
37461
|
rule: {
|
|
37135
37462
|
...noThisInSfc,
|
|
37136
37463
|
framework: "global",
|
|
37137
|
-
category: "Bugs"
|
|
37464
|
+
category: "Bugs",
|
|
37465
|
+
requires: [...new Set(["react", ...noThisInSfc.requires ?? []])]
|
|
37138
37466
|
}
|
|
37139
37467
|
},
|
|
37140
37468
|
{
|
|
@@ -37156,7 +37484,8 @@ const reactDoctorRules = [
|
|
|
37156
37484
|
rule: {
|
|
37157
37485
|
...noTransitionAll,
|
|
37158
37486
|
framework: "global",
|
|
37159
|
-
category: "Performance"
|
|
37487
|
+
category: "Performance",
|
|
37488
|
+
requires: [...new Set(["react", ...noTransitionAll.requires ?? []])]
|
|
37160
37489
|
}
|
|
37161
37490
|
},
|
|
37162
37491
|
{
|
|
@@ -37189,7 +37518,8 @@ const reactDoctorRules = [
|
|
|
37189
37518
|
rule: {
|
|
37190
37519
|
...noUnescapedEntities,
|
|
37191
37520
|
framework: "global",
|
|
37192
|
-
category: "Bugs"
|
|
37521
|
+
category: "Bugs",
|
|
37522
|
+
requires: [...new Set(["react", ...noUnescapedEntities.requires ?? []])]
|
|
37193
37523
|
}
|
|
37194
37524
|
},
|
|
37195
37525
|
{
|
|
@@ -37200,7 +37530,8 @@ const reactDoctorRules = [
|
|
|
37200
37530
|
rule: {
|
|
37201
37531
|
...noUnknownProperty,
|
|
37202
37532
|
framework: "global",
|
|
37203
|
-
category: "Bugs"
|
|
37533
|
+
category: "Bugs",
|
|
37534
|
+
requires: [...new Set(["react", ...noUnknownProperty.requires ?? []])]
|
|
37204
37535
|
}
|
|
37205
37536
|
},
|
|
37206
37537
|
{
|
|
@@ -37211,7 +37542,8 @@ const reactDoctorRules = [
|
|
|
37211
37542
|
rule: {
|
|
37212
37543
|
...noUnsafe,
|
|
37213
37544
|
framework: "global",
|
|
37214
|
-
category: "Bugs"
|
|
37545
|
+
category: "Bugs",
|
|
37546
|
+
requires: [...new Set(["react", ...noUnsafe.requires ?? []])]
|
|
37215
37547
|
}
|
|
37216
37548
|
},
|
|
37217
37549
|
{
|
|
@@ -37222,7 +37554,8 @@ const reactDoctorRules = [
|
|
|
37222
37554
|
rule: {
|
|
37223
37555
|
...noUnstableNestedComponents,
|
|
37224
37556
|
framework: "global",
|
|
37225
|
-
category: "Performance"
|
|
37557
|
+
category: "Performance",
|
|
37558
|
+
requires: [...new Set(["react", ...noUnstableNestedComponents.requires ?? []])]
|
|
37226
37559
|
}
|
|
37227
37560
|
},
|
|
37228
37561
|
{
|
|
@@ -37233,7 +37566,8 @@ const reactDoctorRules = [
|
|
|
37233
37566
|
rule: {
|
|
37234
37567
|
...noUsememoSimpleExpression,
|
|
37235
37568
|
framework: "global",
|
|
37236
|
-
category: "Performance"
|
|
37569
|
+
category: "Performance",
|
|
37570
|
+
requires: [...new Set(["react", ...noUsememoSimpleExpression.requires ?? []])]
|
|
37237
37571
|
}
|
|
37238
37572
|
},
|
|
37239
37573
|
{
|
|
@@ -37255,7 +37589,8 @@ const reactDoctorRules = [
|
|
|
37255
37589
|
rule: {
|
|
37256
37590
|
...noWillUpdateSetState,
|
|
37257
37591
|
framework: "global",
|
|
37258
|
-
category: "Bugs"
|
|
37592
|
+
category: "Bugs",
|
|
37593
|
+
requires: [...new Set(["react", ...noWillUpdateSetState.requires ?? []])]
|
|
37259
37594
|
}
|
|
37260
37595
|
},
|
|
37261
37596
|
{
|
|
@@ -37277,7 +37612,8 @@ const reactDoctorRules = [
|
|
|
37277
37612
|
rule: {
|
|
37278
37613
|
...onlyExportComponents,
|
|
37279
37614
|
framework: "global",
|
|
37280
|
-
category: "Maintainability"
|
|
37615
|
+
category: "Maintainability",
|
|
37616
|
+
requires: [...new Set(["react", ...onlyExportComponents.requires ?? []])]
|
|
37281
37617
|
}
|
|
37282
37618
|
},
|
|
37283
37619
|
{
|
|
@@ -37354,7 +37690,8 @@ const reactDoctorRules = [
|
|
|
37354
37690
|
rule: {
|
|
37355
37691
|
...preferEs6Class,
|
|
37356
37692
|
framework: "global",
|
|
37357
|
-
category: "Maintainability"
|
|
37693
|
+
category: "Maintainability",
|
|
37694
|
+
requires: [...new Set(["react", ...preferEs6Class.requires ?? []])]
|
|
37358
37695
|
}
|
|
37359
37696
|
},
|
|
37360
37697
|
{
|
|
@@ -37376,7 +37713,8 @@ const reactDoctorRules = [
|
|
|
37376
37713
|
rule: {
|
|
37377
37714
|
...preferFunctionComponent,
|
|
37378
37715
|
framework: "global",
|
|
37379
|
-
category: "Maintainability"
|
|
37716
|
+
category: "Maintainability",
|
|
37717
|
+
requires: [...new Set(["react", ...preferFunctionComponent.requires ?? []])]
|
|
37380
37718
|
}
|
|
37381
37719
|
},
|
|
37382
37720
|
{
|
|
@@ -37387,7 +37725,8 @@ const reactDoctorRules = [
|
|
|
37387
37725
|
rule: {
|
|
37388
37726
|
...preferHtmlDialog,
|
|
37389
37727
|
framework: "global",
|
|
37390
|
-
category: "Accessibility"
|
|
37728
|
+
category: "Accessibility",
|
|
37729
|
+
requires: [...new Set(["react", ...preferHtmlDialog.requires ?? []])]
|
|
37391
37730
|
}
|
|
37392
37731
|
},
|
|
37393
37732
|
{
|
|
@@ -37420,7 +37759,8 @@ const reactDoctorRules = [
|
|
|
37420
37759
|
rule: {
|
|
37421
37760
|
...preferStableEmptyFallback,
|
|
37422
37761
|
framework: "global",
|
|
37423
|
-
category: "Performance"
|
|
37762
|
+
category: "Performance",
|
|
37763
|
+
requires: [...new Set(["react", ...preferStableEmptyFallback.requires ?? []])]
|
|
37424
37764
|
}
|
|
37425
37765
|
},
|
|
37426
37766
|
{
|
|
@@ -37431,7 +37771,8 @@ const reactDoctorRules = [
|
|
|
37431
37771
|
rule: {
|
|
37432
37772
|
...preferTagOverRole,
|
|
37433
37773
|
framework: "global",
|
|
37434
|
-
category: "Accessibility"
|
|
37774
|
+
category: "Accessibility",
|
|
37775
|
+
requires: [...new Set(["react", ...preferTagOverRole.requires ?? []])]
|
|
37435
37776
|
}
|
|
37436
37777
|
},
|
|
37437
37778
|
{
|
|
@@ -37442,7 +37783,8 @@ const reactDoctorRules = [
|
|
|
37442
37783
|
rule: {
|
|
37443
37784
|
...preferUseEffectEvent,
|
|
37444
37785
|
framework: "global",
|
|
37445
|
-
category: "Bugs"
|
|
37786
|
+
category: "Bugs",
|
|
37787
|
+
requires: [...new Set(["react", ...preferUseEffectEvent.requires ?? []])]
|
|
37446
37788
|
}
|
|
37447
37789
|
},
|
|
37448
37790
|
{
|
|
@@ -37453,7 +37795,8 @@ const reactDoctorRules = [
|
|
|
37453
37795
|
rule: {
|
|
37454
37796
|
...preferUseSyncExternalStore,
|
|
37455
37797
|
framework: "global",
|
|
37456
|
-
category: "Bugs"
|
|
37798
|
+
category: "Bugs",
|
|
37799
|
+
requires: [...new Set(["react", ...preferUseSyncExternalStore.requires ?? []])]
|
|
37457
37800
|
}
|
|
37458
37801
|
},
|
|
37459
37802
|
{
|
|
@@ -37464,7 +37807,8 @@ const reactDoctorRules = [
|
|
|
37464
37807
|
rule: {
|
|
37465
37808
|
...preferUseReducer,
|
|
37466
37809
|
framework: "global",
|
|
37467
|
-
category: "Bugs"
|
|
37810
|
+
category: "Bugs",
|
|
37811
|
+
requires: [...new Set(["react", ...preferUseReducer.requires ?? []])]
|
|
37468
37812
|
}
|
|
37469
37813
|
},
|
|
37470
37814
|
{
|
|
@@ -37563,7 +37907,8 @@ const reactDoctorRules = [
|
|
|
37563
37907
|
rule: {
|
|
37564
37908
|
...reactInJsxScope,
|
|
37565
37909
|
framework: "global",
|
|
37566
|
-
category: "Bugs"
|
|
37910
|
+
category: "Bugs",
|
|
37911
|
+
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
37567
37912
|
}
|
|
37568
37913
|
},
|
|
37569
37914
|
{
|
|
@@ -37574,7 +37919,8 @@ const reactDoctorRules = [
|
|
|
37574
37919
|
rule: {
|
|
37575
37920
|
...reduxUseselectorInlineDerivation,
|
|
37576
37921
|
framework: "global",
|
|
37577
|
-
category: "Performance"
|
|
37922
|
+
category: "Performance",
|
|
37923
|
+
requires: [...new Set(["react", ...reduxUseselectorInlineDerivation.requires ?? []])]
|
|
37578
37924
|
}
|
|
37579
37925
|
},
|
|
37580
37926
|
{
|
|
@@ -37585,7 +37931,8 @@ const reactDoctorRules = [
|
|
|
37585
37931
|
rule: {
|
|
37586
37932
|
...reduxUseselectorReturnsNewCollection,
|
|
37587
37933
|
framework: "global",
|
|
37588
|
-
category: "Performance"
|
|
37934
|
+
category: "Performance",
|
|
37935
|
+
requires: [...new Set(["react", ...reduxUseselectorReturnsNewCollection.requires ?? []])]
|
|
37589
37936
|
}
|
|
37590
37937
|
},
|
|
37591
37938
|
{
|
|
@@ -37596,7 +37943,8 @@ const reactDoctorRules = [
|
|
|
37596
37943
|
rule: {
|
|
37597
37944
|
...renderingAnimateSvgWrapper,
|
|
37598
37945
|
framework: "global",
|
|
37599
|
-
category: "Performance"
|
|
37946
|
+
category: "Performance",
|
|
37947
|
+
requires: [...new Set(["react", ...renderingAnimateSvgWrapper.requires ?? []])]
|
|
37600
37948
|
}
|
|
37601
37949
|
},
|
|
37602
37950
|
{
|
|
@@ -37618,7 +37966,8 @@ const reactDoctorRules = [
|
|
|
37618
37966
|
rule: {
|
|
37619
37967
|
...renderingHoistJsx,
|
|
37620
37968
|
framework: "global",
|
|
37621
|
-
category: "Performance"
|
|
37969
|
+
category: "Performance",
|
|
37970
|
+
requires: [...new Set(["react", ...renderingHoistJsx.requires ?? []])]
|
|
37622
37971
|
}
|
|
37623
37972
|
},
|
|
37624
37973
|
{
|
|
@@ -37629,7 +37978,8 @@ const reactDoctorRules = [
|
|
|
37629
37978
|
rule: {
|
|
37630
37979
|
...renderingHydrationMismatchTime,
|
|
37631
37980
|
framework: "global",
|
|
37632
|
-
category: "Bugs"
|
|
37981
|
+
category: "Bugs",
|
|
37982
|
+
requires: [...new Set(["react", ...renderingHydrationMismatchTime.requires ?? []])]
|
|
37633
37983
|
}
|
|
37634
37984
|
},
|
|
37635
37985
|
{
|
|
@@ -37640,7 +37990,8 @@ const reactDoctorRules = [
|
|
|
37640
37990
|
rule: {
|
|
37641
37991
|
...renderingHydrationNoFlicker,
|
|
37642
37992
|
framework: "global",
|
|
37643
|
-
category: "Performance"
|
|
37993
|
+
category: "Performance",
|
|
37994
|
+
requires: [...new Set(["react", ...renderingHydrationNoFlicker.requires ?? []])]
|
|
37644
37995
|
}
|
|
37645
37996
|
},
|
|
37646
37997
|
{
|
|
@@ -37651,7 +38002,8 @@ const reactDoctorRules = [
|
|
|
37651
38002
|
rule: {
|
|
37652
38003
|
...renderingScriptDeferAsync,
|
|
37653
38004
|
framework: "global",
|
|
37654
|
-
category: "Performance"
|
|
38005
|
+
category: "Performance",
|
|
38006
|
+
requires: [...new Set(["react", ...renderingScriptDeferAsync.requires ?? []])]
|
|
37655
38007
|
}
|
|
37656
38008
|
},
|
|
37657
38009
|
{
|
|
@@ -37673,7 +38025,8 @@ const reactDoctorRules = [
|
|
|
37673
38025
|
rule: {
|
|
37674
38026
|
...renderingUsetransitionLoading,
|
|
37675
38027
|
framework: "global",
|
|
37676
|
-
category: "Performance"
|
|
38028
|
+
category: "Performance",
|
|
38029
|
+
requires: [...new Set(["react", ...renderingUsetransitionLoading.requires ?? []])]
|
|
37677
38030
|
}
|
|
37678
38031
|
},
|
|
37679
38032
|
{
|
|
@@ -37684,7 +38037,8 @@ const reactDoctorRules = [
|
|
|
37684
38037
|
rule: {
|
|
37685
38038
|
...requireRenderReturn,
|
|
37686
38039
|
framework: "global",
|
|
37687
|
-
category: "Bugs"
|
|
38040
|
+
category: "Bugs",
|
|
38041
|
+
requires: [...new Set(["react", ...requireRenderReturn.requires ?? []])]
|
|
37688
38042
|
}
|
|
37689
38043
|
},
|
|
37690
38044
|
{
|
|
@@ -37695,7 +38049,8 @@ const reactDoctorRules = [
|
|
|
37695
38049
|
rule: {
|
|
37696
38050
|
...rerenderDeferReadsHook,
|
|
37697
38051
|
framework: "global",
|
|
37698
|
-
category: "Performance"
|
|
38052
|
+
category: "Performance",
|
|
38053
|
+
requires: [...new Set(["react", ...rerenderDeferReadsHook.requires ?? []])]
|
|
37699
38054
|
}
|
|
37700
38055
|
},
|
|
37701
38056
|
{
|
|
@@ -37706,7 +38061,8 @@ const reactDoctorRules = [
|
|
|
37706
38061
|
rule: {
|
|
37707
38062
|
...rerenderDependencies,
|
|
37708
38063
|
framework: "global",
|
|
37709
|
-
category: "Bugs"
|
|
38064
|
+
category: "Bugs",
|
|
38065
|
+
requires: [...new Set(["react", ...rerenderDependencies.requires ?? []])]
|
|
37710
38066
|
}
|
|
37711
38067
|
},
|
|
37712
38068
|
{
|
|
@@ -37717,7 +38073,8 @@ const reactDoctorRules = [
|
|
|
37717
38073
|
rule: {
|
|
37718
38074
|
...rerenderDerivedStateFromHook,
|
|
37719
38075
|
framework: "global",
|
|
37720
|
-
category: "Performance"
|
|
38076
|
+
category: "Performance",
|
|
38077
|
+
requires: [...new Set(["react", ...rerenderDerivedStateFromHook.requires ?? []])]
|
|
37721
38078
|
}
|
|
37722
38079
|
},
|
|
37723
38080
|
{
|
|
@@ -37728,7 +38085,8 @@ const reactDoctorRules = [
|
|
|
37728
38085
|
rule: {
|
|
37729
38086
|
...rerenderFunctionalSetstate,
|
|
37730
38087
|
framework: "global",
|
|
37731
|
-
category: "Performance"
|
|
38088
|
+
category: "Performance",
|
|
38089
|
+
requires: [...new Set(["react", ...rerenderFunctionalSetstate.requires ?? []])]
|
|
37732
38090
|
}
|
|
37733
38091
|
},
|
|
37734
38092
|
{
|
|
@@ -37739,7 +38097,8 @@ const reactDoctorRules = [
|
|
|
37739
38097
|
rule: {
|
|
37740
38098
|
...rerenderLazyRefInit,
|
|
37741
38099
|
framework: "global",
|
|
37742
|
-
category: "Performance"
|
|
38100
|
+
category: "Performance",
|
|
38101
|
+
requires: [...new Set(["react", ...rerenderLazyRefInit.requires ?? []])]
|
|
37743
38102
|
}
|
|
37744
38103
|
},
|
|
37745
38104
|
{
|
|
@@ -37750,7 +38109,8 @@ const reactDoctorRules = [
|
|
|
37750
38109
|
rule: {
|
|
37751
38110
|
...rerenderLazyStateInit,
|
|
37752
38111
|
framework: "global",
|
|
37753
|
-
category: "Performance"
|
|
38112
|
+
category: "Performance",
|
|
38113
|
+
requires: [...new Set(["react", ...rerenderLazyStateInit.requires ?? []])]
|
|
37754
38114
|
}
|
|
37755
38115
|
},
|
|
37756
38116
|
{
|
|
@@ -37761,7 +38121,8 @@ const reactDoctorRules = [
|
|
|
37761
38121
|
rule: {
|
|
37762
38122
|
...rerenderMemoBeforeEarlyReturn,
|
|
37763
38123
|
framework: "global",
|
|
37764
|
-
category: "Performance"
|
|
38124
|
+
category: "Performance",
|
|
38125
|
+
requires: [...new Set(["react", ...rerenderMemoBeforeEarlyReturn.requires ?? []])]
|
|
37765
38126
|
}
|
|
37766
38127
|
},
|
|
37767
38128
|
{
|
|
@@ -37772,7 +38133,8 @@ const reactDoctorRules = [
|
|
|
37772
38133
|
rule: {
|
|
37773
38134
|
...rerenderMemoWithDefaultValue,
|
|
37774
38135
|
framework: "global",
|
|
37775
|
-
category: "Performance"
|
|
38136
|
+
category: "Performance",
|
|
38137
|
+
requires: [...new Set(["react", ...rerenderMemoWithDefaultValue.requires ?? []])]
|
|
37776
38138
|
}
|
|
37777
38139
|
},
|
|
37778
38140
|
{
|
|
@@ -37783,7 +38145,8 @@ const reactDoctorRules = [
|
|
|
37783
38145
|
rule: {
|
|
37784
38146
|
...rerenderStateOnlyInHandlers,
|
|
37785
38147
|
framework: "global",
|
|
37786
|
-
category: "Performance"
|
|
38148
|
+
category: "Performance",
|
|
38149
|
+
requires: [...new Set(["react", ...rerenderStateOnlyInHandlers.requires ?? []])]
|
|
37787
38150
|
}
|
|
37788
38151
|
},
|
|
37789
38152
|
{
|
|
@@ -37794,7 +38157,8 @@ const reactDoctorRules = [
|
|
|
37794
38157
|
rule: {
|
|
37795
38158
|
...rerenderTransitionsScroll,
|
|
37796
38159
|
framework: "global",
|
|
37797
|
-
category: "Performance"
|
|
38160
|
+
category: "Performance",
|
|
38161
|
+
requires: [...new Set(["react", ...rerenderTransitionsScroll.requires ?? []])]
|
|
37798
38162
|
}
|
|
37799
38163
|
},
|
|
37800
38164
|
{
|
|
@@ -38213,7 +38577,8 @@ const reactDoctorRules = [
|
|
|
38213
38577
|
rule: {
|
|
38214
38578
|
...roleHasRequiredAriaProps,
|
|
38215
38579
|
framework: "global",
|
|
38216
|
-
category: "Accessibility"
|
|
38580
|
+
category: "Accessibility",
|
|
38581
|
+
requires: [...new Set(["react", ...roleHasRequiredAriaProps.requires ?? []])]
|
|
38217
38582
|
}
|
|
38218
38583
|
},
|
|
38219
38584
|
{
|
|
@@ -38224,7 +38589,8 @@ const reactDoctorRules = [
|
|
|
38224
38589
|
rule: {
|
|
38225
38590
|
...roleSupportsAriaProps,
|
|
38226
38591
|
framework: "global",
|
|
38227
|
-
category: "Accessibility"
|
|
38592
|
+
category: "Accessibility",
|
|
38593
|
+
requires: [...new Set(["react", ...roleSupportsAriaProps.requires ?? []])]
|
|
38228
38594
|
}
|
|
38229
38595
|
},
|
|
38230
38596
|
{
|
|
@@ -38235,7 +38601,8 @@ const reactDoctorRules = [
|
|
|
38235
38601
|
rule: {
|
|
38236
38602
|
...rulesOfHooks,
|
|
38237
38603
|
framework: "global",
|
|
38238
|
-
category: "Bugs"
|
|
38604
|
+
category: "Bugs",
|
|
38605
|
+
requires: [...new Set(["react", ...rulesOfHooks.requires ?? []])]
|
|
38239
38606
|
}
|
|
38240
38607
|
},
|
|
38241
38608
|
{
|
|
@@ -38246,7 +38613,8 @@ const reactDoctorRules = [
|
|
|
38246
38613
|
rule: {
|
|
38247
38614
|
...scope,
|
|
38248
38615
|
framework: "global",
|
|
38249
|
-
category: "Accessibility"
|
|
38616
|
+
category: "Accessibility",
|
|
38617
|
+
requires: [...new Set(["react", ...scope.requires ?? []])]
|
|
38250
38618
|
}
|
|
38251
38619
|
},
|
|
38252
38620
|
{
|
|
@@ -38257,7 +38625,8 @@ const reactDoctorRules = [
|
|
|
38257
38625
|
rule: {
|
|
38258
38626
|
...selfClosingComp,
|
|
38259
38627
|
framework: "global",
|
|
38260
|
-
category: "Maintainability"
|
|
38628
|
+
category: "Maintainability",
|
|
38629
|
+
requires: [...new Set(["react", ...selfClosingComp.requires ?? []])]
|
|
38261
38630
|
}
|
|
38262
38631
|
},
|
|
38263
38632
|
{
|
|
@@ -38364,7 +38733,8 @@ const reactDoctorRules = [
|
|
|
38364
38733
|
rule: {
|
|
38365
38734
|
...stateInConstructor,
|
|
38366
38735
|
framework: "global",
|
|
38367
|
-
category: "Maintainability"
|
|
38736
|
+
category: "Maintainability",
|
|
38737
|
+
requires: [...new Set(["react", ...stateInConstructor.requires ?? []])]
|
|
38368
38738
|
}
|
|
38369
38739
|
},
|
|
38370
38740
|
{
|
|
@@ -38375,7 +38745,8 @@ const reactDoctorRules = [
|
|
|
38375
38745
|
rule: {
|
|
38376
38746
|
...stylePropObject,
|
|
38377
38747
|
framework: "global",
|
|
38378
|
-
category: "Bugs"
|
|
38748
|
+
category: "Bugs",
|
|
38749
|
+
requires: [...new Set(["react", ...stylePropObject.requires ?? []])]
|
|
38379
38750
|
}
|
|
38380
38751
|
},
|
|
38381
38752
|
{
|
|
@@ -38386,7 +38757,8 @@ const reactDoctorRules = [
|
|
|
38386
38757
|
rule: {
|
|
38387
38758
|
...tabindexNoPositive,
|
|
38388
38759
|
framework: "global",
|
|
38389
|
-
category: "Accessibility"
|
|
38760
|
+
category: "Accessibility",
|
|
38761
|
+
requires: [...new Set(["react", ...tabindexNoPositive.requires ?? []])]
|
|
38390
38762
|
}
|
|
38391
38763
|
},
|
|
38392
38764
|
{
|
|
@@ -38562,7 +38934,8 @@ const reactDoctorRules = [
|
|
|
38562
38934
|
rule: {
|
|
38563
38935
|
...voidDomElementsNoChildren,
|
|
38564
38936
|
framework: "global",
|
|
38565
|
-
category: "Bugs"
|
|
38937
|
+
category: "Bugs",
|
|
38938
|
+
requires: [...new Set(["react", ...voidDomElementsNoChildren.requires ?? []])]
|
|
38566
38939
|
}
|
|
38567
38940
|
},
|
|
38568
38941
|
{
|