@@ -1156,6 +1156,125 @@ TEST(tslsp_jsx_component_resolutions_join_exact_tag_sites) {
11561156 PASS ();
11571157}
11581158
1159+ TEST (tslsp_jsx_top_level_async_arrow_callers_keep_exact_identity ) {
1160+ const char * source = "function Card(): JSX.Element { return <span/>; }\n"
1161+ "const Direct = async () => <Card/>;\n"
1162+ "export const Exported = async () => <Card/>;\n"
1163+ "function Declared(): JSX.Element { return <Card/>; }\n" ;
1164+ const char * const declarations [] = {"const Direct" , "export const Exported" ,
1165+ "function Declared" };
1166+ const char * const callers [] = {"test.main.Direct" , "test.main.Exported" , "test.main.Declared" };
1167+ CBMFileResult * r = extract_tsx (source );
1168+ const bool result_created = r != NULL ;
1169+ bool exact_carrier [3 ] = {false};
1170+ bool exact_resolution [3 ] = {false};
1171+ bool resolved_invocation [3 ] = {false};
1172+ bool resolved_card_callee [3 ] = {false};
1173+ bool resolved_jsx_strategy [3 ] = {false};
1174+ int carrier_count [3 ] = {0 };
1175+ int failed_cases = result_created ? 0 : 1 ;
1176+
1177+ if (!result_created )
1178+ printf (" JSX caller diagnostic: extraction failed\n" );
1179+ if (r ) {
1180+ for (int i = 0 ; i < 3 ; i ++ ) {
1181+ const char * declaration = strstr (source , declarations [i ]);
1182+ const char * site = declaration ? strstr (declaration , "<Card/>" ) : NULL ;
1183+ if (!site ) {
1184+ printf (" JSX caller diagnostic: source site missing for %s\n" , callers [i ]);
1185+ failed_cases ++ ;
1186+ continue ;
1187+ }
1188+ const uint32_t start = (uint32_t )(site - source );
1189+ const uint32_t end = start + (uint32_t )strlen ("<Card/>" );
1190+ const CBMCall * carrier = NULL ;
1191+ for (int j = 0 ; j < r -> calls .count ; j ++ ) {
1192+ const CBMCall * call = & r -> calls .items [j ];
1193+ if (call -> callee_name && strcmp (call -> callee_name , "Card" ) == 0 &&
1194+ call -> site_start_byte == start && call -> site_end_byte == end ) {
1195+ carrier = call ;
1196+ carrier_count [i ]++ ;
1197+ }
1198+ }
1199+
1200+ const CBMResolvedCall * resolved = carrier ? cbm_pipeline_find_lsp_resolution (
1201+ & r -> resolved_calls , carrier , false)
1202+ : NULL ;
1203+ exact_carrier [i ] = carrier && carrier -> enclosing_func_qn &&
1204+ strcmp (carrier -> enclosing_func_qn , callers [i ]) == 0 &&
1205+ carrier -> site_start_byte == start && carrier -> site_end_byte == end &&
1206+ carrier -> requires_lsp_resolution ;
1207+ exact_resolution [i ] = resolved && resolved -> caller_qn &&
1208+ strcmp (resolved -> caller_qn , callers [i ]) == 0 &&
1209+ resolved -> site_start_byte == start && resolved -> site_end_byte == end ;
1210+ resolved_invocation [i ] = resolved && resolved -> kind == CBM_RESOLVED_INVOCATION ;
1211+ if (resolved && resolved -> callee_qn ) {
1212+ const size_t callee_len = strlen (resolved -> callee_qn );
1213+ resolved_card_callee [i ] = callee_len >= strlen (".Card" ) &&
1214+ strcmp (resolved -> callee_qn + callee_len - strlen (".Card" ),
1215+ ".Card" ) == 0 ;
1216+ }
1217+ resolved_jsx_strategy [i ] =
1218+ resolved && resolved -> strategy && strcmp (resolved -> strategy , "lsp_ts_jsx" ) == 0 ;
1219+ if (carrier_count [i ] != 1 || !exact_carrier [i ] || !exact_resolution [i ] ||
1220+ !resolved_invocation [i ] || !resolved_card_callee [i ] || !resolved_jsx_strategy [i ]) {
1221+ printf (" JSX caller diagnostic: expected=%s span=%u:%u carriers=%d caller=%s "
1222+ "requires_lsp=%d resolution=%s resolution_span=%u:%u kind=%d callee=%s "
1223+ "strategy=%s\n" ,
1224+ callers [i ], start , end , carrier_count [i ],
1225+ carrier && carrier -> enclosing_func_qn ? carrier -> enclosing_func_qn : "(null)" ,
1226+ carrier ? carrier -> requires_lsp_resolution : 0 ,
1227+ resolved && resolved -> caller_qn ? resolved -> caller_qn : "(null)" ,
1228+ resolved ? resolved -> site_start_byte : 0 , resolved ? resolved -> site_end_byte : 0 ,
1229+ resolved ? resolved -> kind : -1 ,
1230+ resolved && resolved -> callee_qn ? resolved -> callee_qn : "(null)" ,
1231+ resolved && resolved -> strategy ? resolved -> strategy : "(null)" );
1232+ failed_cases ++ ;
1233+ }
1234+ }
1235+ }
1236+
1237+ cbm_free_result (r );
1238+ ASSERT_EQ (failed_cases , 0 );
1239+ PASS ();
1240+ }
1241+
1242+ /* An exported arrow const is a module definition, not a lexical local. Bound
1243+ * into the module scope, it made the JSX resolver fail closed on `<Badge />`
1244+ * (the edge was lost: JSX carriers have no other resolver) and the exact
1245+ * same-module call path skip `formatLabel()`. Both callers must resolve both
1246+ * targets exactly, whether the caller is a function or an arrow const. */
1247+ static bool ts_resolved_with_strategy (const CBMFileResult * r , const char * caller ,
1248+ const char * callee , const char * strategy ) {
1249+ int idx = require_resolved (r , caller , callee );
1250+ const char * got = idx >= 0 ? r -> resolved_calls .items [idx ].strategy : NULL ;
1251+ if (idx >= 0 && (!got || strcmp (got , strategy ) != 0 )) {
1252+ printf (" %s -> %s resolved by %s, expected %s\n" , caller , callee , got ? got : "(null)" ,
1253+ strategy );
1254+ }
1255+ return got && strcmp (got , strategy ) == 0 ;
1256+ }
1257+
1258+ TEST (tslsp_jsx_exported_arrow_component_resolves_in_same_module ) {
1259+ CBMFileResult * r =
1260+ extract_tsx ("export const Badge = () => <span/>;\n"
1261+ "export const formatLabel = (s: string) => s.trim();\n"
1262+ "export function FnUser() { formatLabel('a'); return <Badge/>; }\n"
1263+ "export const ArrowUser = () => { formatLabel('b'); return <Badge/>; };\n" );
1264+ ASSERT_NOT_NULL (r );
1265+ bool fn_jsx = ts_resolved_with_strategy (r , "main.FnUser" , "main.Badge" , "lsp_ts_jsx" );
1266+ bool arrow_jsx = ts_resolved_with_strategy (r , "main.ArrowUser" , "main.Badge" , "lsp_ts_jsx" );
1267+ bool fn_call = ts_resolved_with_strategy (r , "main.FnUser" , "main.formatLabel" , "lsp_ts_local" );
1268+ bool arrow_call =
1269+ ts_resolved_with_strategy (r , "main.ArrowUser" , "main.formatLabel" , "lsp_ts_local" );
1270+ cbm_free_result (r );
1271+ ASSERT_TRUE (fn_jsx );
1272+ ASSERT_TRUE (arrow_jsx );
1273+ ASSERT_TRUE (fn_call );
1274+ ASSERT_TRUE (arrow_call );
1275+ PASS ();
1276+ }
1277+
11591278/* A callable value named like a module component owns the JSX tag. The LSP
11601279 * pass must not confidently redirect it to the module function, and the
11611280 * carrier must require semantic resolution so graph construction cannot do
@@ -4863,6 +4982,8 @@ SUITE(ts_lsp) {
48634982 RUN_TEST (tslsp_jsx_component_with_children );
48644983 RUN_TEST (tslsp_jsx_import_requires_registered_component );
48654984 RUN_TEST (tslsp_jsx_component_resolutions_join_exact_tag_sites );
4985+ RUN_TEST (tslsp_jsx_top_level_async_arrow_callers_keep_exact_identity );
4986+ RUN_TEST (tslsp_jsx_exported_arrow_component_resolves_in_same_module );
48664987 RUN_TEST (tslsp_jsx_local_tag_shadow_blocks_module_component );
48674988 RUN_TEST (tslsp_jsx_intrinsic_skipped );
48684989 RUN_TEST (tslsp_jsx_nested_component );
0 commit comments