@@ -365,6 +365,96 @@ TEST(ei_typescript_named_relative_import) {
365365 PASS ();
366366}
367367
368+ /* #1682: extensionless dotted basenames are part of the module name. The
369+ * resolver used to strip `.engine`, miss the module, and bind both imports to
370+ * the same-named fixture Function in the sibling spec file. */
371+ TEST (ei_typescript_dotted_relative_import_targets_source_module_issue1682 ) {
372+ static const char * engine_path = "packages/api/src/modules/featureX/featureX.engine.ts" ;
373+ static const char * consumer_path = "packages/api/src/modules/consumer/consumer.service.ts" ;
374+ static const EILangFile f [] = {
375+ {"packages/api/src/modules/featureX/featureX.engine.ts" ,
376+ "export interface SomeType { id: string; qty: number; }\n"
377+ "export interface Evaluation { rateByItem: Record<string, number>; }\n"
378+ "export function helperB(configs: SomeType[], lines: SomeType[]): Evaluation {\n"
379+ " return { rateByItem: { [lines[0].id]: lines[0].qty + configs.length } };\n"
380+ "}\n" },
381+ {"packages/api/src/modules/featureX/featureX.service.ts" ,
382+ "import { SomeType, Evaluation, helperB } from './featureX.engine';\n"
383+ "export class FeatureXService {\n"
384+ " evaluate(configs: SomeType[], lines: SomeType[]): Evaluation {\n"
385+ " return helperB(configs, lines);\n"
386+ " }\n"
387+ "}\n" },
388+ {"packages/api/src/modules/featureX/featureX.service.spec.ts" ,
389+ "import { SomeType, helperB } from './featureX.engine';\n"
390+ "function featureX(overrides: Partial<SomeType>): SomeType {\n"
391+ " return { id: 'x', qty: 1, ...overrides };\n"
392+ "}\n"
393+ "export function exerciseFixture(): number {\n"
394+ " return helperB([featureX({})], [featureX({ qty: 2 })]).rateByItem.x;\n"
395+ "}\n" },
396+ {"packages/api/src/modules/consumer/consumer.service.ts" ,
397+ "import { helperB, type SomeType } from '../featureX/featureX.engine';\n"
398+ "export class ConsumerService {\n"
399+ " callerMethod(items: SomeType[]): number {\n"
400+ " return helperB(items, [{ id: 'p1', qty: 1 }]).rateByItem.p1;\n"
401+ " }\n"
402+ "}\n" },
403+ {"packages/mobile/src/api.ts" ,
404+ "export function helperB(token: string): Promise<unknown> {\n"
405+ " return fetch('/api/x', { method: 'POST', body: token });\n"
406+ "}\n" },
407+ };
408+
409+ EILangProj lp ;
410+ cbm_store_t * store = ei_index_files (& lp , f , (int )(sizeof (f ) / sizeof (f [0 ])));
411+ ASSERT_NOT_NULL (store );
412+
413+ int64_t consumer_id = ei_node_id_for_file_label (store , lp .project , consumer_path , "File" );
414+ ASSERT_GT (consumer_id , 0 );
415+
416+ cbm_edge_t * edges = NULL ;
417+ int edge_count = 0 ;
418+ ASSERT_EQ (
419+ cbm_store_find_edges_by_source_type (store , consumer_id , "IMPORTS" , & edges , & edge_count ),
420+ CBM_STORE_OK );
421+
422+ bool saw_helper = false;
423+ bool saw_type = false;
424+ bool helper_target_ok = false;
425+ bool type_target_ok = false;
426+ for (int i = 0 ; i < edge_count ; i ++ ) {
427+ const char * props = edges [i ].properties_json ? edges [i ].properties_json : "" ;
428+ bool is_helper = strstr (props , "\"local_name\":\"helperB\"" ) != NULL ;
429+ bool is_type = strstr (props , "\"local_name\":\"SomeType\"" ) != NULL ;
430+ if (!is_helper && !is_type ) {
431+ continue ;
432+ }
433+
434+ cbm_node_t * target = (cbm_node_t * )calloc (1 , sizeof (cbm_node_t ));
435+ ASSERT_NOT_NULL (target );
436+ ASSERT_EQ (cbm_store_find_node_by_id (store , edges [i ].target_id , target ), CBM_STORE_OK );
437+ bool target_ok = target -> file_path && strcmp (target -> file_path , engine_path ) == 0 ;
438+ if (is_helper ) {
439+ saw_helper = true;
440+ helper_target_ok = target_ok ;
441+ }
442+ if (is_type ) {
443+ saw_type = true;
444+ type_target_ok = target_ok ;
445+ }
446+ cbm_store_free_nodes (target , 1 );
447+ }
448+ cbm_store_free_edges (edges , edge_count );
449+ ei_cleanup (& lp , store );
450+
451+ ASSERT_TRUE (saw_helper );
452+ ASSERT_TRUE (saw_type );
453+ ASSERT_TRUE (helper_target_ok );
454+ ASSERT_TRUE (type_target_ok );
455+ PASS ();
456+ }
457+
368458/* TypeScript: default import `import helper from './util'`. */
369459TEST (ei_typescript_default_import ) {
370460 static const EILangFile f [] = {
@@ -1063,6 +1153,7 @@ SUITE(edge_imports) {
10631153
10641154 /* ── GREEN GUARDS — TypeScript (must stay passing) ── */
10651155 RUN_TEST (ei_typescript_named_relative_import );
1156+ RUN_TEST (ei_typescript_dotted_relative_import_targets_source_module_issue1682 );
10661157 RUN_TEST (ei_typescript_default_import );
10671158 RUN_TEST (ei_typescript_namespace_import );
10681159 RUN_TEST (ei_typescript_aliased_import );
0 commit comments