11use biome_js_syntax:: {
2- AnyJsArrayBindingPatternElement , AnyJsBindingPattern , AnyJsImportClause , AnyJsModuleItem ,
3- AnyJsObjectBindingPatternMember , AnyJsRoot , JsImport , JsModuleItemList , JsVariableStatement ,
2+ AnyJsArrayBindingPatternElement , AnyJsBindingPattern , AnyJsModuleItem ,
3+ AnyJsObjectBindingPatternMember , AnyJsRoot , AnyJsStatement , AnyTsIdentifierBinding , JsImport ,
4+ JsModuleItemList , JsVariableStatement ,
45} ;
56use biome_rowan:: { AstNode , AstSeparatedList , TextRange , TokenText , WalkEvent } ;
67use rustc_hash:: FxHashMap ;
@@ -52,11 +53,30 @@ impl EmbeddedBuilder {
5253 fn visit_module_item_list ( & mut self , list : JsModuleItemList ) {
5354 for item in list {
5455 match item {
55- AnyJsModuleItem :: AnyJsStatement ( statement) => {
56- if let Some ( variable_statement) = statement . as_js_variable_statement ( ) {
56+ AnyJsModuleItem :: AnyJsStatement ( statement) => match statement {
57+ AnyJsStatement :: JsVariableStatement ( variable_statement) => {
5758 self . visit_js_variable_statement ( variable_statement. clone ( ) ) ;
5859 }
59- }
60+ AnyJsStatement :: JsFunctionDeclaration ( decl) => {
61+ self . register_js_binding ( decl. id ( ) ) ;
62+ }
63+ AnyJsStatement :: JsClassDeclaration ( decl) => {
64+ self . register_js_binding ( decl. id ( ) ) ;
65+ }
66+ AnyJsStatement :: TsEnumDeclaration ( decl) => {
67+ self . register_js_binding ( decl. id ( ) ) ;
68+ }
69+ AnyJsStatement :: TsInterfaceDeclaration ( decl) => {
70+ self . register_ts_identifier_binding ( decl. id ( ) ) ;
71+ }
72+ AnyJsStatement :: TsTypeAliasDeclaration ( decl) => {
73+ self . register_ts_identifier_binding ( decl. binding_identifier ( ) ) ;
74+ }
75+ AnyJsStatement :: TsDeclareFunctionDeclaration ( decl) => {
76+ self . register_js_binding ( decl. id ( ) ) ;
77+ }
78+ _ => { }
79+ } ,
6080 AnyJsModuleItem :: JsExport ( _) => { }
6181 AnyJsModuleItem :: JsImport ( import) => {
6282 self . visit_js_import ( import) ;
@@ -85,7 +105,8 @@ impl EmbeddedBuilder {
85105 }
86106 }
87107
88- if let AnyJsImportClause :: JsImportDefaultClause ( import) = clause {
108+ // Handle default clause using accessors generated by the syntax crate.
109+ if let Some ( import) = clause. as_js_import_default_clause ( ) {
89110 let name = import. default_specifier ( ) . ok ( ) ?;
90111 let name = name. local_name ( ) . ok ( ) ?;
91112 let name = name. as_js_identifier_binding ( ) ?;
@@ -94,6 +115,45 @@ impl EmbeddedBuilder {
94115 . insert ( name. text_trimmed_range ( ) , name. token_text_trimmed ( ) ) ;
95116 }
96117
118+ // Namespace imports: `import * as Foo from "bar"` should register `Foo`.
119+ if let Some ( import) = clause. as_js_import_namespace_clause ( ) {
120+ let specifier = import. namespace_specifier ( ) . ok ( ) ?;
121+ let name = specifier. local_name ( ) . ok ( ) ?;
122+ let name = name. as_js_identifier_binding ( ) ?;
123+ let name = name. name_token ( ) . ok ( ) ?;
124+ self . js_bindings
125+ . insert ( name. text_trimmed_range ( ) , name. token_text_trimmed ( ) ) ;
126+ }
127+
128+ Some ( ( ) )
129+ }
130+
131+ /// Registers the name binding from a `SyntaxResult<AnyJsBinding>`.
132+ /// Used for `JsFunctionDeclaration::id()`, `JsClassDeclaration::id()`,
133+ /// `TsEnumDeclaration::id()`, and `TsDeclareFunctionDeclaration::id()`.
134+ fn register_js_binding (
135+ & mut self ,
136+ result : biome_rowan:: SyntaxResult < biome_js_syntax:: AnyJsBinding > ,
137+ ) -> Option < ( ) > {
138+ let binding = result. ok ( ) ?;
139+ let identifier = binding. as_js_identifier_binding ( ) ?;
140+ let token = identifier. name_token ( ) . ok ( ) ?;
141+ self . js_bindings
142+ . insert ( token. text_trimmed_range ( ) , token. token_text_trimmed ( ) ) ;
143+ Some ( ( ) )
144+ }
145+ /// Registers the name binding from a `SyntaxResult<AnyTsIdentifierBinding>`.
146+ /// Used for `TsInterfaceDeclaration::id()` and
147+ /// `TsTypeAliasDeclaration::binding_identifier()`.
148+ fn register_ts_identifier_binding (
149+ & mut self ,
150+ result : biome_rowan:: SyntaxResult < AnyTsIdentifierBinding > ,
151+ ) -> Option < ( ) > {
152+ let binding = result. ok ( ) ?;
153+ let identifier = binding. as_ts_identifier_binding ( ) ?;
154+ let token = identifier. name_token ( ) . ok ( ) ?;
155+ self . js_bindings
156+ . insert ( token. text_trimmed_range ( ) , token. token_text_trimmed ( ) ) ;
97157 Some ( ( ) )
98158 }
99159
@@ -312,4 +372,60 @@ let lorem = "";
312372 assert ! ( contains_binding( & service, "Alas2" ) ) ;
313373 assert ! ( contains_binding( & service, "lorem" ) ) ;
314374 }
375+
376+ #[ test]
377+ fn tracks_function_declarations ( ) {
378+ let source = r#"
379+ function buildLink(base: string, path: string): string { return base + path; }
380+ async function fetchData() {}
381+ function* generator() {}
382+ "# ;
383+ let mut service = EmbeddedExportedBindings :: default ( ) ;
384+ let mut builder = service. builder ( ) ;
385+ visit_js_root ( & mut builder, & parse_js ( source) ) ;
386+ service. finish ( builder) ;
387+ assert ! ( contains_binding( & service, "buildLink" ) ) ;
388+ assert ! ( contains_binding( & service, "fetchData" ) ) ;
389+ assert ! ( contains_binding( & service, "generator" ) ) ;
390+ }
391+
392+ #[ test]
393+ fn tracks_class_declarations ( ) {
394+ let source = r#"
395+ class MyService {}
396+ abstract class BaseHandler {}
397+ "# ;
398+ let mut service = EmbeddedExportedBindings :: default ( ) ;
399+ let mut builder = service. builder ( ) ;
400+ visit_js_root ( & mut builder, & parse_js ( source) ) ;
401+ service. finish ( builder) ;
402+ assert ! ( contains_binding( & service, "MyService" ) ) ;
403+ assert ! ( contains_binding( & service, "BaseHandler" ) ) ;
404+ }
405+
406+ #[ test]
407+ fn tracks_typescript_declarations ( ) {
408+ let source = r#"
409+ type UserId = string;
410+ interface UserProfile { name: string }
411+ enum Direction { Up, Down }
412+ "# ;
413+ let mut service = EmbeddedExportedBindings :: default ( ) ;
414+ let mut builder = service. builder ( ) ;
415+ visit_js_root ( & mut builder, & parse_js ( source) ) ;
416+ service. finish ( builder) ;
417+ assert ! ( contains_binding( & service, "UserId" ) ) ;
418+ assert ! ( contains_binding( & service, "UserProfile" ) ) ;
419+ assert ! ( contains_binding( & service, "Direction" ) ) ;
420+ }
421+
422+ #[ test]
423+ fn tracks_namespace_imports ( ) {
424+ let source = r#"import * as Vue from "vue";"# ;
425+ let mut service = EmbeddedExportedBindings :: default ( ) ;
426+ let mut builder = service. builder ( ) ;
427+ visit_js_root ( & mut builder, & parse_js ( source) ) ;
428+ service. finish ( builder) ;
429+ assert ! ( contains_binding( & service, "Vue" ) ) ;
430+ }
315431}
0 commit comments