diff --git a/plugins/python/parser/pyparser/pybuiltin.py b/plugins/python/parser/pyparser/pybuiltin.py index c16f7cae8..9031c9b5b 100644 --- a/plugins/python/parser/pyparser/pybuiltin.py +++ b/plugins/python/parser/pyparser/pybuiltin.py @@ -2,6 +2,7 @@ import os import importlib.util import traceback +import sysconfig from jedi.api.classes import Name from parserlog import log, bcolors from parserconfig import ParserConfig @@ -9,27 +10,36 @@ class PYBuiltin: builtin = {} + @staticmethod + def __searchDirectory(directory: str): + for root, dirs, files in os.walk(directory): + for file in files: + p = os.path.join(root, file) + ext = os.path.splitext(p)[1] + + if ext and ext.lower() == '.py': + PYBuiltin.builtin[p] = True + @staticmethod def findBuiltins(config: ParserConfig): try: + # Consider all Python modules in the stdlib directory builtin + sysconfig_paths = sysconfig.get_paths() + if "stdlib" in sysconfig_paths: + PYBuiltin.__searchDirectory(sysconfig_paths["stdlib"]) + + # Locate module paths via ModuleSpec + # However, this can return "frozen" and "built-in" as module origin and not the actual file # Note: Python 3.10+ required stdlib_modules = sys.stdlib_module_names - for e in stdlib_modules: spec = importlib.util.find_spec(e) - if spec and spec.origin: + if spec and spec.origin and spec.origin != "frozen" and spec.origin != "built-in": PYBuiltin.builtin[spec.origin] = True if spec and spec.submodule_search_locations: for submodule_dir in spec.submodule_search_locations: - for root, dirs, files in os.walk(submodule_dir): - for file in files: - p = os.path.join(root, file) - ext = os.path.splitext(p)[1] - - if ext and ext.lower() == '.py': - PYBuiltin.builtin[p] = True - + PYBuiltin.__searchDirectory(submodule_dir) except: log(f"{bcolors.FAIL}Failed to find Python builtins!") if config.stack_trace: diff --git a/plugins/python/test/src/pythonparsertest.cpp b/plugins/python/test/src/pythonparsertest.cpp index fcf36b223..c656e4b29 100644 --- a/plugins/python/test/src/pythonparsertest.cpp +++ b/plugins/python/test/src/pythonparsertest.cpp @@ -408,6 +408,47 @@ TEST_F(PythonParserTest, ImportModule) EXPECT_EQ(pyname.is_import, true); } +TEST_F(PythonParserTest, BuiltinVariable) +{ + model::PYName pyname; + + pyname = queryFile("imports.py", + (odb::query::line_start == 2 && + odb::query::value == "import os")); + + EXPECT_EQ(pyname.is_builtin, true); + + pyname = queryFile("imports.py", + (odb::query::line_start == 6 && + odb::query::value == "print")); + + EXPECT_EQ(pyname.is_builtin, true); + + pyname = queryFile("imports.py", + (odb::query::line_start == 12 && + odb::query::value == "getpid")); + + EXPECT_EQ(pyname.is_builtin, true); + + pyname = queryFile("functions.py", + (odb::query::line_start == 85 && + odb::query::value == "str")); + + EXPECT_EQ(pyname.is_builtin, true); + + pyname = queryFile("functions.py", + (odb::query::line_start == 85 && + odb::query::value == "List")); + + EXPECT_EQ(pyname.is_builtin, true); + + pyname = queryFile("functions.py", + (odb::query::line_start == 98 && + odb::query::value == "range")); + + EXPECT_EQ(pyname.is_builtin, true); +} + TEST_F(PythonParserTest, ReferenceID) { model::PYName pyname;