oxlint-plugin-react-doctor 0.4.2-dev.6bfc03a → 0.4.2-dev.772385f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -823,6 +823,310 @@ 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
826
1130
  //#region src/plugin/utils/is-nextjs-metadata-image-route-filename.ts
827
1131
  const isNextjsMetadataImageRouteFilename = (rawFilename) => {
828
1132
  if (!rawFilename) return false;
@@ -832,6 +1136,192 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
832
1136
  //#region src/plugin/utils/normalize-filename.ts
833
1137
  const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
834
1138
  //#endregion
1139
+ //#region src/plugin/utils/strip-paren-expression.ts
1140
+ const TS_WRAPPER_TYPES = new Set([
1141
+ "ParenthesizedExpression",
1142
+ "TSAsExpression",
1143
+ "TSSatisfiesExpression",
1144
+ "TSTypeAssertion",
1145
+ "TSNonNullExpression",
1146
+ "TSInstantiationExpression"
1147
+ ]);
1148
+ const stripParenExpression = (node) => {
1149
+ let current = node;
1150
+ while (true) {
1151
+ if (TS_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) {
1152
+ current = current.expression;
1153
+ continue;
1154
+ }
1155
+ if (isNodeOfType(current, "ChainExpression") && current.expression) {
1156
+ current = current.expression;
1157
+ continue;
1158
+ }
1159
+ break;
1160
+ }
1161
+ return current;
1162
+ };
1163
+ //#endregion
1164
+ //#region src/plugin/utils/is-generated-image-render-context.ts
1165
+ const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
1166
+ const SATORI_MODULE = "satori";
1167
+ const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
1168
+ const isGeneratedImageRenderFilename = (rawFilename) => {
1169
+ if (!rawFilename) return false;
1170
+ return isNextjsMetadataImageRouteFilename(normalizeFilename$1(rawFilename));
1171
+ };
1172
+ const isImageResponseCallee = (contextNode, callee) => {
1173
+ if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
1174
+ if (!isMemberProperty(callee, "ImageResponse")) return false;
1175
+ if (!isNodeOfType(callee.object, "Identifier")) return false;
1176
+ const namespaceIdentifierName = callee.object.name;
1177
+ return IMAGE_RESPONSE_MODULES.some((moduleSource) => isNamespaceImportFromModule(contextNode, namespaceIdentifierName, moduleSource));
1178
+ };
1179
+ const isSatoriCallee = (contextNode, callee) => {
1180
+ if (!isNodeOfType(callee, "Identifier")) return false;
1181
+ if (getImportedNameFromModule(contextNode, callee.name, SATORI_MODULE) === "satori") return true;
1182
+ return isDefaultImportFromModule(contextNode, callee.name, SATORI_MODULE);
1183
+ };
1184
+ const isGeneratedImageRendererCall = (node) => {
1185
+ if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return false;
1186
+ if (!isNodeOfType(node.callee, "Identifier") && !isNodeOfType(node.callee, "MemberExpression")) return false;
1187
+ return isImageResponseCallee(node, node.callee) || isSatoriCallee(node, node.callee);
1188
+ };
1189
+ const isComponentIdentifierName = (name) => {
1190
+ const firstCharacter = name[0];
1191
+ return Boolean(firstCharacter && firstCharacter === firstCharacter.toUpperCase());
1192
+ };
1193
+ const isFunctionLike$3 = (node) => Boolean(node && (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")));
1194
+ const markFunctionReturnJsx = (functionNode, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
1195
+ if (!isFunctionLike$3(functionNode)) return;
1196
+ if (isNodeOfType(functionNode, "ArrowFunctionExpression")) {
1197
+ const body = stripParenExpression(functionNode.body);
1198
+ if (!isNodeOfType(body, "BlockStatement")) {
1199
+ markGeneratedImageExpression(body, programRoot, generatedImageJsxNodes, visitedComponentNames);
1200
+ return;
1201
+ }
1202
+ }
1203
+ const body = functionNode.body;
1204
+ if (!isNodeOfType(body, "BlockStatement")) return;
1205
+ walkAst(body, (descendantNode) => {
1206
+ if (descendantNode !== body && isFunctionLike$3(descendantNode)) return false;
1207
+ if (!isNodeOfType(descendantNode, "ReturnStatement")) return;
1208
+ if (!descendantNode.argument) return;
1209
+ markGeneratedImageExpression(stripParenExpression(descendantNode.argument), programRoot, generatedImageJsxNodes, visitedComponentNames);
1210
+ });
1211
+ };
1212
+ const hasNormalJsxUsage = (programRoot, componentName, generatedImageJsxNodes) => {
1213
+ let hasNormalUsage = false;
1214
+ walkAst(programRoot, (descendantNode) => {
1215
+ if (hasNormalUsage) return false;
1216
+ if (!isNodeOfType(descendantNode, "JSXOpeningElement")) return;
1217
+ if (generatedImageJsxNodes.has(descendantNode)) return;
1218
+ if (flattenJsxName$1(descendantNode.name) !== componentName) return;
1219
+ hasNormalUsage = true;
1220
+ return false;
1221
+ });
1222
+ return hasNormalUsage;
1223
+ };
1224
+ const markComponentRenderJsx = (programRoot, openingElement, generatedImageJsxNodes, visitedComponentNames) => {
1225
+ const tagName = flattenJsxName$1(openingElement.name);
1226
+ if (!tagName || tagName.includes(".") || !isComponentIdentifierName(tagName)) return;
1227
+ if (visitedComponentNames.has(tagName)) return;
1228
+ if (hasNormalJsxUsage(programRoot, tagName, generatedImageJsxNodes)) return;
1229
+ const binding = findVariableInitializer(openingElement, tagName);
1230
+ if (!binding?.initializer) return;
1231
+ visitedComponentNames.add(tagName);
1232
+ markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
1233
+ };
1234
+ const isInsideGeneratedImageRendererArgument = (node) => {
1235
+ let cursor = node.parent;
1236
+ while (cursor) {
1237
+ if (isGeneratedImageRendererCall(cursor)) return true;
1238
+ cursor = cursor.parent ?? null;
1239
+ }
1240
+ return false;
1241
+ };
1242
+ const hasNormalFunctionCallUsage = (programRoot, functionName) => {
1243
+ let hasNormalUsage = false;
1244
+ walkAst(programRoot, (descendantNode) => {
1245
+ if (hasNormalUsage) return false;
1246
+ if (!isNodeOfType(descendantNode, "CallExpression")) return;
1247
+ if (!isNodeOfType(descendantNode.callee, "Identifier")) return;
1248
+ if (descendantNode.callee.name !== functionName) return;
1249
+ if (isInsideGeneratedImageRendererArgument(descendantNode)) return;
1250
+ hasNormalUsage = true;
1251
+ return false;
1252
+ });
1253
+ return hasNormalUsage;
1254
+ };
1255
+ const markJsxSubtree = (node, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
1256
+ walkAst(node, (descendantNode) => {
1257
+ if (!isNodeOfType(descendantNode, "JSXOpeningElement")) return;
1258
+ generatedImageJsxNodes.add(descendantNode);
1259
+ markComponentRenderJsx(programRoot, descendantNode, generatedImageJsxNodes, visitedComponentNames);
1260
+ });
1261
+ };
1262
+ const markGeneratedImageExpression = (expression, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
1263
+ const unwrappedExpression = stripParenExpression(expression);
1264
+ if (isNodeOfType(unwrappedExpression, "JSXElement") || isNodeOfType(unwrappedExpression, "JSXFragment")) {
1265
+ markJsxSubtree(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
1266
+ return;
1267
+ }
1268
+ if (isFunctionLike$3(unwrappedExpression)) {
1269
+ markFunctionReturnJsx(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
1270
+ return;
1271
+ }
1272
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
1273
+ markGeneratedImageExpression(unwrappedExpression.consequent, programRoot, generatedImageJsxNodes, visitedComponentNames);
1274
+ markGeneratedImageExpression(unwrappedExpression.alternate, programRoot, generatedImageJsxNodes, visitedComponentNames);
1275
+ return;
1276
+ }
1277
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
1278
+ markGeneratedImageExpression(unwrappedExpression.left, programRoot, generatedImageJsxNodes, visitedComponentNames);
1279
+ markGeneratedImageExpression(unwrappedExpression.right, programRoot, generatedImageJsxNodes, visitedComponentNames);
1280
+ return;
1281
+ }
1282
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
1283
+ const callee = unwrappedExpression.callee;
1284
+ if (isFunctionLike$3(callee)) {
1285
+ markFunctionReturnJsx(callee, programRoot, generatedImageJsxNodes, visitedComponentNames);
1286
+ return;
1287
+ }
1288
+ if (!isNodeOfType(callee, "Identifier")) return;
1289
+ if (visitedComponentNames.has(callee.name)) return;
1290
+ if (hasNormalJsxUsage(programRoot, callee.name, generatedImageJsxNodes)) return;
1291
+ if (hasNormalFunctionCallUsage(programRoot, callee.name)) return;
1292
+ const binding = findVariableInitializer(callee, callee.name);
1293
+ if (!binding?.initializer || !isFunctionLike$3(stripParenExpression(binding.initializer))) return;
1294
+ visitedComponentNames.add(callee.name);
1295
+ markFunctionReturnJsx(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
1296
+ return;
1297
+ }
1298
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
1299
+ if (visitedComponentNames.has(unwrappedExpression.name)) return;
1300
+ visitedComponentNames.add(unwrappedExpression.name);
1301
+ const binding = findVariableInitializer(unwrappedExpression, unwrappedExpression.name);
1302
+ if (!binding?.initializer) return;
1303
+ markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
1304
+ }
1305
+ };
1306
+ const collectGeneratedImageJsxNodes = (programRoot) => {
1307
+ const cached = generatedImageJsxCache.get(programRoot);
1308
+ if (cached) return cached;
1309
+ const generatedImageJsxNodes = /* @__PURE__ */ new WeakSet();
1310
+ walkAst(programRoot, (descendantNode) => {
1311
+ if (!isGeneratedImageRendererCall(descendantNode)) return;
1312
+ for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, generatedImageJsxNodes, /* @__PURE__ */ new Set());
1313
+ });
1314
+ generatedImageJsxCache.set(programRoot, generatedImageJsxNodes);
1315
+ return generatedImageJsxNodes;
1316
+ };
1317
+ const isGeneratedImageRenderContext = (context, node) => {
1318
+ if (isGeneratedImageRenderFilename(context.filename)) return true;
1319
+ if (!node) return false;
1320
+ const programRoot = findProgramRoot(node);
1321
+ if (!programRoot) return false;
1322
+ return collectGeneratedImageJsxNodes(programRoot).has(node);
1323
+ };
1324
+ //#endregion
835
1325
  //#region src/plugin/utils/is-hidden-from-screen-reader.ts
836
1326
  const isHiddenFromScreenReader = (openingElement, settings) => {
837
1327
  if (getElementType(openingElement, settings).toLowerCase() === "input") {
@@ -887,7 +1377,7 @@ const PREFER_ALT = "Screen readers skip a decorative image more reliably with `a
887
1377
  const MESSAGE_OBJECT = "Blind users can't use this `<object>` because screen readers can't describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
888
1378
  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
1379
  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
- const resolveSettings$53 = (settings) => {
1380
+ const resolveSettings$52 = (settings) => {
891
1381
  const reactDoctor = settings?.["react-doctor"];
892
1382
  return typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.altText ?? {} : {};
893
1383
  };
@@ -1003,8 +1493,8 @@ const altText = defineRule({
1003
1493
  recommendation: "Give every meaningful image an `alt`, `aria-label`, or `aria-labelledby`.",
1004
1494
  category: "Accessibility",
1005
1495
  create: (context) => {
1006
- if (isNextjsMetadataImageRouteFilename(normalizeFilename$1(context.filename ?? ""))) return {};
1007
- const settings = resolveSettings$53(context.settings);
1496
+ if (isGeneratedImageRenderContext(context)) return {};
1497
+ const settings = resolveSettings$52(context.settings);
1008
1498
  const checkImg = !settings.elements || settings.elements.includes("img");
1009
1499
  const checkObject = !settings.elements || settings.elements.includes("object");
1010
1500
  const checkArea = !settings.elements || settings.elements.includes("area");
@@ -1014,6 +1504,7 @@ const altText = defineRule({
1014
1504
  const areaAliases = new Set(settings.area ?? []);
1015
1505
  const inputImageAliases = new Set(settings["input[type=\"image\"]"] ?? []);
1016
1506
  return { JSXOpeningElement(node) {
1507
+ if (isGeneratedImageRenderContext(context, node)) return;
1017
1508
  const tag = getElementType(node, context.settings);
1018
1509
  if (checkImg && (tag === "img" || imgAliases.has(tag))) {
1019
1510
  imgRule(node, node, context);
@@ -1048,7 +1539,7 @@ const DEFAULT_AMBIGUOUS = [
1048
1539
  "a link",
1049
1540
  "learn more"
1050
1541
  ];
1051
- const resolveSettings$52 = (settings) => {
1542
+ const resolveSettings$51 = (settings) => {
1052
1543
  const reactDoctor = settings?.["react-doctor"];
1053
1544
  return { words: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorAmbiguousText ?? {} : {}).words ?? DEFAULT_AMBIGUOUS };
1054
1545
  };
@@ -1089,7 +1580,7 @@ const anchorAmbiguousText = defineRule({
1089
1580
  recommendation: "Name where a link goes. Avoid 'click here', 'learn more', and 'link'.",
1090
1581
  category: "Accessibility",
1091
1582
  create: (context) => {
1092
- const settings = resolveSettings$52(context.settings);
1583
+ const settings = resolveSettings$51(context.settings);
1093
1584
  const ambiguousSet = new Set(settings.words.map((word) => word.toLowerCase()));
1094
1585
  return { JSXElement(node) {
1095
1586
  if (getElementType(node.openingElement, context.settings) !== "a") return;
@@ -1139,7 +1630,7 @@ const getStaticTemplateLiteralValue = (templateLiteral) => {
1139
1630
  const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
1140
1631
  const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
1141
1632
  const MESSAGE_CANT_BE_ANCHOR = "Keyboard users can't trigger this link because it's a click handler with no real `href`, so use `<button>` instead.";
1142
- const resolveSettings$51 = (settings) => {
1633
+ const resolveSettings$50 = (settings) => {
1143
1634
  const reactDoctor = settings?.["react-doctor"];
1144
1635
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorIsValid ?? {} : {};
1145
1636
  const jsxA11y = settings?.["jsx-a11y"];
@@ -1178,7 +1669,7 @@ const anchorIsValid = defineRule({
1178
1669
  recommendation: "Give links a real destination. Use `<button>` for in-page actions.",
1179
1670
  category: "Accessibility",
1180
1671
  create: (context) => {
1181
- const settings = resolveSettings$51(context.settings);
1672
+ const settings = resolveSettings$50(context.settings);
1182
1673
  return { JSXOpeningElement(node) {
1183
1674
  if (getElementType(node, context.settings) !== "a") return;
1184
1675
  let hrefAttribute;
@@ -2213,7 +2704,7 @@ const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
2213
2704
  //#endregion
2214
2705
  //#region src/plugin/rules/a11y/aria-role.ts
2215
2706
  const buildBaseMessage = (suffix) => `Screen reader users get no help from this \`role\` because it isn't a valid ARIA role, so use a real, non-abstract role.${suffix}`;
2216
- const resolveSettings$50 = (settings) => {
2707
+ const resolveSettings$49 = (settings) => {
2217
2708
  const reactDoctor = settings?.["react-doctor"];
2218
2709
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
2219
2710
  return {
@@ -2229,7 +2720,7 @@ const ariaRole = defineRule({
2229
2720
  recommendation: "Use a real, non-abstract ARIA role.",
2230
2721
  category: "Accessibility",
2231
2722
  create: (context) => {
2232
- const settings = resolveSettings$50(context.settings);
2723
+ const settings = resolveSettings$49(context.settings);
2233
2724
  return { JSXOpeningElement(node) {
2234
2725
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
2235
2726
  if (!roleAttribute) return;
@@ -2330,7 +2821,7 @@ const LOOP_TYPES = [
2330
2821
  "WhileStatement",
2331
2822
  "DoWhileStatement"
2332
2823
  ];
2333
- const FUNCTION_LIKE_TYPES$1 = new Set([
2824
+ const FUNCTION_LIKE_TYPES = new Set([
2334
2825
  "FunctionDeclaration",
2335
2826
  "FunctionExpression",
2336
2827
  "ArrowFunctionExpression"
@@ -3255,7 +3746,7 @@ const FORM_CONTROL_TAGS = new Set([
3255
3746
  "select",
3256
3747
  "form"
3257
3748
  ]);
3258
- const resolveSettings$49 = (settings) => {
3749
+ const resolveSettings$48 = (settings) => {
3259
3750
  const reactDoctor = settings?.["react-doctor"];
3260
3751
  return { inputComponents: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {}).inputComponents ?? [] };
3261
3752
  };
@@ -3267,7 +3758,7 @@ const autocompleteValid = defineRule({
3267
3758
  recommendation: "Use a valid autofill token in `autoComplete`.",
3268
3759
  category: "Accessibility",
3269
3760
  create: (context) => {
3270
- const settings = resolveSettings$49(context.settings);
3761
+ const settings = resolveSettings$48(context.settings);
3271
3762
  return { JSXOpeningElement: (node) => {
3272
3763
  const tag = getElementType(node, context.settings);
3273
3764
  if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.includes(tag)) return;
@@ -3320,7 +3811,7 @@ const isCreateElementCall = (node) => {
3320
3811
  //#region src/plugin/rules/react-builtins/button-has-type.ts
3321
3812
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
3322
3813
  const INVALID_MESSAGE = "This button's `type` is invalid.";
3323
- const resolveSettings$48 = (settings) => {
3814
+ const resolveSettings$47 = (settings) => {
3324
3815
  const reactDoctor = settings?.["react-doctor"];
3325
3816
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
3326
3817
  return {
@@ -3362,7 +3853,7 @@ const buttonHasType = defineRule({
3362
3853
  severity: "warn",
3363
3854
  recommendation: "Always set a `type` on a `<button>`: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
3364
3855
  create: (context) => {
3365
- const settings = resolveSettings$48(context.settings);
3856
+ const settings = resolveSettings$47(context.settings);
3366
3857
  const isTestlikeFile = isTestlikeFilename(context.filename);
3367
3858
  return {
3368
3859
  JSXOpeningElement(node) {
@@ -3431,7 +3922,7 @@ const buttonHasType = defineRule({
3431
3922
  //#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
3432
3923
  const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
3433
3924
  const EXCLUSIVE_MESSAGE = "This input behaves unpredictably with both `checked` & `defaultChecked` set.";
3434
- const resolveSettings$47 = (settings) => {
3925
+ const resolveSettings$46 = (settings) => {
3435
3926
  const reactDoctor = settings?.["react-doctor"];
3436
3927
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
3437
3928
  return {
@@ -3484,7 +3975,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
3484
3975
  recommendation: "Add `onChange` (controlled) or `readOnly` (display-only), or use `defaultChecked` for an uncontrolled checkbox.",
3485
3976
  category: "Correctness",
3486
3977
  create: (context) => {
3487
- const settings = resolveSettings$47(context.settings);
3978
+ const settings = resolveSettings$46(context.settings);
3488
3979
  const reportFromPresence = (presence) => {
3489
3980
  if (presence.checkedNode && presence.defaultCheckedNode && !settings.ignoreExclusiveCheckedAttribute) context.report({
3490
3981
  node: presence.checkedNode,
@@ -3628,9 +4119,6 @@ const clientLocalstorageNoVersion = defineRule({
3628
4119
  } })
3629
4120
  });
3630
4121
  //#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
4122
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
3635
4123
  const clientPassiveEventListeners = defineRule({
3636
4124
  id: "client-passive-event-listeners",
@@ -3670,31 +4158,6 @@ const isReactComponentName = (name) => {
3670
4158
  return firstCharacter >= 65 && firstCharacter <= 90;
3671
4159
  };
3672
4160
  //#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
4161
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
3699
4162
  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
4163
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
@@ -3708,7 +4171,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
3708
4171
  const LABEL_ELEMENT = "label";
3709
4172
  const DEFAULT_DEPTH = 5;
3710
4173
  const MAX_DEPTH = 25;
3711
- const resolveSettings$46 = (settings) => {
4174
+ const resolveSettings$45 = (settings) => {
3712
4175
  const reactDoctor = settings?.["react-doctor"];
3713
4176
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
3714
4177
  return {
@@ -3828,7 +4291,7 @@ const controlHasAssociatedLabel = defineRule({
3828
4291
  recommendation: "Give every interactive control a label screen readers can read.",
3829
4292
  category: "Accessibility",
3830
4293
  create: (context) => {
3831
- const settings = resolveSettings$46(context.settings);
4294
+ const settings = resolveSettings$45(context.settings);
3832
4295
  const isTestlikeFile = isTestlikeFilename(context.filename);
3833
4296
  return { JSXElement(node) {
3834
4297
  if (isTestlikeFile) return;
@@ -4199,7 +4662,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
4199
4662
  "lazy",
4200
4663
  "withTracking"
4201
4664
  ];
4202
- const resolveSettings$45 = (settings) => {
4665
+ const resolveSettings$44 = (settings) => {
4203
4666
  const reactDoctor = settings?.["react-doctor"];
4204
4667
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
4205
4668
  const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
@@ -4391,7 +4854,7 @@ const displayName = defineRule({
4391
4854
  recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
4392
4855
  category: "Architecture",
4393
4856
  create: (context) => {
4394
- const settings = resolveSettings$45(context.settings);
4857
+ const settings = resolveSettings$44(context.settings);
4395
4858
  const ignoreNamed = settings.ignoreTranspilerName ? false : true;
4396
4859
  const reportAt = (node) => {
4397
4860
  context.report({
@@ -6263,7 +6726,7 @@ const compileGlob = (pattern) => {
6263
6726
  //#endregion
6264
6727
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
6265
6728
  const DEFAULT_FORBID_PROPS = ["className", "style"];
6266
- const resolveSettings$44 = (settings) => {
6729
+ const resolveSettings$43 = (settings) => {
6267
6730
  const reactDoctor = settings?.["react-doctor"];
6268
6731
  const forbid = (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid;
6269
6732
  if (!forbid || forbid.length === 0) return { forbid: DEFAULT_FORBID_PROPS };
@@ -6301,9 +6764,9 @@ const isForbiddenForTag = (entry, tag) => {
6301
6764
  if (entry.allowedForPatterns.some((regex) => regex.test(tag))) return false;
6302
6765
  return true;
6303
6766
  };
6304
- const flattenJsxName$1 = (name) => {
6767
+ const flattenJsxName = (name) => {
6305
6768
  if (isNodeOfType(name, "JSXIdentifier")) return name.name;
6306
- if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$1(name.object)}.${name.property.name}`;
6769
+ if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName(name.object)}.${name.property.name}`;
6307
6770
  if (name.type === "ThisExpression" || name.type === "JSXThisExpression") return "this";
6308
6771
  return "";
6309
6772
  };
@@ -6317,11 +6780,11 @@ const forbidComponentProps = defineRule({
6317
6780
  recommendation: "List the props you want to block per component in the `forbidComponentProps.forbid` setting.",
6318
6781
  category: "Architecture",
6319
6782
  create: (context) => {
6320
- const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
6783
+ const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
6321
6784
  return { JSXOpeningElement(node) {
6322
6785
  if (entries.length === 0) return;
6323
6786
  if (!isSupportedJsxName(node.name)) return;
6324
- const tag = flattenJsxName$1(node.name);
6787
+ const tag = flattenJsxName(node.name);
6325
6788
  if (!tag) return;
6326
6789
  if (!(isReactComponentName(tag.split(".")[0]) || tag.includes("."))) return;
6327
6790
  for (const attribute of node.attributes) {
@@ -6346,7 +6809,7 @@ const forbidComponentProps = defineRule({
6346
6809
  //#endregion
6347
6810
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
6348
6811
  const buildMessage$23 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags.`;
6349
- const resolveSettings$43 = (settings) => {
6812
+ const resolveSettings$42 = (settings) => {
6350
6813
  const reactDoctor = settings?.["react-doctor"];
6351
6814
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
6352
6815
  const map = /* @__PURE__ */ new Map();
@@ -6368,7 +6831,7 @@ const forbidDomProps = defineRule({
6368
6831
  recommendation: "List the HTML props you want to block in the `forbidDomProps.forbid` setting.",
6369
6832
  category: "Architecture",
6370
6833
  create: (context) => {
6371
- const forbidMap = resolveSettings$43(context.settings);
6834
+ const forbidMap = resolveSettings$42(context.settings);
6372
6835
  return { JSXOpeningElement(node) {
6373
6836
  if (forbidMap.size === 0) return;
6374
6837
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -6416,34 +6879,6 @@ const flattenCalleeName = (callee) => {
6416
6879
  return null;
6417
6880
  };
6418
6881
  //#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
6882
  //#region src/plugin/utils/is-react-function-call.ts
6448
6883
  const PRAGMA = "React";
6449
6884
  const isReactFunctionCall = (node, expectedCall) => {
@@ -6455,7 +6890,7 @@ const isReactFunctionCall = (node, expectedCall) => {
6455
6890
  //#endregion
6456
6891
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
6457
6892
  const buildMessage$22 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here.`;
6458
- const resolveSettings$42 = (settings) => {
6893
+ const resolveSettings$41 = (settings) => {
6459
6894
  const reactDoctor = settings?.["react-doctor"];
6460
6895
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
6461
6896
  const map = /* @__PURE__ */ new Map();
@@ -6471,11 +6906,11 @@ const forbidElements = defineRule({
6471
6906
  recommendation: "List the element names you want to block in the `forbidElements.forbid` setting.",
6472
6907
  category: "Architecture",
6473
6908
  create: (context) => {
6474
- const forbidMap = resolveSettings$42(context.settings);
6909
+ const forbidMap = resolveSettings$41(context.settings);
6475
6910
  return {
6476
6911
  JSXOpeningElement(node) {
6477
6912
  if (forbidMap.size === 0) return;
6478
- const fullName = flattenJsxName(node.name);
6913
+ const fullName = flattenJsxName$1(node.name);
6479
6914
  if (!fullName || !forbidMap.has(fullName)) return;
6480
6915
  context.report({
6481
6916
  node: node.name,
@@ -6542,7 +6977,7 @@ const DEFAULT_HEADING_TAGS = [
6542
6977
  "h5",
6543
6978
  "h6"
6544
6979
  ];
6545
- const resolveSettings$41 = (settings) => {
6980
+ const resolveSettings$40 = (settings) => {
6546
6981
  const reactDoctor = settings?.["react-doctor"];
6547
6982
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
6548
6983
  return { headingTags: [...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []] };
@@ -6555,7 +6990,7 @@ const headingHasContent = defineRule({
6555
6990
  recommendation: "Put readable text in every heading.",
6556
6991
  category: "Accessibility",
6557
6992
  create: (context) => {
6558
- const settings = resolveSettings$41(context.settings);
6993
+ const settings = resolveSettings$40(context.settings);
6559
6994
  return { JSXOpeningElement(node) {
6560
6995
  const elementType = getElementType(node, context.settings);
6561
6996
  if (!settings.headingTags.includes(elementType)) return;
@@ -6575,7 +7010,7 @@ const headingHasContent = defineRule({
6575
7010
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
6576
7011
  const REQUIRE_DESTRUCTURE_MESSAGE = "This `useState` result is hard to follow.";
6577
7012
  const NAMING_CONVENTION_MESSAGE = "The setter here is hard to spot.";
6578
- const resolveSettings$40 = (settings) => {
7013
+ const resolveSettings$39 = (settings) => {
6579
7014
  const reactDoctor = settings?.["react-doctor"];
6580
7015
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
6581
7016
  };
@@ -6602,7 +7037,7 @@ const hookUseState = defineRule({
6602
7037
  recommendation: "Destructure useState as `const [thing, setThing] = useState(…)`.",
6603
7038
  category: "Architecture",
6604
7039
  create: (context) => {
6605
- const { allowDestructuredState } = resolveSettings$40(context.settings);
7040
+ const { allowDestructuredState } = resolveSettings$39(context.settings);
6606
7041
  return { CallExpression(node) {
6607
7042
  if (!isReactFunctionCall(node, "useState")) return;
6608
7043
  const parent = node.parent;
@@ -6705,7 +7140,7 @@ const hooksNoNanInDeps = defineRule({
6705
7140
  //#endregion
6706
7141
  //#region src/plugin/rules/a11y/html-has-lang.ts
6707
7142
  const MESSAGE$44 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
6708
- const resolveSettings$39 = (settings) => {
7143
+ const resolveSettings$38 = (settings) => {
6709
7144
  const reactDoctor = settings?.["react-doctor"];
6710
7145
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
6711
7146
  };
@@ -6742,7 +7177,7 @@ const htmlHasLang = defineRule({
6742
7177
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
6743
7178
  category: "Accessibility",
6744
7179
  create: (context) => {
6745
- const settings = resolveSettings$39(context.settings);
7180
+ const settings = resolveSettings$38(context.settings);
6746
7181
  const tagSet = new Set(settings.htmlTags);
6747
7182
  return { JSXOpeningElement(node) {
6748
7183
  const tag = getElementType(node, context.settings);
@@ -7138,7 +7573,7 @@ const DEFAULT_REDUNDANT_WORDS = [
7138
7573
  "photo",
7139
7574
  "picture"
7140
7575
  ];
7141
- const resolveSettings$38 = (settings) => {
7576
+ const resolveSettings$37 = (settings) => {
7142
7577
  const reactDoctor = settings?.["react-doctor"];
7143
7578
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
7144
7579
  return {
@@ -7185,8 +7620,10 @@ const imgRedundantAlt = defineRule({
7185
7620
  recommendation: "Do not put 'image' or 'photo' in alt text. Describe what is shown.",
7186
7621
  category: "Accessibility",
7187
7622
  create: (context) => {
7188
- const settings = resolveSettings$38(context.settings);
7623
+ if (isGeneratedImageRenderContext(context)) return {};
7624
+ const settings = resolveSettings$37(context.settings);
7189
7625
  return { JSXOpeningElement(node) {
7626
+ if (isGeneratedImageRenderContext(context, node)) return;
7190
7627
  const tag = getElementType(node, context.settings);
7191
7628
  if (!settings.components.includes(tag)) return;
7192
7629
  if (isHiddenFromScreenReader(node, context.settings)) return;
@@ -7275,7 +7712,7 @@ const DEFAULT_TABBABLE_ROLES = [
7275
7712
  "switch",
7276
7713
  "textbox"
7277
7714
  ];
7278
- const resolveSettings$37 = (settings) => {
7715
+ const resolveSettings$36 = (settings) => {
7279
7716
  const reactDoctor = settings?.["react-doctor"];
7280
7717
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
7281
7718
  };
@@ -7287,7 +7724,7 @@ const interactiveSupportsFocus = defineRule({
7287
7724
  recommendation: "Add `tabIndex` to elements with interactive roles and handlers.",
7288
7725
  category: "Accessibility",
7289
7726
  create: (context) => {
7290
- const settings = resolveSettings$37(context.settings);
7727
+ const settings = resolveSettings$36(context.settings);
7291
7728
  const tabbableSet = new Set(settings.tabbable);
7292
7729
  return { JSXOpeningElement(node) {
7293
7730
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
@@ -7308,88 +7745,6 @@ const interactiveSupportsFocus = defineRule({
7308
7745
  }
7309
7746
  });
7310
7747
  //#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
7748
  //#region src/plugin/rules/jotai/jotai-derived-atom-returns-fresh-object.ts
7394
7749
  const isAtomFromJotai = (callExpression) => {
7395
7750
  if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
@@ -8585,7 +8940,7 @@ const jsTosortedImmutable = defineRule({
8585
8940
  const NEVER_MESSAGE$2 = () => `This prop is written inconsistently.`;
8586
8941
  const ALWAYS_MESSAGE$2 = () => `This prop is written inconsistently.`;
8587
8942
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing.`;
8588
- const resolveSettings$36 = (settings) => {
8943
+ const resolveSettings$35 = (settings) => {
8589
8944
  const reactDoctor = settings?.["react-doctor"];
8590
8945
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
8591
8946
  return {
@@ -8603,7 +8958,7 @@ const jsxBooleanValue = defineRule({
8603
8958
  recommendation: "Pick a single boolean-attribute style across the codebase (default: omit `={true}`).",
8604
8959
  category: "Architecture",
8605
8960
  create: (context) => {
8606
- const settings = resolveSettings$36(context.settings);
8961
+ const settings = resolveSettings$35(context.settings);
8607
8962
  const alwaysSet = new Set(settings.always);
8608
8963
  const neverSet = new Set(settings.never);
8609
8964
  return { JSXAttribute(node) {
@@ -8657,7 +9012,7 @@ const jsxBooleanValue = defineRule({
8657
9012
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces do nothing here.";
8658
9013
  const REQUIRED_BRACES_MESSAGE = "This value needs curly braces `{ }` to read as an expression.";
8659
9014
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
8660
- const resolveSettings$35 = (settings) => {
9015
+ const resolveSettings$34 = (settings) => {
8661
9016
  const reactDoctor = settings?.["react-doctor"];
8662
9017
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
8663
9018
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -8753,7 +9108,7 @@ const jsxCurlyBracePresence = defineRule({
8753
9108
  recommendation: "Pick a consistent quoting style for JSX literal values.",
8754
9109
  category: "Architecture",
8755
9110
  create: (context) => {
8756
- const settings = resolveSettings$35(context.settings);
9111
+ const settings = resolveSettings$34(context.settings);
8757
9112
  return {
8758
9113
  JSXAttribute(node) {
8759
9114
  const value = node.value;
@@ -8836,7 +9191,7 @@ const containsJsxElement = (root) => {
8836
9191
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
8837
9192
  const JSX_NOT_ALLOWED = (extension) => `This file has JSX but a \`${extension}\` name.`;
8838
9193
  const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are meant for JSX, but this one has none.`;
8839
- const resolveSettings$34 = (settings) => {
9194
+ const resolveSettings$33 = (settings) => {
8840
9195
  const reactDoctor = settings?.["react-doctor"];
8841
9196
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
8842
9197
  return {
@@ -8858,7 +9213,7 @@ const jsxFilenameExtension = defineRule({
8858
9213
  recommendation: "Name files with JSX `.jsx` or `.tsx`, or whatever extension your project uses.",
8859
9214
  category: "Architecture",
8860
9215
  create: (context) => {
8861
- const settings = resolveSettings$34(context.settings);
9216
+ const settings = resolveSettings$33(context.settings);
8862
9217
  const allowedExtensions = normalizeExtensions(settings.extensions);
8863
9218
  const filename = normalizeFilename$1(context.filename ?? "fixture.tsx");
8864
9219
  const extensionOnly = path.extname(filename).slice(1);
@@ -8912,7 +9267,7 @@ const isJsxFragmentElement = (node) => {
8912
9267
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
8913
9268
  const SYNTAX_MESSAGE = "This fragment is written inconsistently.";
8914
9269
  const ELEMENT_MESSAGE = "This fragment is written inconsistently.";
8915
- const resolveSettings$33 = (settings) => {
9270
+ const resolveSettings$32 = (settings) => {
8916
9271
  const reactDoctor = settings?.["react-doctor"];
8917
9272
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
8918
9273
  };
@@ -8924,7 +9279,7 @@ const jsxFragments = defineRule({
8924
9279
  recommendation: "Pick one fragment style across the codebase.",
8925
9280
  category: "Architecture",
8926
9281
  create: (context) => {
8927
- const { mode } = resolveSettings$33(context.settings);
9282
+ const { mode } = resolveSettings$32(context.settings);
8928
9283
  return {
8929
9284
  JSXElement(node) {
8930
9285
  if (mode !== "syntax") return;
@@ -8968,7 +9323,7 @@ const buildPropRegex = (handlerPropPrefix) => {
8968
9323
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
8969
9324
  return new RegExp(`^(${escaped})[A-Z].*$`);
8970
9325
  };
8971
- const resolveSettings$32 = (settings) => {
9326
+ const resolveSettings$31 = (settings) => {
8972
9327
  const reactDoctor = settings?.["react-doctor"];
8973
9328
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
8974
9329
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -9044,7 +9399,7 @@ const jsxHandlerNames = defineRule({
9044
9399
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers.",
9045
9400
  category: "Architecture",
9046
9401
  create: (context) => {
9047
- const settings = resolveSettings$32(context.settings);
9402
+ const settings = resolveSettings$31(context.settings);
9048
9403
  return { JSXAttribute(node) {
9049
9404
  if (settings.ignoreComponentNames.length > 0) {
9050
9405
  const opening = node.parent;
@@ -9129,7 +9484,7 @@ const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reo
9129
9484
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
9130
9485
  const KEY_BEFORE_SPREAD = "The `{...spread}` can overwrite this `key` & break React's tracking.";
9131
9486
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
9132
- const resolveSettings$31 = (settings) => {
9487
+ const resolveSettings$30 = (settings) => {
9133
9488
  const reactDoctor = settings?.["react-doctor"];
9134
9489
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
9135
9490
  return {
@@ -9277,7 +9632,7 @@ const jsxKey = defineRule({
9277
9632
  severity: "error",
9278
9633
  recommendation: "Add a `key={...}` prop to each element produced inside `.map` / array literal.",
9279
9634
  create: (context) => {
9280
- const settings = resolveSettings$31(context.settings);
9635
+ const settings = resolveSettings$30(context.settings);
9281
9636
  return {
9282
9637
  JSXElement(node) {
9283
9638
  const openingElement = node.openingElement;
@@ -9327,194 +9682,10 @@ const jsxKey = defineRule({
9327
9682
  }
9328
9683
  });
9329
9684
  //#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
9685
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
9515
9686
  const buildMessage$18 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
9516
9687
  const DEFAULT_MAX_DEPTH = 14;
9517
- const resolveSettings$30 = (settings) => {
9688
+ const resolveSettings$29 = (settings) => {
9518
9689
  const reactDoctor = settings?.["react-doctor"];
9519
9690
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
9520
9691
  };
@@ -9574,7 +9745,7 @@ const jsxMaxDepth = defineRule({
9574
9745
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
9575
9746
  category: "Architecture",
9576
9747
  create: (context) => {
9577
- const { max } = resolveSettings$30(context.settings);
9748
+ const { max } = resolveSettings$29(context.settings);
9578
9749
  const checkNode = (node) => {
9579
9750
  if (!isLeafJsxNode(node)) return;
9580
9751
  const total = computeJsxAncestorDepth(node) + computeChildrenDepth(node.children ?? [], /* @__PURE__ */ new Set());
@@ -11269,7 +11440,7 @@ const jsxNoNewObjectAsProp = defineRule({
11269
11440
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
11270
11441
  const MESSAGE$35 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
11271
11442
  const JAVASCRIPT_URL_PATTERN = /j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
11272
- const resolveSettings$29 = (settings) => {
11443
+ const resolveSettings$28 = (settings) => {
11273
11444
  const reactDoctor = settings?.["react-doctor"];
11274
11445
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
11275
11446
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -11295,7 +11466,7 @@ const jsxNoScriptUrl = defineRule({
11295
11466
  recommendation: "Replace `javascript:` URLs with `onClick` or `onSubmit` handlers. React 19 blocks them anyway.",
11296
11467
  category: "Security",
11297
11468
  create: (context) => {
11298
- const options = resolveSettings$29(context.settings);
11469
+ const options = resolveSettings$28(context.settings);
11299
11470
  return { JSXOpeningElement(node) {
11300
11471
  const elementName = getElementName(node);
11301
11472
  if (!elementName) return;
@@ -11315,287 +11486,6 @@ const jsxNoScriptUrl = defineRule({
11315
11486
  }
11316
11487
  });
11317
11488
  //#endregion
11318
- //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
11319
- const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
11320
- const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
11321
- const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
11322
- const resolveSettings$28 = (settings) => {
11323
- const reactDoctor = settings?.["react-doctor"];
11324
- const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
11325
- const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
11326
- const linkComponents = /* @__PURE__ */ new Map();
11327
- for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
11328
- else if (typeof entry === "object" && entry !== null) {
11329
- const linkAttribute = entry.linkAttribute ?? "href";
11330
- linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
11331
- }
11332
- const formComponents = /* @__PURE__ */ new Map();
11333
- for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
11334
- else if (typeof entry === "object" && entry !== null) {
11335
- const formAttribute = entry.formAttribute ?? "action";
11336
- formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
11337
- }
11338
- return {
11339
- enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
11340
- warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
11341
- allowReferrer: ruleSettings.allowReferrer ?? false,
11342
- links: ruleSettings.links ?? true,
11343
- forms: ruleSettings.forms ?? false,
11344
- linkComponents,
11345
- formComponents
11346
- };
11347
- };
11348
- const isExternalLink = (href) => href.includes("//");
11349
- const matchHrefExpression = (expression, state) => {
11350
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
11351
- if (isExternalLink(expression.value)) state.isExternal = true;
11352
- return;
11353
- }
11354
- if (isNodeOfType(expression, "Identifier")) {
11355
- state.isDynamic = true;
11356
- return;
11357
- }
11358
- if (isNodeOfType(expression, "ConditionalExpression")) {
11359
- matchHrefExpression(expression.consequent, state);
11360
- matchHrefExpression(expression.alternate, state);
11361
- }
11362
- };
11363
- const checkHref = (attributeValue, enforceDynamicLinks) => {
11364
- const state = {
11365
- isExternal: false,
11366
- isDynamic: false
11367
- };
11368
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
11369
- else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
11370
- if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
11371
- return !(state.isExternal || state.isDynamic);
11372
- };
11373
- const checkRelValue = (text, allowReferrer) => {
11374
- const tokens = text.split(/\s+/);
11375
- if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
11376
- return tokens.some((token) => token.toLowerCase() === "noreferrer");
11377
- };
11378
- const matchRelExpression = (expression, allowReferrer) => {
11379
- const empty = {
11380
- combined: false,
11381
- testName: "",
11382
- consequent: false,
11383
- alternate: false
11384
- };
11385
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
11386
- combined: checkRelValue(expression.value, allowReferrer),
11387
- testName: "",
11388
- consequent: false,
11389
- alternate: false
11390
- };
11391
- if (isNodeOfType(expression, "ConditionalExpression")) {
11392
- const consequent = matchRelExpression(expression.consequent, allowReferrer);
11393
- const alternate = matchRelExpression(expression.alternate, allowReferrer);
11394
- const test = expression.test;
11395
- if (isNodeOfType(test, "Identifier")) return {
11396
- combined: consequent.combined && alternate.combined,
11397
- testName: test.name,
11398
- consequent: consequent.combined,
11399
- alternate: alternate.combined
11400
- };
11401
- return {
11402
- combined: consequent.combined && alternate.combined,
11403
- testName: "",
11404
- consequent: consequent.combined,
11405
- alternate: alternate.combined
11406
- };
11407
- }
11408
- return empty;
11409
- };
11410
- const checkRel = (attributeValue, allowReferrer) => {
11411
- const empty = {
11412
- combined: false,
11413
- testName: "",
11414
- consequent: false,
11415
- alternate: false
11416
- };
11417
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
11418
- combined: checkRelValue(attributeValue.value, allowReferrer),
11419
- testName: "",
11420
- consequent: false,
11421
- alternate: false
11422
- };
11423
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
11424
- const expression = attributeValue.expression;
11425
- if (expression.type === "JSXEmptyExpression") return empty;
11426
- return matchRelExpression(expression, allowReferrer);
11427
- }
11428
- return empty;
11429
- };
11430
- const matchTargetExpression = (expression) => {
11431
- const empty = {
11432
- combined: false,
11433
- testName: "",
11434
- consequent: false,
11435
- alternate: false
11436
- };
11437
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
11438
- combined: expression.value.toLowerCase() === "_blank",
11439
- testName: "",
11440
- consequent: false,
11441
- alternate: false
11442
- };
11443
- if (isNodeOfType(expression, "ConditionalExpression")) {
11444
- const consequent = matchTargetExpression(expression.consequent);
11445
- const alternate = matchTargetExpression(expression.alternate);
11446
- const test = expression.test;
11447
- const combined = consequent.combined || alternate.combined;
11448
- if (isNodeOfType(test, "Identifier")) return {
11449
- combined,
11450
- testName: test.name,
11451
- consequent: consequent.combined,
11452
- alternate: alternate.combined
11453
- };
11454
- return {
11455
- combined,
11456
- testName: "",
11457
- consequent: consequent.combined,
11458
- alternate: alternate.combined
11459
- };
11460
- }
11461
- return empty;
11462
- };
11463
- const checkTarget = (attributeValue) => {
11464
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
11465
- combined: attributeValue.value.toLowerCase() === "_blank",
11466
- testName: "",
11467
- consequent: false,
11468
- alternate: false
11469
- };
11470
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
11471
- const expression = attributeValue.expression;
11472
- if (expression.type === "JSXEmptyExpression") return {
11473
- combined: false,
11474
- testName: "",
11475
- consequent: false,
11476
- alternate: false
11477
- };
11478
- return matchTargetExpression(expression);
11479
- }
11480
- return {
11481
- combined: false,
11482
- testName: "",
11483
- consequent: false,
11484
- alternate: false
11485
- };
11486
- };
11487
- const getOpeningElementName = (node) => {
11488
- const name = node.name;
11489
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
11490
- return null;
11491
- };
11492
- const jsxNoTargetBlank = defineRule({
11493
- id: "jsx-no-target-blank",
11494
- title: "Unsafe target=_blank link",
11495
- severity: "warn",
11496
- recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
11497
- category: "Security",
11498
- create: (context) => {
11499
- const settings = resolveSettings$28(context.settings);
11500
- const isLink = (tagName) => {
11501
- if (!settings.links) return false;
11502
- if (tagName === "a") return true;
11503
- return settings.linkComponents.has(tagName);
11504
- };
11505
- const isForm = (tagName) => {
11506
- if (!settings.forms) return false;
11507
- if (tagName === "form") return true;
11508
- return settings.formComponents.has(tagName);
11509
- };
11510
- return { JSXOpeningElement(node) {
11511
- const tagName = getOpeningElementName(node);
11512
- if (!tagName) return;
11513
- if (!isLink(tagName) && !isForm(tagName)) return;
11514
- const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
11515
- const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
11516
- let targetTuple = {
11517
- combined: false,
11518
- testName: "",
11519
- consequent: false,
11520
- alternate: false
11521
- };
11522
- let relTuple = {
11523
- combined: false,
11524
- testName: "",
11525
- consequent: false,
11526
- alternate: false
11527
- };
11528
- let isHrefValid = true;
11529
- let hasHrefValue = false;
11530
- let warnSpread = false;
11531
- let targetReportNode = node.name;
11532
- let spreadReportNode = null;
11533
- for (const attribute of node.attributes) {
11534
- if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
11535
- if (settings.warnOnSpreadAttributes) {
11536
- warnSpread = true;
11537
- spreadReportNode = attribute;
11538
- targetTuple = {
11539
- combined: false,
11540
- testName: "",
11541
- consequent: false,
11542
- alternate: false
11543
- };
11544
- relTuple = {
11545
- combined: false,
11546
- testName: "",
11547
- consequent: false,
11548
- alternate: false
11549
- };
11550
- isHrefValid = false;
11551
- hasHrefValue = true;
11552
- }
11553
- continue;
11554
- }
11555
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
11556
- const attributeName = attribute.name;
11557
- if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
11558
- const propName = attributeName.name;
11559
- const value = attribute.value;
11560
- if (propName === "target") {
11561
- if (value) {
11562
- targetTuple = checkTarget(value);
11563
- targetReportNode = value;
11564
- }
11565
- } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
11566
- if (value) {
11567
- hasHrefValue = true;
11568
- isHrefValid = checkHref(value, settings.enforceDynamicLinks);
11569
- }
11570
- } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
11571
- }
11572
- if (warnSpread) {
11573
- if (hasHrefValue && isHrefValid || relTuple.combined) return;
11574
- context.report({
11575
- node: spreadReportNode ?? node,
11576
- message: SPREAD_MESSAGE
11577
- });
11578
- return;
11579
- }
11580
- if (!isHrefValid) {
11581
- if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
11582
- const consequentBad = targetTuple.consequent && !relTuple.consequent;
11583
- const alternateBad = targetTuple.alternate && !relTuple.alternate;
11584
- if (consequentBad || alternateBad) context.report({
11585
- node: targetReportNode,
11586
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
11587
- });
11588
- return;
11589
- }
11590
- if (targetTuple.combined && !relTuple.combined) context.report({
11591
- node: targetReportNode,
11592
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
11593
- });
11594
- }
11595
- } };
11596
- }
11597
- });
11598
- //#endregion
11599
11489
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
11600
11490
  const buildMessage$17 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
11601
11491
  const KNOWN_GLOBALS = new Set([
@@ -11925,7 +11815,7 @@ const jsxPropsNoSpreading = defineRule({
11925
11815
  create: (context) => {
11926
11816
  const settings = resolveSettings$25(context.settings);
11927
11817
  return { JSXOpeningElement(node) {
11928
- const tagName = flattenJsxName(node.name);
11818
+ const tagName = flattenJsxName$1(node.name);
11929
11819
  if (!tagName) return;
11930
11820
  const isCustom = isReactComponentName(tagName) || tagName.includes(".");
11931
11821
  const isHtml = !isCustom;
@@ -12515,7 +12405,6 @@ const nextjsAsyncClientComponent = defineRule({
12515
12405
  const PAGE_FILE_PATTERN = /\/page\.(tsx?|jsx?)$/;
12516
12406
  const PAGE_OR_LAYOUT_FILE_PATTERN = /\/(page|layout)\.(tsx?|jsx?)$/;
12517
12407
  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;
12518
- const OG_ROUTE_PATTERN = /\/og\b/i;
12519
12408
  const PAGES_DIRECTORY_PATTERN = /\/pages\//;
12520
12409
  const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
12521
12410
  "redirect",
@@ -12952,11 +12841,9 @@ const nextjsNoImgElement = defineRule({
12952
12841
  severity: "warn",
12953
12842
  recommendation: "`import Image from 'next/image'` for automatic WebP/AVIF, lazy loading, and responsive srcset",
12954
12843
  create: (context) => {
12955
- const filename = normalizeFilename$1(context.filename ?? "");
12956
- const isOgRoute = OG_ROUTE_PATTERN.test(filename);
12957
- const isMetadataImageRoute = isNextjsMetadataImageRouteFilename(filename);
12844
+ if (isGeneratedImageRenderContext(context)) return {};
12958
12845
  return { JSXOpeningElement(node) {
12959
- if (isOgRoute || isMetadataImageRoute) return;
12846
+ if (isGeneratedImageRenderContext(context, node)) return;
12960
12847
  if (isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "img") context.report({
12961
12848
  node,
12962
12849
  message: "Plain <img> ships unoptimized, oversized images to your users."
@@ -23799,7 +23686,7 @@ const noUnknownProperty = defineRule({
23799
23686
  create: (context) => {
23800
23687
  const { ignore = [], requireDataLowercase = false } = resolveSettings$10(context.settings);
23801
23688
  const ignoreSet = new Set(ignore);
23802
- if (isNextjsMetadataImageRouteFilename(context.filename)) ignoreSet.add("tw");
23689
+ if (isGeneratedImageRenderContext(context)) ignoreSet.add("tw");
23803
23690
  let fileIsNonReactJsx = false;
23804
23691
  return {
23805
23692
  Program(node) {
@@ -23834,6 +23721,7 @@ const noUnknownProperty = defineRule({
23834
23721
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
23835
23722
  const actualName = getJsxAttributeName(attribute.name);
23836
23723
  if (!actualName) continue;
23724
+ if (actualName === "tw" && isGeneratedImageRenderContext(context, node)) continue;
23837
23725
  if (ignoreSet.has(actualName)) continue;
23838
23726
  if (isValidDataAttribute(actualName)) {
23839
23727
  if (requireDataLowercase && hasUppercaseChar(actualName)) context.report({
@@ -25455,12 +25343,12 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
25455
25343
  const child = nodeRecord[key];
25456
25344
  if (Array.isArray(child)) for (const item of child) {
25457
25345
  if (!isAstNode(item)) continue;
25458
- if (FUNCTION_LIKE_TYPES$1.has(item.type)) continue;
25346
+ if (FUNCTION_LIKE_TYPES.has(item.type)) continue;
25459
25347
  visit(item);
25460
25348
  if (returnsObject) return;
25461
25349
  }
25462
25350
  else if (isAstNode(child)) {
25463
- if (FUNCTION_LIKE_TYPES$1.has(child.type)) continue;
25351
+ if (FUNCTION_LIKE_TYPES.has(child.type)) continue;
25464
25352
  visit(child);
25465
25353
  }
25466
25354
  }
@@ -26882,12 +26770,12 @@ const functionBodyHasReturnWithValue = (functionNode) => {
26882
26770
  const child = nodeRecord[key];
26883
26771
  if (Array.isArray(child)) for (const item of child) {
26884
26772
  if (!isAstNode(item)) continue;
26885
- if (FUNCTION_LIKE_TYPES$1.has(item.type)) continue;
26773
+ if (FUNCTION_LIKE_TYPES.has(item.type)) continue;
26886
26774
  visit(item);
26887
26775
  if (didFindReturn) return;
26888
26776
  }
26889
26777
  else if (isAstNode(child)) {
26890
- if (FUNCTION_LIKE_TYPES$1.has(child.type)) continue;
26778
+ if (FUNCTION_LIKE_TYPES.has(child.type)) continue;
26891
26779
  visit(child);
26892
26780
  }
26893
26781
  }
@@ -28637,7 +28525,7 @@ const isExpoUiNamespaceImport = (contextNode, localName) => {
28637
28525
  };
28638
28526
  const isExpoUiComponentElement = (openingElement, contextNode, componentName) => {
28639
28527
  if (!openingElement.name) return false;
28640
- const dottedName = flattenJsxName(openingElement.name);
28528
+ const dottedName = flattenJsxName$1(openingElement.name);
28641
28529
  if (!dottedName) return false;
28642
28530
  const [rootLocalName, secondName] = dottedName.split(".");
28643
28531
  if (isNamedImportOf(contextNode, rootLocalName, componentName)) return true;
@@ -28727,7 +28615,7 @@ const collectTopLevelReturnExpressions = (functionNode) => {
28727
28615
  if (!block || !isNodeOfType(block, "BlockStatement")) return [];
28728
28616
  const returnExpressions = [];
28729
28617
  const visit = (node) => {
28730
- if (FUNCTION_LIKE_TYPES$1.has(node.type)) return;
28618
+ if (FUNCTION_LIKE_TYPES.has(node.type)) return;
28731
28619
  if (isNodeOfType(node, "ReturnStatement") && node.argument) returnExpressions.push(node.argument);
28732
28620
  const nodeRecord = node;
28733
28621
  for (const fieldName of Object.keys(nodeRecord)) {
@@ -35999,17 +35887,6 @@ const reactDoctorRules = [
35999
35887
  category: "Security"
36000
35888
  }
36001
35889
  },
36002
- {
36003
- key: "react-doctor/jsx-no-target-blank",
36004
- id: "jsx-no-target-blank",
36005
- source: "react-doctor",
36006
- originallyExternal: true,
36007
- rule: {
36008
- ...jsxNoTargetBlank,
36009
- framework: "global",
36010
- category: "Security"
36011
- }
36012
- },
36013
35890
  {
36014
35891
  key: "react-doctor/jsx-no-undef",
36015
35892
  id: "jsx-no-undef",