diff --git a/README.md b/README.md index a5773d9..ef0c866 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,16 @@ ## 安装 -### 从NuGet安装 - ```bash dotnet add package TypeScriptParser ``` -或通过包管理器控制台: - -```powershell -Install-Package TypeScriptParser -``` +## 文档 -也可以访问 [NuGet包页面](https://www.nuget.org/packages/TypeScriptParser) 查看更多安装选项。 +- [使用指南](docs/USAGE_GUIDE.md) - 快速开始和常用示例 +- [API参考](docs/API_REFERENCE.md) - 完整的API文档 -## 快速开始 +## 构建项目 ```bash # 1. 构建native库 @@ -36,6 +31,10 @@ dotnet test --configuration Release # 4. 打包NuGet包 dotnet pack -c Release -o ./artifacts + +# 5. 测试打包 +dotnet package add TypeScriptParser --project TypeScriptParser.TestPackage/ --version 0.0.1-dev +dotnet test TypeScriptParser.TestPackage ``` ## 项目结构 @@ -44,15 +43,7 @@ dotnet pack -c Release -o ./artifacts - [`TypeScriptParser.Native/`](TypeScriptParser.Native/) - 跨平台Native库包 - [`TypeScriptParser.Tests/`](TypeScriptParser.Tests/) - 单元测试 -## 开发指南 - -### Debug模式构建 -```bash -dotnet build -c Debug -dotnet test --configuration Debug -``` - -### 支持平台 +## 支持平台 - Linux x64 - macOS ARM64 - Windows x64 diff --git a/TypeScriptParser.TestPackage/TypeScriptParser.TestPackage.csproj b/TypeScriptParser.TestPackage/TypeScriptParser.TestPackage.csproj index b8d1e63..1735d3c 100644 --- a/TypeScriptParser.TestPackage/TypeScriptParser.TestPackage.csproj +++ b/TypeScriptParser.TestPackage/TypeScriptParser.TestPackage.csproj @@ -5,6 +5,8 @@ enable enable false + + $(NoWarn);CS1591 diff --git a/TypeScriptParser.TestPackage/UnitTest1.cs b/TypeScriptParser.TestPackage/UnitTest1.cs index 8a3f826..4b738b9 100644 --- a/TypeScriptParser.TestPackage/UnitTest1.cs +++ b/TypeScriptParser.TestPackage/UnitTest1.cs @@ -1,4 +1,5 @@ using Xunit; +using TypeScriptParser; namespace TypeScriptParser.TestPackage; @@ -7,14 +8,16 @@ public class UnitTest1 [Fact] public void Can_Create_Parser() { - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); - Assert.True(parser.IsAvailable); + using var parser = new Parser(); + // 通过能否成功解析来验证Parser可用性 + var tree = parser.ParseString("const x = 1;"); + Assert.NotNull(tree); } [Fact] public void Can_Parse_Code() { - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); var tree = parser.ParseString("const x = 1;"); Assert.Equal("program", tree.root_node().type()); } diff --git a/TypeScriptParser.Tests/TypeScriptAnalyzer.cs b/TypeScriptParser.Tests/TypeScriptAnalyzer.cs index 153b9f8..de80345 100644 --- a/TypeScriptParser.Tests/TypeScriptAnalyzer.cs +++ b/TypeScriptParser.Tests/TypeScriptAnalyzer.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; -using GitHub.TreeSitter; +using TypeScriptParser.TreeSitter; using System.Runtime.InteropServices; +using TypeScriptParser; -namespace TreeSitter.TypeScript +namespace TypeScriptParser.Tests { // 参数信息结构 public class Parameter @@ -28,13 +29,13 @@ public class ExportedFunction // TypeScript分析器模块 public class TypeScriptAnalyzer : IDisposable { - private readonly TypeScriptParser parser; + private readonly Parser parser; private readonly List exportedFunctions = []; private bool disposed = false; public TypeScriptAnalyzer() { - parser = new TypeScriptParser(); + parser = new Parser(); } public List AnalyzeFile(string fileContent) @@ -49,7 +50,7 @@ public List AnalyzeFile(string fileContent) return exportedFunctions; } - using var cursor = parser.CreateCursor(tree); + using var cursor = new TSCursor(tree.root_node(), tree.language()); TraverseForExports(cursor, fileContent); return [.. exportedFunctions]; diff --git a/TypeScriptParser.Tests/TypeScriptParser.Tests.csproj b/TypeScriptParser.Tests/TypeScriptParser.Tests.csproj index fc13e17..818c315 100644 --- a/TypeScriptParser.Tests/TypeScriptParser.Tests.csproj +++ b/TypeScriptParser.Tests/TypeScriptParser.Tests.csproj @@ -6,6 +6,8 @@ disable false true + + $(NoWarn);CS1591 diff --git a/TypeScriptParser.Tests/tests/ExportExtractorTests.cs b/TypeScriptParser.Tests/tests/ExportExtractorTests.cs new file mode 100644 index 0000000..6609e6d --- /dev/null +++ b/TypeScriptParser.Tests/tests/ExportExtractorTests.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using TypeScriptParser; +using TypeScriptParser.TreeSitter; + +namespace TypeScriptParser.Tests +{ + public class ExportExtractorTests + { + [Fact] + public void ExtractExportFunctions_SimpleExport_ShouldReturnFunctionNames() + { + // Arrange + string code = @" +export function add(a: number, b: number): number { + return a + b; +} + +export function multiply(x: number, y: number): number { + return x * y; +}"; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Equal(2, exportedFunctions.Count); + Assert.Contains("add", exportedFunctions); + Assert.Contains("multiply", exportedFunctions); + } + + [Fact] + public void ExtractExportFunctions_MixedExports_ShouldOnlyReturnFunctions() + { + // Arrange + string code = @" +export function add(a: number, b: number): number { + return a + b; +} + +function helper() { + return 'not exported'; +} + +export const PI = 3.14; + +export function multiply(x: number, y: number): number { + return x * y; +}"; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Equal(2, exportedFunctions.Count); + Assert.Contains("add", exportedFunctions); + Assert.Contains("multiply", exportedFunctions); + Assert.DoesNotContain("helper", exportedFunctions); + Assert.DoesNotContain("PI", exportedFunctions); + } + + [Fact] + public void ExtractExportFunctions_NoExports_ShouldReturnEmpty() + { + // Arrange + string code = @" +function helper() { + return 'not exported'; +} + +const PI = 3.14;"; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Empty(exportedFunctions); + } + + [Fact] + public void ExtractExportFunctions_EmptyCode_ShouldReturnEmpty() + { + // Arrange + string code = ""; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Empty(exportedFunctions); + } + + [Fact] + public void ExtractExportFunctions_ComplexExports_ShouldExtractAllFunctions() + { + // Arrange + string code = @" +export function calculate(x: number): number { + return x * 2; +} + +export function processData(data: any[]): any[] { + return data.filter(item => item != null); +} + +export function validateInput(input: string): boolean { + return input && input.length > 0; +} + +// 非函数导出 +export const config = { api: 'https://api.example.com' }; +export let counter = 0; + +// 非导出函数 +function internalHelper() { + return 'internal'; +}"; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Equal(3, exportedFunctions.Count); + Assert.Contains("calculate", exportedFunctions); + Assert.Contains("processData", exportedFunctions); + Assert.Contains("validateInput", exportedFunctions); + Assert.DoesNotContain("internalHelper", exportedFunctions); + } + + [Fact] + public void ExtractExportFunctions_AsyncFunctions_ShouldExtractAsyncFunctions() + { + // Arrange + string code = @" +export async function fetchData(url: string): Promise { + const response = await fetch(url); + return response.json(); +} + +export function syncFunction(): string { + return 'sync'; +}"; + + // Act + var exportedFunctions = ExtractExportFunctions(code); + + // Assert + Assert.Equal(2, exportedFunctions.Count); + Assert.Contains("fetchData", exportedFunctions); + Assert.Contains("syncFunction", exportedFunctions); + } + + /// + /// 提取TypeScript代码中所有导出的函数名称 + /// + /// TypeScript源代码 + /// 导出函数名称列表 + private List ExtractExportFunctions(string code) + { + var exportedFunctions = new List(); + + using var parser = new Parser(); + using var tree = parser.ParseString(code); + + // 创建查询匹配 export 函数 + var query = tree.language().query_new(@" + (export_statement + declaration: (function_declaration + name: (identifier) @func_name)) + ", out var offset, out var error); + + if (query != null && error == TSQueryError.TSQueryErrorNone) + { + using var cursor = new TSQueryCursor(); + cursor.exec(query, tree.root_node()); + + while (cursor.next_match(out var match, out var captures)) + { + foreach (var capture in captures) + { + var funcName = capture.node.text(code); + if (!string.IsNullOrWhiteSpace(funcName)) + { + exportedFunctions.Add(funcName); + } + } + } + + query.Dispose(); + } + + return exportedFunctions; + } + } +} \ No newline at end of file diff --git a/TypeScriptParser.Tests/tests/TypeScriptAnalyzerTests.cs b/TypeScriptParser.Tests/tests/TypeScriptAnalyzerTests.cs index 14b4371..e0d52c4 100644 --- a/TypeScriptParser.Tests/tests/TypeScriptAnalyzerTests.cs +++ b/TypeScriptParser.Tests/tests/TypeScriptAnalyzerTests.cs @@ -1,7 +1,7 @@ using System; using System.IO; using Xunit; -using TreeSitter.TypeScript; +using TypeScriptParser.Tests; namespace TypeScriptParser.Tests { diff --git a/TypeScriptParser.Tests/tests/TypeScriptParserTests.cs b/TypeScriptParser.Tests/tests/TypeScriptParserTests.cs index 22e9147..027be78 100644 --- a/TypeScriptParser.Tests/tests/TypeScriptParserTests.cs +++ b/TypeScriptParser.Tests/tests/TypeScriptParserTests.cs @@ -1,6 +1,7 @@ using System; using Xunit; -using TreeSitter.TypeScript; +using TypeScriptParser; +using TypeScriptParser.TreeSitter; namespace TypeScriptParser.Tests { @@ -10,19 +11,20 @@ public class TypeScriptParserTests public void TypeScriptParser_Constructor_ShouldCreateValidInstance() { // Arrange & Act - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); // Assert Assert.NotNull(parser); - Assert.NotNull(parser.Language); - Assert.True(parser.IsAvailable); + // 通过能否成功解析来验证Parser可用性 + var tree = parser.ParseString("const x = 1;"); + Assert.NotNull(tree); } [Fact] public void ParseString_SimpleTypeScriptCode_ShouldReturnValidTree() { // Arrange - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); string sourceCode = "const x: number = 42;"; // Act @@ -36,7 +38,7 @@ public void ParseString_SimpleTypeScriptCode_ShouldReturnValidTree() public void ParseString_EmptyString_ShouldReturnValidTree() { // Arrange - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); string sourceCode = ""; // Act @@ -50,46 +52,48 @@ public void ParseString_EmptyString_ShouldReturnValidTree() public void CreateCursor_WithValidTree_ShouldReturnCursor() { // Arrange - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); string sourceCode = "function hello() { return 'world'; }"; using var tree = parser.ParseString(sourceCode); // Act - using var cursor = parser.CreateCursor(tree); + using var cursor = new TSCursor(tree.root_node(), tree.language()); // Assert Assert.NotNull(cursor); } [Fact] - public void CreateCursor_WithNullTree_ShouldThrowArgumentNullException() + public void CreateCursor_WithValidNode_ShouldWork() { // Arrange - using var parser = new TreeSitter.TypeScript.TypeScriptParser(); + using var parser = new Parser(); + var tree = parser.ParseString("const x = 1;"); - // Act & Assert - Assert.Throws(() => parser.CreateCursor(null)); + // Act & Assert - 测试正常创建游标 + using var cursor = new TSCursor(tree.root_node(), tree.language()); + Assert.NotNull(cursor); + Assert.NotNull(cursor.current_symbol()); } [Fact] - public void Dispose_AfterDispose_IsAvailableShouldBeFalse() + public void Dispose_AfterDispose_ParseStringShouldThrow() { // Arrange - var parser = new TreeSitter.TypeScript.TypeScriptParser(); - Assert.True(parser.IsAvailable); + var parser = new Parser(); // Act parser.Dispose(); // Assert - Assert.False(parser.IsAvailable); + Assert.Throws(() => parser.ParseString("const x = 1;")); } [Fact] public void ParseString_AfterDispose_ShouldThrowObjectDisposedException() { // Arrange - var parser = new TreeSitter.TypeScript.TypeScriptParser(); + var parser = new Parser(); parser.Dispose(); // Act & Assert diff --git a/TypeScriptParser/src/TypeScriptParser.cs b/TypeScriptParser/src/TypeScriptParser.cs index d1a675f..03546db 100644 --- a/TypeScriptParser/src/TypeScriptParser.cs +++ b/TypeScriptParser/src/TypeScriptParser.cs @@ -1,13 +1,13 @@ using System; -using GitHub.TreeSitter; +using TypeScriptParser.TreeSitter; using System.Runtime.InteropServices; -namespace TreeSitter.TypeScript +namespace TypeScriptParser { /// /// TypeScript 解析器模块 - 负责 Tree-sitter TypeScript 语言的初始化和基础解析功能 /// - public class TypeScriptParser : IDisposable + public class Parser : IDisposable { private readonly TSLanguage lang; private TSParser parser; @@ -16,18 +16,13 @@ public class TypeScriptParser : IDisposable [DllImport("tree-sitter-typescript", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr tree_sitter_typescript(); - public TypeScriptParser() + public Parser() { lang = new TSLanguage(tree_sitter_typescript()); parser = new TSParser(); parser.set_language(lang); } - /// - /// 获取 TypeScript 语言对象 - /// - public TSLanguage Language => lang; - /// /// 解析 TypeScript 源代码字符串 /// @@ -36,32 +31,10 @@ public TypeScriptParser() public TSTree ParseString(string sourceCode) { if (disposed) - throw new ObjectDisposedException(nameof(TypeScriptParser)); - + throw new ObjectDisposedException(nameof(Parser)); return parser.parse_string(null, sourceCode); } - /// - /// 创建语法树游标 - /// - /// 语法树 - /// TSCursor 对象 - public TSCursor CreateCursor(TSTree tree) - { - if (disposed) - throw new ObjectDisposedException(nameof(TypeScriptParser)); - - if (tree == null) - throw new ArgumentNullException(nameof(tree)); - - return new TSCursor(tree.root_node(), lang); - } - - /// - /// 检查解析器是否可用 - /// - public bool IsAvailable => !disposed && parser != null && lang != null; - public void Dispose() { Dispose(true); @@ -81,7 +54,7 @@ protected virtual void Dispose(bool disposing) } } - ~TypeScriptParser() + ~Parser() { Dispose(false); } diff --git a/TypeScriptParser/src/binding.cs b/TypeScriptParser/src/binding.cs index 30c7772..4c8b880 100644 --- a/TypeScriptParser/src/binding.cs +++ b/TypeScriptParser/src/binding.cs @@ -11,7 +11,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace GitHub.TreeSitter +namespace TypeScriptParser.TreeSitter { public enum TSInputEncoding { TSInputEncodingUTF8, diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..87e1773 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,262 @@ +# TypeScript Parser API 参考 + +基于 Tree-sitter 的 TypeScript 解析器 .NET 绑定。 + +## 快速开始 + +```csharp +using TypeScriptParser; + +using var parser = new Parser(); +using var tree = parser.ParseString("const x: number = 42;"); +var root = tree.root_node(); +Console.WriteLine(root.type()); // "program" +``` + +## 高级 API + +### Parser + +```csharp +public class Parser : IDisposable +{ + public Parser() + public TSTree ParseString(string sourceCode) + public void Dispose() +} +``` + +**使用:** +```csharp +using var parser = new Parser(); +using var tree = parser.ParseString(code); +``` + +## 底层 API + +### TSTree - 语法树 + +```csharp +public sealed class TSTree : IDisposable +{ + public TSNode root_node() + public TSTree copy() // 多线程使用 + public TSLanguage language() + public void edit(TSInputEdit edit) // 增量解析 +} +``` + +### TSNode - 节点 + +```csharp +public struct TSNode +{ + // 基本信息 + public string type() + public bool is_null() + public bool is_named() + public bool has_error() + + // 位置 + public uint start_offset() + public uint end_offset() + public TSPoint start_point() + public TSPoint end_point() + + // 导航 + public TSNode parent() + public TSNode child(uint index) + public uint child_count() + public TSNode next_sibling() + public TSNode prev_sibling() + + // 字段访问 + public TSNode child_by_field_name(string field_name) + + // 文本 + public string text(string sourceCode) +} +``` + +### TSCursor - 遍历 + +```csharp +public sealed class TSCursor : IDisposable +{ + public TSCursor(TSNode node, TSLanguage lang) + + public TSNode current_node() + public string current_symbol() + public bool goto_first_child() + public bool goto_next_sibling() + public bool goto_parent() +} +``` + +**遍历示例:** +```csharp +using var cursor = new TSCursor(tree.root_node(), tree.language()); +do { + var node = cursor.current_node(); + Console.WriteLine($"{cursor.current_symbol()}: {node.type()}"); +} while (cursor.goto_next_sibling() || cursor.goto_first_child()); +``` + +### TSQuery - 查询 + +```csharp +public sealed class TSQuery : IDisposable +{ + public uint pattern_count() + public uint capture_count() + public void disable_pattern(uint patternIndex) +} + +public sealed class TSQueryCursor : IDisposable +{ + public void exec(TSQuery query, TSNode node) + public bool next_match(out TSQueryMatch match, out TSQueryCapture[] captures) + public void set_match_limit(uint limit) +} +``` + +**查询示例:** +```csharp +var query = language.query_new("(function_declaration name: (identifier) @name)", + out var offset, out var error); +using var queryCursor = new TSQueryCursor(); +queryCursor.exec(query, tree.root_node()); + +while (queryCursor.next_match(out var match, out var captures)) { + foreach (var capture in captures) { + Console.WriteLine($"函数名: {capture.node.text(sourceCode)}"); + } +} +``` + +### TSLanguage - 语言定义 + +```csharp +public sealed class TSLanguage : IDisposable +{ + public string[] symbols + public string[] fields + + public string symbol_name(ushort symbol) + public string field_name_for_id(ushort fieldId) + public TSQuery query_new(string source, out uint error_offset, out TSQueryError error_type) +} +``` + +## 数据结构 + +### TSPoint +```csharp +public struct TSPoint +{ + public uint row; // 行号(从0开始) + public uint column; // 列号(从0开始) +} +``` + +### TSRange +```csharp +public struct TSRange +{ + public TSPoint start_point; + public TSPoint end_point; + public uint start_byte; + public uint end_byte; +} +``` + +### TSQueryCapture +```csharp +public struct TSQueryCapture +{ + public TSNode node; + public uint index; +} +``` + +## 实用示例 + +### 查找所有函数 + +```csharp +var query = language.query_new(@" + (function_declaration + name: (identifier) @func_name + parameters: (formal_parameters) @params) @func +", out var offset, out var error); + +using var cursor = new TSQueryCursor(); +cursor.exec(query, tree.root_node()); + +while (cursor.next_match(out var match, out var captures)) { + var funcNode = captures[0].node; // @func + var nameNode = captures[1].node; // @func_name + + Console.WriteLine($"函数: {nameNode.text(sourceCode)}"); + Console.WriteLine($"位置: 行{funcNode.start_point().row + 1}"); +} +``` + +### 遍历所有变量声明 + +```csharp +using var cursor = new TSCursor(tree.root_node(), tree.language()); + +void TraverseNode() { + var node = cursor.current_node(); + + if (node.type() == "variable_declarator") { + var nameNode = node.child_by_field_name("name"); + if (!nameNode.is_null()) { + Console.WriteLine($"变量: {nameNode.text(sourceCode)}"); + } + } + + if (cursor.goto_first_child()) { + do { + TraverseNode(); + } while (cursor.goto_next_sibling()); + cursor.goto_parent(); + } +} + +TraverseNode(); +``` + +### 增量解析 + +```csharp +// 初始解析 +using var tree = parser.ParseString("const x = 1;"); + +// 编辑:在末尾添加内容 +var edit = new TSInputEdit { + start_byte = 12, // "const x = 1;" 长度 + old_end_byte = 12, + new_end_byte = 25, // 新增 "const y = 2;" 长度 + start_point = new TSPoint(0, 12), + old_end_point = new TSPoint(0, 12), + new_end_point = new TSPoint(0, 25) +}; + +tree.edit(edit); +var newTree = parser.ParseString(tree, "const x = 1;const y = 2;"); +``` + +## 注意事项 + +- 所有 `IDisposable` 对象使用 `using` 语句 +- 多线程使用 `tree.copy()` 创建副本 +- 设置超时:`parser.set_timeout_micros(5000000)` +- 检查错误:`root.has_error()` + +## 平台支持 + +- Linux x64 +- macOS ARM64 +- Windows x64 \ No newline at end of file diff --git a/docs/USAGE_GUIDE.md b/docs/USAGE_GUIDE.md new file mode 100644 index 0000000..9da3bc8 --- /dev/null +++ b/docs/USAGE_GUIDE.md @@ -0,0 +1,170 @@ +# TypeScript Parser 使用指南 + +一个简单易用的 TypeScript 解析器,帮你分析和处理 TypeScript 代码。 + +## 🚀 快速开始 + +### 安装 +```bash +dotnet add package TypeScriptParser +``` + +### 第一个例子 +```csharp +using TypeScriptParser; + +// 1. 创建解析器 +using var parser = new Parser(); + +// 2. 解析代码 +using var tree = parser.ParseString("const message = 'Hello TypeScript!';"); + +// 3. 查看结果 +var root = tree.root_node(); +Console.WriteLine($"解析成功!根节点类型: {root.type()}"); +// 输出: 解析成功!根节点类型: program +``` + +## 📖 基础概念 + +### 语法树 +当你写代码时: +```typescript +const x = 42; +``` + +解析器会把它变成这样的树结构: +``` +program +└── variable_declaration + └── variable_declarator + ├── identifier (x) + └── number (42) +``` + +### 探索节点 +```csharp +using var parser = new Parser(); +using var tree = parser.ParseString("const name = 'Alice';"); + +var root = tree.root_node(); +Console.WriteLine($"根节点类型: {root.type()}"); +Console.WriteLine($"子节点数量: {root.child_count()}"); + +// 遍历子节点 +for (uint i = 0; i < root.child_count(); i++) +{ + var child = root.child(i); + Console.WriteLine($"子节点 {i}: {child.type()}"); +} +``` + +## 🎯 实用示例 + +### 提取所有导出函数 +```csharp +using TypeScriptParser; +using TypeScriptParser.TreeSitter; + +string code = @" +export function add(a: number, b: number): number { + return a + b; +} + +function helper() { + return 'not exported'; +} + +export function multiply(x: number, y: number): number { + return x * y; +} +"; + +using var parser = new Parser(); +using var tree = parser.ParseString(code); + +// 创建查询 +var query = tree.language().query_new(@" + (export_statement + declaration: (function_declaration + name: (identifier) @func_name)) +", out var offset, out var error); + +if (query != null && error == TSQueryError.TSQueryErrorNone) +{ + using var cursor = new TSQueryCursor(); + cursor.exec(query, tree.root_node()); + + Console.WriteLine("导出的函数:"); + while (cursor.next_match(out var match, out var captures)) + { + foreach (var capture in captures) + { + var funcName = capture.node.text(code); + Console.WriteLine($" - {funcName}"); + } + } + + query.Dispose(); +} + +// 输出: +// 导出的函数: +// - add +// - multiply +``` + +### 遍历所有节点 +```csharp +using var parser = new Parser(); +using var tree = parser.ParseString("const x = 1; let y = 'hello';"); +using var cursor = new TSCursor(tree.root_node(), tree.language()); + +void TraverseTree() +{ + var node = cursor.current_node(); + Console.WriteLine($"{node.type()}: {node.text(code)}"); + + if (cursor.goto_first_child()) + { + do + { + TraverseTree(); + } while (cursor.goto_next_sibling()); + cursor.goto_parent(); + } +} + +TraverseTree(); +``` + +## 💡 最佳实践 + +### ✅ 推荐做法 +```csharp +// 使用 using 语句管理资源 +using var parser = new Parser(); +using var tree = parser.ParseString(code); + +// 检查节点是否为空 +var nameNode = node.child_by_field_name("name"); +if (!nameNode.is_null()) +{ + Console.WriteLine(nameNode.text(code)); +} + +// 检查解析错误 +if (tree.root_node().has_error()) +{ + Console.WriteLine("代码有语法错误"); +} +``` + +### ❌ 避免做法 +- 忘记释放 `IDisposable` 对象 +- 直接访问可能为空的节点 +- 在循环中重复创建相同的查询 + +--- + +更多详细信息请参考 [API参考文档](API_REFERENCE.md) \ No newline at end of file