@@ -20,12 +20,14 @@ pub fn is_package(module_path: &Path) -> bool {
2020 . unwrap_or ( false )
2121}
2222
23+ /// Check if a module is a descendant of another module.
24+ pub fn is_descendant ( module_name : & str , potential_ancestor : & str ) -> bool {
25+ module_name. starts_with ( & format ! ( "{}." , potential_ancestor) )
26+ }
27+
2328/// Check if module is internal
2429pub fn is_internal ( module_name : & str , package : & str ) -> bool {
25- if module_name == package || module_name. starts_with ( & format ! ( "{}." , package) ) {
26- return true ;
27- }
28- false
30+ module_name == package || is_descendant ( module_name, package)
2931}
3032
3133/// Convert module path to module name
@@ -105,13 +107,62 @@ pub fn resolve_internal_module(
105107 None
106108}
107109
108- /// Get external module name
109- pub fn resolve_external_module ( module_name : & str ) -> String {
110- // For simplicity, just return the root module for external imports
111- // This matches the basic behavior from _distill_external_module
112- module_name
113- . split ( '.' )
114- . next ( )
115- . unwrap_or ( module_name)
116- . to_string ( )
110+ /// Given a module that we already know is external, turn it into a module to add to the graph.
111+ ///
112+ /// The 'distillation' process involves removing any unwanted subpackages. For example,
113+ /// django.models.db should be turned into simply django.
114+ ///
115+ /// The process is more complex for potential namespace packages, as it's not possible to
116+ /// determine the portion package simply from name. Rather than adding the overhead of a
117+ /// filesystem read, we just get the shallowest component that does not clash with an internal
118+ /// module namespace. Take, for example, foo.blue.alpha.one. If one of the found
119+ /// packages is foo.blue.beta, the module will be distilled to foo.blue.alpha.
120+ /// Alternatively, if the found package is foo.green, the distilled module will
121+ /// be foo.blue.
122+ ///
123+ /// Returns None if the module is a parent of one of the internal packages (doesn't make sense,
124+ /// probably an import of a namespace package).
125+ pub fn distill_external_module (
126+ module_name : & str ,
127+ found_package_names : & HashSet < String > ,
128+ ) -> Option < String > {
129+ let module_root = module_name. split ( '.' ) . next ( ) . unwrap ( ) ;
130+
131+ for found_package in found_package_names {
132+ // If it's a module that is a parent of the package, return None
133+ // as it doesn't make sense and is probably an import of a namespace package.
134+ if is_descendant ( found_package, module_name) {
135+ return None ;
136+ }
137+ }
138+
139+ // If it shares a namespace with an internal module, get the shallowest component that does
140+ // not clash with an internal module namespace.
141+ let mut candidate_portions: Vec < String > = Vec :: new ( ) ;
142+ let mut sorted_found_packages: Vec < & String > = found_package_names. iter ( ) . collect ( ) ;
143+ sorted_found_packages. sort ( ) ;
144+ sorted_found_packages. reverse ( ) ;
145+
146+ for found_package in sorted_found_packages {
147+ if is_descendant ( found_package, module_root) {
148+ let mut internal_components: Vec < & str > = found_package. split ( '.' ) . collect ( ) ;
149+ let mut external_components: Vec < & str > = module_name. split ( '.' ) . collect ( ) ;
150+ let mut external_namespace_components: Vec < & str > = vec ! [ ] ;
151+ while external_components[ 0 ] == internal_components[ 0 ] {
152+ external_namespace_components. push ( external_components. remove ( 0 ) ) ;
153+ internal_components. remove ( 0 ) ;
154+ }
155+ external_namespace_components. push ( external_components[ 0 ] ) ;
156+ candidate_portions. push ( external_namespace_components. join ( "." ) ) ;
157+ }
158+ }
159+
160+ if !candidate_portions. is_empty ( ) {
161+ // If multiple internal modules share a namespace with this module, use the deepest one
162+ // as we know that that will be a namespace too.
163+ candidate_portions. sort_by_key ( |portion| portion. split ( '.' ) . count ( ) ) ;
164+ Some ( candidate_portions. last ( ) . unwrap ( ) . clone ( ) )
165+ } else {
166+ Some ( module_root. to_string ( ) )
167+ }
117168}
0 commit comments