@@ -179,21 +179,6 @@ private predicate legalDottedName(string name) {
179179bindingset [ name]
180180private predicate legalShortName ( string name ) { name .regexpMatch ( "(\\p{L}|_)(\\p{L}|\\d|_)*" ) }
181181
182- /**
183- * Holds if `f` is potentially a source package.
184- * Does it have an __init__.py file (or --respect-init=False for Python 2) and is it within the source archive?
185- */
186- private predicate isPotentialSourcePackage ( Folder f ) {
187- f .getRelativePath ( ) != "" and
188- isPotentialPackage ( f )
189- }
190-
191- private predicate isPotentialPackage ( Folder f ) {
192- exists ( f .getFile ( "__init__.py" ) )
193- or
194- py_flags_versioned ( "options.respect_init" , "False" , _) and major_version ( ) = 2 and exists ( f )
195- }
196-
197182private string moduleNameFromBase ( Container file ) {
198183 // We used to also require `isPotentialPackage(f)` to hold in this case,
199184 // but we saw modules not getting resolved because their folder did not
@@ -236,31 +221,114 @@ private predicate transitively_imported_from_entry_point(File file) {
236221 )
237222}
238223
224+ /**
225+ * Holds if the folder `f` is a regular Python package,
226+ * containing an `__init__.py` file.
227+ */
228+ private predicate isRegularPackage ( Folder f , string name ) {
229+ legalShortName ( name ) and
230+ name = f .getStem ( ) and
231+ exists ( f .getFile ( "__init__.py" ) )
232+ }
233+
234+ /** Gets the name of a module imported in package `c`. */
235+ private string moduleImportedInPackage ( Container c ) {
236+ legalShortName ( result ) and
237+ // it has to be imported in this folder
238+ result =
239+ any ( ImportExpr i | i .getLocation ( ) .getFile ( ) .getParent ( ) = c )
240+ .getName ( )
241+ // strip everything after the first `.`
242+ .regexpReplaceAll ( "\\..*" , "" ) and
243+ result != ""
244+ }
245+
246+ /** Holds if the file `f` could be resolved to a module named `name`. */
247+ private predicate isPotentialModuleFile ( File file , string name ) {
248+ legalShortName ( name ) and
249+ name = file .getStem ( ) and
250+ file .getExtension ( ) = [ "py" , "pyc" , "so" , "pyd" ] and
251+ // it has to be imported in this folder
252+ name = moduleImportedInPackage ( file .getParent ( ) )
253+ }
254+
255+ /**
256+ * Holds if the folder `f` is a namespace package named `name`.
257+ *
258+ * See https://peps.python.org/pep-0420/#specification
259+ * for details on namespace packages.
260+ */
261+ private predicate isNameSpacePackage ( Folder f , string name ) {
262+ legalShortName ( name ) and
263+ name = f .getStem ( ) and
264+ not isRegularPackage ( f , name ) and
265+ // it has to be imported in a file
266+ // either in this folder or next to this folder
267+ name = moduleImportedInPackage ( [ f , f .getParent ( ) ] ) and
268+ // no sibling regular package
269+ // and no sibling module
270+ not exists ( Folder sibling | sibling .getParent ( ) = f .getParent ( ) |
271+ isRegularPackage ( sibling .getFolder ( name ) , name )
272+ or
273+ isPotentialModuleFile ( sibling .getAFile ( ) , name )
274+ )
275+ }
276+
277+ /**
278+ * Holds if the folder `f` is a package (either a regular package
279+ * or a namespace package) named `name`.
280+ */
281+ private predicate isPackage ( Folder f , string name ) {
282+ isRegularPackage ( f , name )
283+ or
284+ isNameSpacePackage ( f , name )
285+ }
286+
287+ /**
288+ * Holds if the file `f` is a module named `name`.
289+ */
290+ private predicate isModuleFile ( File file , string name ) {
291+ isPotentialModuleFile ( file , name ) and
292+ not isPackage ( file .getParent ( ) , _)
293+ }
294+
295+ /**
296+ * Holds if the folder `f` is a package named `name`
297+ * and does reside inside another package.
298+ */
299+ private predicate isOutermostPackage ( Folder f , string name ) {
300+ isPackage ( f , name ) and
301+ not isPackage ( f .getParent ( ) , _)
302+ }
303+
304+ /** Gets the name of the module that `c` resolves to, if any. */
239305cached
240- string moduleNameFromFile ( Container file ) {
306+ string moduleNameFromFile ( Container c ) {
307+ // package
308+ isOutermostPackage ( c , result )
309+ or
310+ // module
311+ isModuleFile ( c , result )
312+ or
241313 Stages:: AST:: ref ( ) and
242314 exists ( string basename |
243- basename = moduleNameFromBase ( file ) and
315+ basename = moduleNameFromBase ( c ) and
244316 legalShortName ( basename )
245317 |
246- result = moduleNameFromFile ( file .getParent ( ) ) + "." + basename
318+ // recursive case
319+ result = moduleNameFromFile ( c .getParent ( ) ) + "." + basename
247320 or
248321 // If `file` is a transitive import of a file that's executed directly, we allow references
249322 // to it by its `basename`.
250- transitively_imported_from_entry_point ( file ) and
323+ transitively_imported_from_entry_point ( c ) and
251324 result = basename
252325 )
253326 or
254- isPotentialSourcePackage ( file ) and
255- result = file .getStem ( ) and
256- (
257- not isPotentialSourcePackage ( file .getParent ( ) ) or
258- not legalShortName ( file .getParent ( ) .getBaseName ( ) )
259- )
260- or
261- result = file .getStem ( ) and file .getParent ( ) = file .getImportRoot ( )
327+ //
328+ // standard library
329+ result = c .getStem ( ) and c .getParent ( ) = c .getImportRoot ( )
262330 or
263- result = file .getStem ( ) and isStubRoot ( file .getParent ( ) )
331+ result = c .getStem ( ) and isStubRoot ( c .getParent ( ) )
264332}
265333
266334private predicate isStubRoot ( Folder f ) {
0 commit comments