@@ -16,7 +16,7 @@ class InvalidSourceList(Exception):
1616 """Exception indicating a problem in the list of sources given to mypy."""
1717
1818
19- def create_source_list (files : Sequence [str ], options : Options ,
19+ def create_source_list (paths : Sequence [str ], options : Options ,
2020 fscache : Optional [FileSystemCache ] = None ,
2121 allow_empty_dir : bool = False ) -> List [BuildSource ]:
2222 """From a list of source files/directories, makes a list of BuildSources.
@@ -26,22 +26,24 @@ def create_source_list(files: Sequence[str], options: Options,
2626 fscache = fscache or FileSystemCache ()
2727 finder = SourceFinder (fscache )
2828
29- targets = []
30- for f in files :
31- if f .endswith (PY_EXTENSIONS ):
29+ sources = []
30+ for path in paths :
31+ path = os .path .normpath (path )
32+ if path .endswith (PY_EXTENSIONS ):
3233 # Can raise InvalidSourceList if a directory doesn't have a valid module name.
33- name , base_dir = finder .crawl_up (os .path .normpath (f ))
34- targets .append (BuildSource (f , name , None , base_dir ))
35- elif fscache .isdir (f ):
36- sub_targets = finder .expand_dir (os .path .normpath (f ))
37- if not sub_targets and not allow_empty_dir :
38- raise InvalidSourceList ("There are no .py[i] files in directory '{}'"
39- .format (f ))
40- targets .extend (sub_targets )
34+ name , base_dir = finder .crawl_up (path )
35+ sources .append (BuildSource (path , name , None , base_dir ))
36+ elif fscache .isdir (path ):
37+ sub_sources = finder .find_sources_in_dir (path , explicit_package_roots = None )
38+ if not sub_sources and not allow_empty_dir :
39+ raise InvalidSourceList (
40+ "There are no .py[i] files in directory '{}'" .format (path )
41+ )
42+ sources .extend (sub_sources )
4143 else :
42- mod = os .path .basename (f ) if options .scripts_are_modules else None
43- targets .append (BuildSource (f , mod , None ))
44- return targets
44+ mod = os .path .basename (path ) if options .scripts_are_modules else None
45+ sources .append (BuildSource (path , mod , None ))
46+ return sources
4547
4648
4749def keyfunc (name : str ) -> Tuple [int , str ]:
@@ -62,57 +64,82 @@ def __init__(self, fscache: FileSystemCache) -> None:
6264 # A cache for package names, mapping from directory path to module id and base dir
6365 self .package_cache = {} # type: Dict[str, Tuple[str, str]]
6466
65- def expand_dir (self , arg : str , mod_prefix : str = '' ) -> List [BuildSource ]:
66- """Convert a directory name to a list of sources to build."""
67- f = self .get_init_file (arg )
68- if mod_prefix and not f :
69- return []
67+ def find_sources_in_dir (
68+ self , path : str , explicit_package_roots : Optional [List [str ]]
69+ ) -> List [BuildSource ]:
70+ if explicit_package_roots is None :
71+ mod_prefix , root_dir = self .crawl_up_dir (path )
72+ else :
73+ mod_prefix = os .path .basename (path )
74+ root_dir = os .path .dirname (path ) or "."
75+ if mod_prefix :
76+ mod_prefix += "."
77+ return self .find_sources_in_dir_helper (path , mod_prefix , root_dir , explicit_package_roots )
78+
79+ def find_sources_in_dir_helper (
80+ self , dir_path : str , mod_prefix : str , root_dir : str ,
81+ explicit_package_roots : Optional [List [str ]]
82+ ) -> List [BuildSource ]:
83+ assert not mod_prefix or mod_prefix .endswith ("." )
84+
85+ init_file = self .get_init_file (dir_path )
86+ # If the current directory is an explicit package root, explore it as such.
87+ # Alternatively, if we aren't given explicit package roots and we don't have an __init__
88+ # file, recursively explore this directory as a new package root.
89+ if (
90+ (explicit_package_roots is not None and dir_path in explicit_package_roots )
91+ or (explicit_package_roots is None and init_file is None )
92+ ):
93+ mod_prefix = ""
94+ root_dir = dir_path
95+
7096 seen = set () # type: Set[str]
7197 sources = []
72- top_mod , base_dir = self .crawl_up_dir (arg )
73- if f and not mod_prefix :
74- mod_prefix = top_mod + '.'
75- if mod_prefix :
76- sources .append (BuildSource (f , mod_prefix .rstrip ('.' ), None , base_dir ))
77- names = self .fscache .listdir (arg )
98+
99+ if init_file :
100+ sources .append (BuildSource (init_file , mod_prefix .rstrip ("." ), None , root_dir ))
101+
102+ names = self .fscache .listdir (dir_path )
78103 names .sort (key = keyfunc )
79104 for name in names :
80105 # Skip certain names altogether
81- if (name == '__pycache__' or name == 'py.typed'
82- or name .startswith ('.' )
83- or name .endswith (('~' , '.pyc' , '.pyo' ))):
106+ if name == '__pycache__' or name .startswith ('.' ) or name .endswith ('~' ):
84107 continue
85- path = os .path .join (arg , name )
108+ path = os .path .join (dir_path , name )
109+
86110 if self .fscache .isdir (path ):
87- sub_sources = self .expand_dir (path , mod_prefix + name + '.' )
111+ sub_sources = self .find_sources_in_dir_helper (
112+ path , mod_prefix + name + '.' , root_dir , explicit_package_roots
113+ )
88114 if sub_sources :
89115 seen .add (name )
90116 sources .extend (sub_sources )
91117 else :
92- base , suffix = os .path .splitext (name )
93- if base == '__init__' :
118+ stem , suffix = os .path .splitext (name )
119+ if stem == '__init__' :
94120 continue
95- if base not in seen and '.' not in base and suffix in PY_EXTENSIONS :
96- seen .add (base )
97- src = BuildSource (path , mod_prefix + base , None , base_dir )
121+ if stem not in seen and '.' not in stem and suffix in PY_EXTENSIONS :
122+ seen .add (stem )
123+ src = BuildSource (path , mod_prefix + stem , None , root_dir )
98124 sources .append (src )
125+
99126 return sources
100127
101- def crawl_up (self , arg : str ) -> Tuple [str , str ]:
128+ def crawl_up (self , path : str ) -> Tuple [str , str ]:
102129 """Given a .py[i] filename, return module and base directory
103130
104131 We crawl up the path until we find a directory without
105132 __init__.py[i], or until we run out of path components.
106133 """
107- dir , mod = os .path .split (arg )
108- mod = strip_py (mod ) or mod
109- base , base_dir = self .crawl_up_dir (dir )
110- if mod == '__init__' or not mod :
111- mod = base
134+ parent , filename = os .path .split (path )
135+ module_name = strip_py (filename ) or os . path . basename ( filename )
136+ module_prefix , base_dir = self .crawl_up_dir (parent )
137+ if module_name == '__init__' or not module_name :
138+ module = module_prefix
112139 else :
113- mod = module_join (base , mod )
140+ module = module_join (module_prefix , module_name )
114141
115- return mod , base_dir
142+ return module , base_dir
116143
117144 def crawl_up_dir (self , dir : str ) -> Tuple [str , str ]:
118145 """Given a directory name, return the corresponding module name and base directory
@@ -124,25 +151,24 @@ def crawl_up_dir(self, dir: str) -> Tuple[str, str]:
124151
125152 parent_dir , base = os .path .split (dir )
126153 if not dir or not self .get_init_file (dir ) or not base :
127- res = ''
154+ module = ''
128155 base_dir = dir or '.'
129156 else :
130157 # Ensure that base is a valid python module name
131158 if base .endswith ('-stubs' ):
132159 base = base [:- 6 ] # PEP-561 stub-only directory
133160 if not base .isidentifier ():
134161 raise InvalidSourceList ('{} is not a valid Python package name' .format (base ))
135- parent , base_dir = self .crawl_up_dir (parent_dir )
136- res = module_join (parent , base )
162+ parent_module , base_dir = self .crawl_up_dir (parent_dir )
163+ module = module_join (parent_module , base )
137164
138- self .package_cache [dir ] = res , base_dir
139- return res , base_dir
165+ self .package_cache [dir ] = module , base_dir
166+ return module , base_dir
140167
141168 def get_init_file (self , dir : str ) -> Optional [str ]:
142169 """Check whether a directory contains a file named __init__.py[i].
143170
144- If so, return the file's name (with dir prefixed). If not, return
145- None.
171+ If so, return the file's name (with dir prefixed). If not, return None.
146172
147173 This prefers .pyi over .py (because of the ordering of PY_EXTENSIONS).
148174 """
0 commit comments