@@ -4646,6 +4646,37 @@ static void extract_destructured_vars(CBMExtractCtx *ctx, TSNode pattern, TSNode
46464646 }
46474647}
46484648
4649+ /* True for `require("...")` call_expressions with a string-literal path — the
4650+ * CommonJS import form that extract_imports records with local_name = the
4651+ * enclosing declarator's identifier. Same string-argument kinds as
4652+ * process_commonjs_require so the two stay in lockstep. */
4653+ static bool is_require_import_call (TSNode value , const char * source , CBMArena * a ) {
4654+ if (strcmp (ts_node_type (value ), "call_expression" ) != 0 ) {
4655+ return false;
4656+ }
4657+ TSNode fn = ts_node_child_by_field_name (value , TS_FIELD ("function" ));
4658+ if (ts_node_is_null (fn ) || strcmp (ts_node_type (fn ), "identifier" ) != 0 ) {
4659+ return false;
4660+ }
4661+ char * fn_name = cbm_node_text (a , fn , source );
4662+ if (!fn_name || strcmp (fn_name , "require" ) != 0 ) {
4663+ return false;
4664+ }
4665+ TSNode args = ts_node_child_by_field_name (value , TS_FIELD ("arguments" ));
4666+ if (ts_node_is_null (args )) {
4667+ return false;
4668+ }
4669+ uint32_t argc = ts_node_named_child_count (args );
4670+ for (uint32_t i = 0 ; i < argc ; i ++ ) {
4671+ const char * ak = ts_node_type (ts_node_named_child (args , i ));
4672+ if (strcmp (ak , "string" ) == 0 || strcmp (ak , "string_literal" ) == 0 ||
4673+ strcmp (ak , "template_string" ) == 0 ) {
4674+ return true;
4675+ }
4676+ }
4677+ return false;
4678+ }
4679+
46494680// JS/TS variable extraction: skip function-assigned declarators.
46504681static void extract_js_vars (CBMExtractCtx * ctx , TSNode node , CBMArena * a ) {
46514682 uint32_t n = ts_node_named_child_count (node );
@@ -4654,13 +4685,15 @@ static void extract_js_vars(CBMExtractCtx *ctx, TSNode node, CBMArena *a) {
46544685 if (strcmp (ts_node_type (child ), "variable_declarator" ) != 0 ) {
46554686 continue ;
46564687 }
4688+ bool is_require = false;
46574689 TSNode value = ts_node_child_by_field_name (child , TS_FIELD ("value" ));
46584690 if (!ts_node_is_null (value )) {
46594691 const char * vk = ts_node_type (value );
46604692 if (strcmp (vk , "arrow_function" ) == 0 || strcmp (vk , "function_expression" ) == 0 ||
46614693 strcmp (vk , "generator_function" ) == 0 ) {
46624694 continue ;
46634695 }
4696+ is_require = is_require_import_call (value , ctx -> source , a );
46644697 }
46654698 TSNode vname = ts_node_child_by_field_name (child , TS_FIELD ("name" ));
46664699 if (!ts_node_is_null (vname )) {
@@ -4670,6 +4703,15 @@ static void extract_js_vars(CBMExtractCtx *ctx, TSNode node, CBMArena *a) {
46704703 if (strcmp (nk , "object_pattern" ) == 0 || strcmp (nk , "array_pattern" ) == 0 ) {
46714704 extract_destructured_vars (ctx , vname , child , a );
46724705 } else {
4706+ /* `const foo = require('./foo')` is an import binding, not a
4707+ * definition — extract_imports records local_name="foo". A
4708+ * Variable node here shadows call resolution onto the alias
4709+ * and orphans the imported callee (#871); ESM `import`
4710+ * bindings emit no Variable either. Destructured requires
4711+ * keep theirs: the import row only records the module. */
4712+ if (is_require ) {
4713+ continue ;
4714+ }
46734715 push_var_def (ctx , cbm_node_text (a , vname , ctx -> source ), child );
46744716 }
46754717 }
0 commit comments