From 3457b9cd80f305b3170e667c1cd3de783b506681 Mon Sep 17 00:00:00 2001 From: daleardi Date: Tue, 5 May 2026 23:56:51 -0400 Subject: [PATCH] add .luau extension support (Roblox Luau) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `.luau` to CODE_EXTENSIONS and route it through extract_lua (tree-sitter-lua). Roblox first-party code uses .luau; without this, graphify silently skipped 379/479 files on a real Roblox codebase and the resulting graph was dominated by vendored .lua dependencies. tree-sitter-lua doesn't parse Luau type annotations, but it successfully extracts function declarations and call edges from Luau source — verified on a 379-file Roblox codebase (1265 nodes, 1471 edges, 236 communities). A dedicated tree-sitter-luau grammar would be a richer long-term fix; this is the minimal change to make Luau projects work today. Co-Authored-By: Claude Opus 4.7 (1M context) --- graphify/detect.py | 2 +- graphify/extract.py | 1 + tests/fixtures/sample.luau | 35 +++++++++++++++++++++++++++++++++++ tests/test_extract.py | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sample.luau diff --git a/graphify/detect.py b/graphify/detect.py index 50f9e2c5e..918336178 100644 --- a/graphify/detect.py +++ b/graphify/detect.py @@ -18,7 +18,7 @@ class FileType(str, Enum): _MANIFEST_PATH = "graphify-out/manifest.json" -CODE_EXTENSIONS = {'.py', '.ts', '.js', '.jsx', '.tsx', '.mjs', '.ejs', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.toc', '.zig', '.ps1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.dart', '.v', '.sv', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08'} +CODE_EXTENSIONS = {'.py', '.ts', '.js', '.jsx', '.tsx', '.mjs', '.ejs', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.luau', '.toc', '.zig', '.ps1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.dart', '.v', '.sv', '.sql', '.r', '.f', '.F', '.f90', '.F90', '.f95', '.F95', '.f03', '.F03', '.f08', '.F08'} DOC_EXTENSIONS = {'.md', '.mdx', '.txt', '.rst', '.html', '.yaml', '.yml'} PAPER_EXTENSIONS = {'.pdf'} IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'} diff --git a/graphify/extract.py b/graphify/extract.py index 6f5a0e893..4eab8d7bb 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -4087,6 +4087,7 @@ _DISPATCH: dict[str, Any] = { ".php": extract_php, ".swift": extract_swift, ".lua": extract_lua, + ".luau": extract_lua, ".toc": extract_lua, ".zig": extract_zig, ".ps1": extract_powershell, diff --git a/tests/fixtures/sample.luau b/tests/fixtures/sample.luau new file mode 100644 index 000000000..7bce440af --- /dev/null +++ b/tests/fixtures/sample.luau @@ -0,0 +1,35 @@ +-- Luau sample (Roblox): typed Lua superset. +-- tree-sitter-lua doesn't parse the type annotations, but extracts +-- function declarations and call edges fine. + +local Server = {} +Server.__index = Server + +type ServerConfig = { + port: number, + name: string?, +} + +function Server.new(config: ServerConfig): Server + local self = setmetatable({}, Server) + self.port = config.port + self.name = config.name or "default" + return self +end + +function Server:start(): () + print(string.format("listening on :%d", self.port)) +end + +function Server:stop(): () + print("stopped") +end + +local function main() + local s = Server.new({ port = 8080 }) + s:start() +end + +main() + +return Server diff --git a/tests/test_extract.py b/tests/test_extract.py index 06d371bcc..862a209bc 100644 --- a/tests/test_extract.py +++ b/tests/test_extract.py @@ -61,7 +61,7 @@ def test_collect_files_from_dir(): supported = {".py", ".js", ".ts", ".tsx", ".go", ".rs", ".java", ".c", ".cpp", ".cc", ".cxx", ".rb", ".cs", ".kt", ".kts", ".scala", ".php", ".h", ".hpp", - ".swift", ".lua", ".toc", ".zig", ".ps1", ".ex", ".exs", + ".swift", ".lua", ".luau", ".toc", ".zig", ".ps1", ".ex", ".exs", ".m", ".mm"} assert all(f.suffix in supported for f in files) assert len(files) > 0