mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-14 05:30:19 +00:00
Ho hum.
This commit is contained in:
@@ -0,0 +1,630 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/mcp-filepuff/pkg/protocol"
|
||||
sitter "github.com/smacker/go-tree-sitter"
|
||||
)
|
||||
|
||||
func TestExtractGoDocComment(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
defer registry.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
nodeKind string
|
||||
wantText string
|
||||
wantStyle CommentStyle
|
||||
}{
|
||||
{
|
||||
name: "single line comment",
|
||||
code: `package main
|
||||
|
||||
// Hello says hello
|
||||
func Hello() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "Hello says hello",
|
||||
wantStyle: CommentStyleLine,
|
||||
},
|
||||
{
|
||||
name: "multi-line comments",
|
||||
code: `package main
|
||||
|
||||
// This is a function
|
||||
// that does something
|
||||
// important
|
||||
func DoSomething() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "This is a function\nthat does something\nimportant",
|
||||
wantStyle: CommentStyleLine,
|
||||
},
|
||||
{
|
||||
name: "block comment",
|
||||
code: `package main
|
||||
|
||||
/* This is a block comment
|
||||
describing the function */
|
||||
func BlockCommented() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "This is a block comment\ndescribing the function",
|
||||
wantStyle: CommentStyleBlock,
|
||||
},
|
||||
{
|
||||
name: "doc comment with asterisks",
|
||||
code: `package main
|
||||
|
||||
/*
|
||||
* This is a properly formatted
|
||||
* block comment with asterisks
|
||||
*/
|
||||
func FormattedBlock() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "This is a properly formatted\nblock comment with asterisks",
|
||||
wantStyle: CommentStyleBlock,
|
||||
},
|
||||
{
|
||||
name: "no comment",
|
||||
code: `package main
|
||||
|
||||
func NoComment() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := registry.Parse(context.Background(), "test.go", []byte(tt.code))
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
// Find the target node
|
||||
targetNode := findNodeByKind(result.Tree.RootNode(), tt.nodeKind)
|
||||
if targetNode == nil {
|
||||
t.Fatalf("could not find node of type %s", tt.nodeKind)
|
||||
}
|
||||
|
||||
doc := ExtractDocComment(targetNode, []byte(tt.code), protocol.LangGo)
|
||||
|
||||
if tt.wantText == "" {
|
||||
if doc != nil && doc.Text != "" {
|
||||
t.Errorf("expected no doc, got %q", doc.Text)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if doc == nil {
|
||||
t.Fatal("expected doc, got nil")
|
||||
}
|
||||
|
||||
if doc.Text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", doc.Text, tt.wantText)
|
||||
}
|
||||
|
||||
if doc.Style != tt.wantStyle {
|
||||
t.Errorf("style mismatch: got %v, want %v", doc.Style, tt.wantStyle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractJSDocComment(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
defer registry.Close()
|
||||
|
||||
tests := []struct {
|
||||
wantTags map[string]string
|
||||
name string
|
||||
code string
|
||||
nodeKind string
|
||||
wantText string
|
||||
wantStyle CommentStyle
|
||||
}{
|
||||
{
|
||||
name: "JSDoc comment",
|
||||
code: `/**
|
||||
* Adds two numbers together.
|
||||
* @param a The first number
|
||||
* @param b The second number
|
||||
* @returns The sum of a and b
|
||||
*/
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "Adds two numbers together.",
|
||||
wantStyle: CommentStyleJSDoc,
|
||||
wantTags: map[string]string{
|
||||
"param": "a The first number\nb The second number",
|
||||
"returns": "The sum of a and b",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple line comment",
|
||||
code: `// This is a simple function
|
||||
function simple() {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "This is a simple function",
|
||||
wantStyle: CommentStyleLine,
|
||||
},
|
||||
{
|
||||
name: "JSDoc with types",
|
||||
code: `/**
|
||||
* @param {string} name - The name
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
function validate(name) {}
|
||||
`,
|
||||
nodeKind: "function_declaration",
|
||||
wantText: "",
|
||||
wantStyle: CommentStyleJSDoc,
|
||||
wantTags: map[string]string{
|
||||
"param": "{string} name - The name",
|
||||
"returns": "{boolean} True if valid",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := registry.Parse(context.Background(), "test.js", []byte(tt.code))
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
targetNode := findNodeByKind(result.Tree.RootNode(), tt.nodeKind)
|
||||
if targetNode == nil {
|
||||
t.Fatalf("could not find node of type %s", tt.nodeKind)
|
||||
}
|
||||
|
||||
doc := ExtractDocComment(targetNode, []byte(tt.code), protocol.LangJavaScript)
|
||||
|
||||
if doc == nil {
|
||||
t.Fatal("expected doc, got nil")
|
||||
}
|
||||
|
||||
if doc.Text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", doc.Text, tt.wantText)
|
||||
}
|
||||
|
||||
if doc.Style != tt.wantStyle {
|
||||
t.Errorf("style mismatch: got %v, want %v", doc.Style, tt.wantStyle)
|
||||
}
|
||||
|
||||
if tt.wantTags != nil {
|
||||
for k, want := range tt.wantTags {
|
||||
if got := doc.Tags[k]; got != want {
|
||||
t.Errorf("tag %q mismatch:\ngot: %q\nwant: %q", k, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPythonDocComment(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
defer registry.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
nodeKind string
|
||||
wantText string
|
||||
wantStyle CommentStyle
|
||||
}{
|
||||
{
|
||||
name: "docstring",
|
||||
code: `def greet(name):
|
||||
"""Greet a person by name."""
|
||||
print(f"Hello, {name}!")
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "Greet a person by name.",
|
||||
wantStyle: CommentStyleDocstring,
|
||||
},
|
||||
{
|
||||
name: "multi-line docstring",
|
||||
code: `def calculate(x, y):
|
||||
"""
|
||||
Calculate the sum of two numbers.
|
||||
|
||||
Args:
|
||||
x: First number
|
||||
y: Second number
|
||||
|
||||
Returns:
|
||||
The sum of x and y
|
||||
"""
|
||||
return x + y
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "Calculate the sum of two numbers.\n\n Args:\n x: First number\n y: Second number\n\n Returns:\n The sum of x and y",
|
||||
wantStyle: CommentStyleDocstring,
|
||||
},
|
||||
{
|
||||
name: "class docstring",
|
||||
code: `class MyClass:
|
||||
"""This is a class description."""
|
||||
pass
|
||||
`,
|
||||
nodeKind: "class_definition",
|
||||
wantText: "This is a class description.",
|
||||
wantStyle: CommentStyleDocstring,
|
||||
},
|
||||
{
|
||||
name: "single quote docstring",
|
||||
code: `def func():
|
||||
'''Single quote docstring'''
|
||||
pass
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "Single quote docstring",
|
||||
wantStyle: CommentStyleDocstring,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := registry.Parse(context.Background(), "test.py", []byte(tt.code))
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
targetNode := findNodeByKind(result.Tree.RootNode(), tt.nodeKind)
|
||||
if targetNode == nil {
|
||||
t.Fatalf("could not find node of type %s", tt.nodeKind)
|
||||
}
|
||||
|
||||
doc := ExtractDocComment(targetNode, []byte(tt.code), protocol.LangPython)
|
||||
|
||||
if doc == nil {
|
||||
t.Fatal("expected doc, got nil")
|
||||
}
|
||||
|
||||
if doc.Text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", doc.Text, tt.wantText)
|
||||
}
|
||||
|
||||
if doc.Style != tt.wantStyle {
|
||||
t.Errorf("style mismatch: got %v, want %v", doc.Style, tt.wantStyle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractCDocComment(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
defer registry.Close()
|
||||
|
||||
tests := []struct {
|
||||
wantTags map[string]string
|
||||
name string
|
||||
code string
|
||||
nodeKind string
|
||||
wantText string
|
||||
wantStyle CommentStyle
|
||||
}{
|
||||
{
|
||||
name: "Doxygen block comment",
|
||||
code: `/**
|
||||
* Adds two numbers.
|
||||
* @param a First number
|
||||
* @param b Second number
|
||||
* @return Sum of a and b
|
||||
*/
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "Adds two numbers.",
|
||||
wantStyle: CommentStyleDoxygen,
|
||||
wantTags: map[string]string{
|
||||
"param": "a First number\nb Second number",
|
||||
"return": "Sum of a and b",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular block comment",
|
||||
code: `/* This is a regular comment */
|
||||
int regular() { return 0; }
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "This is a regular comment",
|
||||
wantStyle: CommentStyleBlock,
|
||||
},
|
||||
{
|
||||
name: "line comment",
|
||||
code: `// Simple function
|
||||
int simple() { return 1; }
|
||||
`,
|
||||
nodeKind: "function_definition",
|
||||
wantText: "Simple function",
|
||||
wantStyle: CommentStyleLine,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := registry.Parse(context.Background(), "test.c", []byte(tt.code))
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
|
||||
targetNode := findNodeByKind(result.Tree.RootNode(), tt.nodeKind)
|
||||
if targetNode == nil {
|
||||
t.Fatalf("could not find node of type %s", tt.nodeKind)
|
||||
}
|
||||
|
||||
doc := ExtractDocComment(targetNode, []byte(tt.code), protocol.LangC)
|
||||
|
||||
if doc == nil {
|
||||
t.Fatal("expected doc, got nil")
|
||||
}
|
||||
|
||||
if doc.Text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", doc.Text, tt.wantText)
|
||||
}
|
||||
|
||||
if doc.Style != tt.wantStyle {
|
||||
t.Errorf("style mismatch: got %v, want %v", doc.Style, tt.wantStyle)
|
||||
}
|
||||
|
||||
if tt.wantTags != nil {
|
||||
for k, want := range tt.wantTags {
|
||||
if got := doc.Tags[k]; got != want {
|
||||
t.Errorf("tag %q mismatch:\ngot: %q\nwant: %q", k, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJSDoc(t *testing.T) {
|
||||
tests := []struct {
|
||||
wantTags map[string]string
|
||||
name string
|
||||
input string
|
||||
wantText string
|
||||
}{
|
||||
{
|
||||
name: "complete jsdoc",
|
||||
input: `/**
|
||||
* This is a description.
|
||||
* Multiple lines.
|
||||
* @param {string} name The name
|
||||
* @returns {boolean} Result
|
||||
*/`,
|
||||
wantText: "This is a description.\nMultiple lines.",
|
||||
wantTags: map[string]string{
|
||||
"param": "{string} name The name",
|
||||
"returns": "{boolean} Result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty jsdoc",
|
||||
input: `/** */`,
|
||||
wantText: "",
|
||||
wantTags: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "only description",
|
||||
input: `/** Simple description */`,
|
||||
wantText: "Simple description",
|
||||
wantTags: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
text, tags := parseJSDoc(tt.input)
|
||||
|
||||
if text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", text, tt.wantText)
|
||||
}
|
||||
|
||||
if len(tags) != len(tt.wantTags) {
|
||||
t.Errorf("tag count mismatch: got %d, want %d", len(tags), len(tt.wantTags))
|
||||
}
|
||||
|
||||
for k, want := range tt.wantTags {
|
||||
if got := tags[k]; got != want {
|
||||
t.Errorf("tag %q mismatch:\ngot: %q\nwant: %q", k, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDoxygen(t *testing.T) {
|
||||
tests := []struct {
|
||||
wantTags map[string]string
|
||||
name string
|
||||
input string
|
||||
wantText string
|
||||
}{
|
||||
{
|
||||
name: "doxygen with @ tags",
|
||||
input: `/**
|
||||
* Brief description.
|
||||
* @param x Value
|
||||
* @return Result
|
||||
*/`,
|
||||
wantText: "Brief description.",
|
||||
wantTags: map[string]string{
|
||||
"param": "x Value",
|
||||
"return": "Result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "doxygen with backslash tags",
|
||||
input: `/**
|
||||
* Description.
|
||||
* \param y Input
|
||||
* \retval Output value
|
||||
*/`,
|
||||
wantText: "Description.",
|
||||
wantTags: map[string]string{
|
||||
"param": "y Input",
|
||||
"retval": "Output value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "triple slash",
|
||||
input: `/// Simple description`,
|
||||
wantText: "Simple description",
|
||||
wantTags: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
text, tags := parseDoxygen(tt.input)
|
||||
|
||||
if text != tt.wantText {
|
||||
t.Errorf("text mismatch:\ngot: %q\nwant: %q", text, tt.wantText)
|
||||
}
|
||||
|
||||
for k, want := range tt.wantTags {
|
||||
if got := tags[k]; got != want {
|
||||
t.Errorf("tag %q mismatch:\ngot: %q\nwant: %q", k, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDocComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
doc *DocComment
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "with tags",
|
||||
doc: &DocComment{
|
||||
Text: "This is a function.",
|
||||
Tags: map[string]string{
|
||||
"param": "x The value",
|
||||
"returns": "The result",
|
||||
},
|
||||
},
|
||||
want: "This is a function.\n\n@param x The value\n@returns The result",
|
||||
},
|
||||
{
|
||||
name: "no tags",
|
||||
doc: &DocComment{
|
||||
Text: "Simple description.",
|
||||
Tags: nil,
|
||||
},
|
||||
want: "Simple description.",
|
||||
},
|
||||
{
|
||||
name: "nil doc",
|
||||
doc: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty text",
|
||||
doc: &DocComment{
|
||||
Text: "",
|
||||
Tags: nil,
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := FormatDocComment(tt.doc)
|
||||
if got != tt.want {
|
||||
t.Errorf("mismatch:\ngot: %q\nwant: %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectCommentStyle(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want CommentStyle
|
||||
}{
|
||||
{"/** JSDoc */", CommentStyleJSDoc},
|
||||
{"/// Doxygen", CommentStyleDoxygen},
|
||||
{"//! Doxygen", CommentStyleDoxygen},
|
||||
{"/* block */", CommentStyleBlock},
|
||||
{"// line", CommentStyleLine},
|
||||
{"# hash", CommentStyleHash},
|
||||
{`"""docstring"""`, CommentStyleDocstring},
|
||||
{`'''docstring'''`, CommentStyleDocstring},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := detectCommentStyle(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("got %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// findNodeByKind finds the first node of the given kind.
|
||||
func findNodeByKind(root *sitter.Node, nodeType string) *sitter.Node {
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result *sitter.Node
|
||||
WalkTree(root, func(n *sitter.Node) bool {
|
||||
if n.Type() == nodeType {
|
||||
result = n
|
||||
return false // stop walking
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestCleanBlockComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
input: "\n * Line 1\n * Line 2\n ",
|
||||
want: "Line 1\nLine 2",
|
||||
},
|
||||
{
|
||||
input: "Simple",
|
||||
want: "Simple",
|
||||
},
|
||||
{
|
||||
input: "\n\nWith blank lines\n\n",
|
||||
want: "With blank lines",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input[:min(10, len(tt.input))], func(t *testing.T) {
|
||||
got := cleanBlockComment(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user