diff --git a/.tests/unit/test_parsers.py b/.tests/unit/test_parsers.py new file mode 100644 index 0000000..f95c869 --- /dev/null +++ b/.tests/unit/test_parsers.py @@ -0,0 +1,300 @@ +"""Unit tests for parsers.""" + +import pytest +import os +from code_doc_cli.parsers.python_parser import PythonParser +from code_doc_cli.parsers.typescript_parser import TypeScriptParser +from code_doc_cli.parsers.go_parser import GoParser +from code_doc_cli.parsers.registry import ParserRegistry +from code_doc_cli.parsers.base import ElementType +import tempfile + + +class TestPythonParser: + """Tests for Python parser.""" + + def test_parse_function(self): + """Test parsing a Python function.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write(''' +def add(a: int, b: int) -> int: + """Add two numbers together. + + Args: + a: First number + b: Second number + + Returns: + The sum of a and b + """ + return a + b +''') + f.flush() + + parser = PythonParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + assert len(elements) > 0 + + func_names = [e.name for e in elements] + assert "add" in func_names + + add_elem = next(e for e in elements if e.name == "add") + assert add_elem.element_type == ElementType.FUNCTION + assert len(add_elem.parameters) == 2 + + def test_parse_class(self): + """Test parsing a Python class.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write(''' +class Calculator: + """A simple calculator class.""" + + def __init__(self, initial: int = 0): + """Initialize calculator.""" + self.memory = initial + + def multiply(self, x: int, y: int) -> int: + """Multiply two numbers.""" + return x * y +''') + f.flush() + + parser = PythonParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + class_names = [e.name for e in elements] + assert "Calculator" in class_names + + calc_elem = next(e for e in elements if e.name == "Calculator") + assert calc_elem.element_type == ElementType.CLASS + + def test_parse_module(self): + """Test parsing module docstring.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write('''"""This is a test module.""" + +def test_func(): + pass +''') + f.flush() + + parser = PythonParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + module_elems = [e for e in elements if e.element_type == ElementType.MODULE] + assert len(module_elems) == 1 + assert "test module" in module_elems[0].description.lower() + + def test_language_name(self): + """Test language name detection.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write("x = 1\n") + f.flush() + + parser = PythonParser(f.name) + os.unlink(f.name) + + assert parser.get_language_name() == "python" + + def test_supports_file(self): + """Test file extension support.""" + assert PythonParser.supports_file("test.py") + assert PythonParser.supports_file("test.pyw") + assert not PythonParser.supports_file("test.ts") + assert not PythonParser.supports_file("test.go") + + +class TestTypeScriptParser: + """Tests for TypeScript parser.""" + + def test_parse_function(self): + """Test parsing a TypeScript function.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f: + f.write(''' +export function add(a: number, b: number): number { + return a + b; +} +''') + f.flush() + + parser = TypeScriptParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + func_names = [e.name for e in elements if e.element_type == ElementType.FUNCTION] + assert "add" in func_names + + def test_parse_interface(self): + """Test parsing a TypeScript interface.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f: + f.write(''' +export interface User { + id: number; + name: string; +} +''') + f.flush() + + parser = TypeScriptParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + interface_names = [e.name for e in elements if e.element_type == ElementType.INTERFACE] + assert "User" in interface_names + + def test_parse_class(self): + """Test parsing a TypeScript class.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f: + f.write(''' +export class Calculator { + private memory: number; + + constructor(initial: number = 0) { + this.memory = initial; + } + + public multiply(x: number, y: number): number { + return x * y; + } +} +''') + f.flush() + + parser = TypeScriptParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + class_names = [e.name for e in elements if e.element_type == ElementType.CLASS] + assert "Calculator" in class_names + + def test_language_name(self): + """Test language name detection.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".ts", delete=False) as f: + f.write("const x = 1;\n") + f.flush() + + parser = TypeScriptParser(f.name) + os.unlink(f.name) + + assert parser.get_language_name() == "typescript" + + def test_supports_file(self): + """Test file extension support.""" + assert TypeScriptParser.supports_file("test.ts") + assert TypeScriptParser.supports_file("test.tsx") + assert not TypeScriptParser.supports_file("test.py") + + +class TestGoParser: + """Tests for Go parser.""" + + def test_parse_function(self): + """Test parsing a Go function.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".go", delete=False) as f: + f.write(''' +package main + +func Add(a, b int) int { + return a + b +} +''') + f.flush() + + parser = GoParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + func_names = [e.name for e in elements if e.element_type == ElementType.FUNCTION] + assert "Add" in func_names + + def test_parse_struct(self): + """Test parsing a Go struct.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".go", delete=False) as f: + f.write(''' +package main + +type Calculator struct { + memory int +} +''') + f.flush() + + parser = GoParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + struct_names = [e.name for e in elements if e.element_type == ElementType.STRUCT] + assert "Calculator" in struct_names + + def test_parse_package(self): + """Test parsing package docstring.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".go", delete=False) as f: + f.write('''// Package main provides math functions. +package main + +func Add(a, b int) int { + return a + b +} +''') + f.flush() + + parser = GoParser(f.name) + elements = parser.parse() + + os.unlink(f.name) + + module_elems = [e for e in elements if e.element_type == ElementType.MODULE] + assert len(module_elems) >= 1 + + def test_language_name(self): + """Test language name detection.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".go", delete=False) as f: + f.write("package main\n") + f.flush() + + parser = GoParser(f.name) + os.unlink(f.name) + + assert parser.get_language_name() == "go" + + def test_supports_file(self): + """Test file extension support.""" + assert GoParser.supports_file("test.go") + assert not GoParser.supports_file("test.py") + + +class TestParserRegistry: + """Tests for parser registry.""" + + def test_get_parser_class(self): + """Test getting parser class by language.""" + assert ParserRegistry.get_parser_class("python") == PythonParser + assert ParserRegistry.get_parser_class("py") == PythonParser + assert ParserRegistry.get_parser_class("typescript") == TypeScriptParser + assert ParserRegistry.get_parser_class("ts") == TypeScriptParser + assert ParserRegistry.get_parser_class("go") == GoParser + + def test_get_language_from_extension(self): + """Test language detection from extension.""" + assert ParserRegistry.get_language_from_extension("test.py") == "python" + assert ParserRegistry.get_language_from_extension("test.ts") == "typescript" + assert ParserRegistry.get_language_from_extension("test.go") == "go" + + def test_get_supported_languages(self): + """Test getting supported languages.""" + languages = ParserRegistry.get_supported_languages() + assert "python" in languages + assert "typescript" in languages + assert "go" in languages