From f3f442a6e71cc0fa29224eb25d570af4dca75959 Mon Sep 17 00:00:00 2001 From: Zhey Date: Fri, 13 Mar 2026 11:18:16 +0100 Subject: [PATCH 1/5] feat(parsers): add Vue and Svelte SFC support Adds .vue and .svelte indexing by extracting script blocks and parsing them with JS/TS parsers, including line remapping for original files. Also wires extension routing, pre-scan symbol collection, package language enums/docs, and parser coverage tests. Closes #709 --- README.md | 5 +- README.zh-CN.md | 5 +- docs/MCP_TOOLS.md | 2 +- scripts/test_all_parsers.py | 25 ++- src/codegraphcontext/tool_definitions.py | 2 +- src/codegraphcontext/tools/graph_builder.py | 29 ++- .../tools/languages/sfc_common.py | 175 ++++++++++++++++++ .../tools/languages/svelte.py | 15 ++ src/codegraphcontext/tools/languages/vue.py | 15 ++ .../tools/package_resolver.py | 2 + .../utils/tree_sitter_manager.py | 4 + tests/unit/parsers/test_sfc_parsers.py | 89 +++++++++ 12 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 src/codegraphcontext/tools/languages/sfc_common.py create mode 100644 src/codegraphcontext/tools/languages/svelte.py create mode 100644 src/codegraphcontext/tools/languages/vue.py create mode 100644 tests/unit/parsers/test_sfc_parsers.py diff --git a/README.md b/README.md index 07abf4c8..47117cc3 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra - **Live File Watching:** Watch directories for changes and automatically update the graph in real-time (`cgc watch`). - **Interactive Setup:** A user-friendly command-line wizard for easy setup. - **Dual Mode:** Works as a standalone **CLI toolkit** for developers and as an **MCP server** for AI agents. -- **Multi-Language Support:** Full support for 14 programming languages. +- **Multi-Language Support:** Full support for 16 programming languages. - **Flexible Database Backend:** KùzuDB (default, zero-config for all platforms), FalkorDB Lite (Unix-only), FalkorDB Remote, or Neo4j (all platforms via Docker/native). --- @@ -142,7 +142,8 @@ CodeGraphContext provides comprehensive parsing and analysis for the following l | ☕ | **Java** | 🏗️ | **C / C++** | #️⃣ | **C#** | | 🐹 | **Go** | 🦀 | **Rust** | 💎 | **Ruby** | | 🐘 | **PHP** | 🍎 | **Swift** | 🎨 | **Kotlin** | -| 🎯 | **Dart** | 🐪 | **Perl** | | | +| 🎯 | **Dart** | 🐪 | **Perl** | 🧩 | **Vue** | +| 🔥 | **Svelte** | | | | | Each language parser extracts functions, classes, methods, parameters, inheritance relationships, function calls, and imports to build a comprehensive code graph. diff --git a/README.zh-CN.md b/README.zh-CN.md index 917ebee1..819d4075 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -127,7 +127,7 @@ - **实时文件监控:** 监控目录更改并实时自动更新代码图 (`cgc watch`)。 - **交互式设置:** 用户友好的命令行向导,轻松完成设置。 - **双重模式:** 既可以作为开发者的独立 **CLI 工具包**,也可以作为 AI 智能体的 **MCP 服务端**。 -- **多语言支持:** 全面支持 14 种编程语言。 +- **多语言支持:** 全面支持 16 种编程语言。 - **灵活的数据库后端:** KùzuDB(默认,所有平台零配置)、FalkorDB Lite(仅限 Unix)、FalkorDB Remote 或 Neo4j(通过 Docker/原生支持所有平台)。 --- @@ -142,7 +142,8 @@ CodeGraphContext 为以下语言提供全面的解析和分析: | ☕ | **Java** | 🏗️ | **C / C++** | #️⃣ | **C#** | | 🐹 | **Go** | 🦀 | **Rust** | 💎 | **Ruby** | | 🐘 | **PHP** | 🍎 | **Swift** | 🎨 | **Kotlin** | -| 🎯 | **Dart** | 🐪 | **Perl** | | | +| 🎯 | **Dart** | 🐪 | **Perl** | 🧩 | **Vue** | +| 🔥 | **Svelte** | | | | | 每个语言解析器都会提取函数、类、方法、参数、继承关系、函数调用和导入,以构建全面的代码图。 diff --git a/docs/MCP_TOOLS.md b/docs/MCP_TOOLS.md index bb86dc42..dd197a4c 100644 --- a/docs/MCP_TOOLS.md +++ b/docs/MCP_TOOLS.md @@ -24,7 +24,7 @@ Performs a one-time scan of a local folder to add its code to the graph. Ideal f Add an external package to the graph by discovering its location. - **Args**: `package_name` (string), `language` (string), `is_dependency` (boolean) - **Returns**: Job ID -- **Supported Languages**: python, javascript, typescript, java, c, go, ruby, php, cpp +- **Supported Languages**: python, javascript, typescript, java, c, go, ruby, php, cpp, vue, svelte ### `list_indexed_repositories` List all repositories currently indexed in the graph. diff --git a/scripts/test_all_parsers.py b/scripts/test_all_parsers.py index 0bad0de9..0f15ddc6 100644 --- a/scripts/test_all_parsers.py +++ b/scripts/test_all_parsers.py @@ -112,6 +112,27 @@ class MyClass { print "world\\n"; } 1; +''', + 'vue': ''' + + +''', + 'svelte': ''' + + +

Hello

''' } @@ -127,7 +148,9 @@ class MyClass { 'ruby': '.rb', 'c_sharp': '.cs', 'dart': '.dart', - 'perl': '.pl' + 'perl': '.pl', + 'vue': '.vue', + 'svelte': '.svelte' } results = {} diff --git a/src/codegraphcontext/tool_definitions.py b/src/codegraphcontext/tool_definitions.py index 231fa393..a2cb1ad6 100644 --- a/src/codegraphcontext/tool_definitions.py +++ b/src/codegraphcontext/tool_definitions.py @@ -74,7 +74,7 @@ "type": "object", "properties": { "package_name": {"type": "string", "description": "Name of the package to add (e.g., 'requests', 'express', 'moment', 'lodash')."}, - "language": {"type": "string", "description": "The programming language of the package.", "enum": ["python", "javascript", "typescript", "java", "c", "go", "ruby", "php","cpp"]}, + "language": {"type": "string", "description": "The programming language of the package.", "enum": ["python", "javascript", "typescript", "java", "c", "go", "ruby", "php", "cpp", "vue", "svelte"]}, "is_dependency": {"type": "boolean", "description": "Mark as a dependency.", "default": True} }, "required": ["package_name", "language"] diff --git a/src/codegraphcontext/tools/graph_builder.py b/src/codegraphcontext/tools/graph_builder.py index c3e45e81..a44b0ac7 100644 --- a/src/codegraphcontext/tools/graph_builder.py +++ b/src/codegraphcontext/tools/graph_builder.py @@ -22,11 +22,16 @@ class TreeSitterParser: def __init__(self, language_name: str): self.language_name = language_name self.ts_manager = get_tree_sitter_manager() - - # Get the language (cached) and create a new parser for this instance - self.language: Language = self.ts_manager.get_language_safe(language_name) - # In tree-sitter 0.25+, Parser takes language in constructor - self.parser = Parser(self.language) + + # Vue/Svelte parsers extract script blocks and delegate parsing to JS/TS parsers. + # They don't need a dedicated tree-sitter parser initialized here. + self.language: Optional[Language] = None + self.parser: Optional[Parser] = None + if self.language_name not in {'vue', 'svelte'}: + # Get the language (cached) and create a new parser for this instance + self.language = self.ts_manager.get_language_safe(language_name) + # In tree-sitter 0.25+, Parser takes language in constructor + self.parser = Parser(self.language) self.language_specific_parser = None if self.language_name == 'python': @@ -83,6 +88,12 @@ def __init__(self, language_name: str): elif self.language_name == 'elixir': from .languages.elixir import ElixirTreeSitterParser self.language_specific_parser = ElixirTreeSitterParser(self) + elif self.language_name == 'vue': + from .languages.vue import VueTreeSitterParser + self.language_specific_parser = VueTreeSitterParser(self) + elif self.language_name == 'svelte': + from .languages.svelte import SvelteTreeSitterParser + self.language_specific_parser = SvelteTreeSitterParser(self) @@ -132,6 +143,8 @@ def __init__(self, db_manager: DatabaseManager, job_manager: JobManager, loop: a '.pm': TreeSitterParser('perl'), '.ex': TreeSitterParser('elixir'), '.exs': TreeSitterParser('elixir'), + '.vue': TreeSitterParser('vue'), + '.svelte': TreeSitterParser('svelte'), } self.create_schema() @@ -276,6 +289,12 @@ def _pre_scan_for_imports(self, files: list[Path]) -> dict: if '.exs' in files_by_lang: from .languages import elixir as elixir_lang_module imports_map.update(elixir_lang_module.pre_scan_elixir(files_by_lang['.exs'], self.parsers['.exs'])) + if '.vue' in files_by_lang: + from .languages import vue as vue_lang_module + imports_map.update(vue_lang_module.pre_scan_vue(files_by_lang['.vue'], self.parsers['.vue'])) + if '.svelte' in files_by_lang: + from .languages import svelte as svelte_lang_module + imports_map.update(svelte_lang_module.pre_scan_svelte(files_by_lang['.svelte'], self.parsers['.svelte'])) return imports_map diff --git a/src/codegraphcontext/tools/languages/sfc_common.py b/src/codegraphcontext/tools/languages/sfc_common.py new file mode 100644 index 00000000..4c2d88c7 --- /dev/null +++ b/src/codegraphcontext/tools/languages/sfc_common.py @@ -0,0 +1,175 @@ +from pathlib import Path +from typing import Any, Dict, Type +import re + +from codegraphcontext.utils.debug_log import warning_logger +from codegraphcontext.utils.tree_sitter_manager import get_tree_sitter_manager + +from .javascript import JavascriptTreeSitterParser +from .typescript import TypescriptTreeSitterParser + + +_SCRIPT_TAG_RE = re.compile( + r"[^>]*)>(?P.*?)", + re.IGNORECASE | re.DOTALL, +) +_LANG_TS_RE = re.compile( + r"\blang\s*=\s*(?:\"|')?(?:ts|tsx|typescript)(?:\"|')?\b", + re.IGNORECASE, +) +_LINE_FIELDS = {"line_number", "end_line", "function_line_number"} + + +class _ParserWrapper: + """Lightweight wrapper to build language parsers outside GraphBuilder.""" + + def __init__(self, language_name: str): + manager = get_tree_sitter_manager() + self.language_name = language_name + self.language = manager.get_language_safe(language_name) + self.parser = manager.create_parser(language_name) + + +def _extract_script_blocks(source_code: str) -> list[dict[str, Any]]: + """Extract +""" + vue_file = temp_test_dir / "App.vue" + vue_file.write_text(source) + + result = vue_parser.parse(vue_file) + + functions = {fn["name"]: fn for fn in result["functions"]} + assert "hello" in functions + # Ensure line numbers point to the original .vue file, not the extracted script snippet. + assert functions["hello"]["line_number"] == 4 + assert any(imp.get("source") == "./helpers" for imp in result["imports"]) + assert result["lang"] == "vue" + + +def test_svelte_parser_extracts_javascript_block(svelte_parser, temp_test_dir): + source = """ + +

Hello

+""" + svelte_file = temp_test_dir / "Component.svelte" + svelte_file.write_text(source) + + result = svelte_parser.parse(svelte_file) + + functions = {fn["name"]: fn for fn in result["functions"]} + assert "hello" in functions + assert functions["hello"]["line_number"] == 3 + assert any(imp.get("source") == "svelte" for imp in result["imports"]) + assert result["lang"] == "svelte" + + +def test_sfc_pre_scan_collects_symbols(temp_test_dir): + vue_file = temp_test_dir / "Widget.vue" + vue_file.write_text( + """\n""" + ) + + svelte_file = temp_test_dir / "Widget.svelte" + svelte_file.write_text( + """\n""" + ) + + wrapper = MagicMock() + wrapper.language_name = "vue" + + vue_map = pre_scan_vue([vue_file], wrapper) + svelte_map = pre_scan_svelte([svelte_file], wrapper) + + assert "Widget" in vue_map + assert str(vue_file.resolve()) in vue_map["Widget"] + assert "makeWidget" in vue_map + assert str(vue_file.resolve()) in vue_map["makeWidget"] + + assert "makeGreeting" in svelte_map + assert str(svelte_file.resolve()) in svelte_map["makeGreeting"] From d801d6b62739b1c96282d344790e280e1348dc18 Mon Sep 17 00:00:00 2001 From: Zhey Date: Fri, 13 Mar 2026 11:51:36 +0100 Subject: [PATCH 2/5] feat(website): add Vercel Analytics --- website/package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++ website/package.json | 1 + website/src/main.tsx | 2 ++ 3 files changed, 46 insertions(+) diff --git a/website/package-lock.json b/website/package-lock.json index b8b35d3e..93b0d957 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -43,6 +43,7 @@ "@tanstack/react-query": "^5.83.0", "@types/aos": "^3.0.7", "@types/three": "^0.183.1", + "@vercel/analytics": "^2.0.1", "aos": "^2.3.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -3785,6 +3786,48 @@ "react": ">= 16.8.0" } }, + "node_modules/@vercel/analytics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz", + "integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==", + "license": "MIT", + "peerDependencies": { + "@remix-run/react": "^2", + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, "node_modules/@vercel/build-utils": { "version": "13.6.3", "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.6.3.tgz", diff --git a/website/package.json b/website/package.json index 20e7e1aa..30121d5f 100644 --- a/website/package.json +++ b/website/package.json @@ -46,6 +46,7 @@ "@tanstack/react-query": "^5.83.0", "@types/aos": "^3.0.7", "@types/three": "^0.183.1", + "@vercel/analytics": "^2.0.1", "aos": "^2.3.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/website/src/main.tsx b/website/src/main.tsx index 9f843652..c88cb20d 100644 --- a/website/src/main.tsx +++ b/website/src/main.tsx @@ -1,5 +1,6 @@ import React from "react"; import { createRoot } from "react-dom/client"; +import { Analytics } from "@vercel/analytics/react"; import App from "./App.tsx"; import "./index.css"; @@ -8,5 +9,6 @@ import "aos/dist/aos.css"; createRoot(document.getElementById("root")!).render( + ); From 45accad316de46bdcaa956edf990f3356f623aba Mon Sep 17 00:00:00 2001 From: Zhey Date: Sat, 4 Apr 2026 11:32:59 +0200 Subject: [PATCH 3/5] ci: fix manylinux PyInstaller by using shared Python via uv --- .github/workflows/build.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3481810c..64bdc48e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,9 +57,14 @@ jobs: /bin/bash -c " set -e cd /src - /opt/python/cp312-cp312/bin/python -m pip install --upgrade pip - /opt/python/cp312-cp312/bin/pip install . pyinstaller - /opt/python/cp312-cp312/bin/pyinstaller cgc.spec --clean + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH=/root/.local/bin:\$PATH + uv python install 3.12 + uv venv --python 3.12 /tmp/venv + . /tmp/venv/bin/activate + python -m pip install --upgrade pip + pip install . pyinstaller + pyinstaller cgc.spec --clean " sudo chown -R $USER:$USER dist build From 0497db3ceca9cd6ac61b64cac83a887b2d7aec10 Mon Sep 17 00:00:00 2001 From: Zhey Date: Sat, 4 Apr 2026 11:56:40 +0200 Subject: [PATCH 4/5] ci: add --seed to uv venv to bootstrap pip inside manylinux container --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64bdc48e..5e226434 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: curl -LsSf https://astral.sh/uv/install.sh | sh export PATH=/root/.local/bin:\$PATH uv python install 3.12 - uv venv --python 3.12 /tmp/venv + uv venv --python 3.12 --seed /tmp/venv . /tmp/venv/bin/activate python -m pip install --upgrade pip pip install . pyinstaller From 5917b4f46013bf3c8a37e71c309ebcb9617d638f Mon Sep 17 00:00:00 2001 From: Zhey Date: Sat, 4 Apr 2026 12:07:49 +0200 Subject: [PATCH 5/5] ci: disable pip cache for Linux to fix spurious Post setup-python failure --- .github/workflows/build.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e226434..e4cb9bc2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,14 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python 3.12 + - name: Set up Python 3.12 (Linux) + if: runner.os == 'Linux' + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Set up Python 3.12 (Windows/Mac) + if: runner.os != 'Linux' uses: actions/setup-python@v5 with: python-version: '3.12'