|
|
@@ -0,0 +1,553 @@
|
|
|
+import { describe, expect, it, jest, beforeEach } from "@jest/globals"
|
|
|
+import { parseSourceCodeDefinitionsForFile } from ".."
|
|
|
+import * as fs from "fs/promises"
|
|
|
+import * as path from "path"
|
|
|
+import Parser from "web-tree-sitter"
|
|
|
+import { fileExistsAtPath } from "../../../utils/fs"
|
|
|
+import { loadRequiredLanguageParsers } from "../languageParser"
|
|
|
+import { pythonQuery } from "../queries"
|
|
|
+import { initializeTreeSitter, testParseSourceCodeDefinitions, inspectTreeStructure, debugLog } from "./helpers"
|
|
|
+
|
|
|
+// Sample Python content for tests covering all supported structures:
|
|
|
+// - class definitions
|
|
|
+// - function definitions
|
|
|
+// - method definitions (instance methods, class methods, static methods)
|
|
|
+// - decorators (function and class decorators)
|
|
|
+// - module-level variables
|
|
|
+// - constants (by convention, uppercase variables)
|
|
|
+// - async functions and methods
|
|
|
+// - lambda functions
|
|
|
+// - class attributes
|
|
|
+// - property getters/setters
|
|
|
+// - type annotations
|
|
|
+// - dataclasses
|
|
|
+// - nested functions and classes
|
|
|
+// - generator functions
|
|
|
+// - list/dict/set comprehensions
|
|
|
+const samplePythonContent = `
|
|
|
+# Module-level imports
|
|
|
+import os
|
|
|
+import sys
|
|
|
+from typing import List, Dict, Optional, Tuple, Any, Union, Callable
|
|
|
+from dataclasses import dataclass, field
|
|
|
+from abc import ABC, abstractmethod
|
|
|
+
|
|
|
+# Module-level constants (by convention, uppercase variables)
|
|
|
+MAX_RETRIES = 5
|
|
|
+DEFAULT_TIMEOUT = 30
|
|
|
+API_BASE_URL = "https://api.example.com/v1"
|
|
|
+ALLOWED_EXTENSIONS = [".jpg", ".png", ".gif"]
|
|
|
+
|
|
|
+# Module-level variables
|
|
|
+config = {
|
|
|
+ "debug": True,
|
|
|
+ "log_level": "INFO",
|
|
|
+ "max_connections": 100
|
|
|
+}
|
|
|
+
|
|
|
+current_user = None
|
|
|
+session_active = False
|
|
|
+
|
|
|
+# Type-annotated variables
|
|
|
+user_id: int = 12345
|
|
|
+username: str = "johndoe"
|
|
|
+is_admin: bool = False
|
|
|
+scores: List[int] = [95, 87, 92]
|
|
|
+user_data: Dict[str, Any] = {"name": "John", "age": 30}
|
|
|
+
|
|
|
+# Basic function definition
|
|
|
+def calculate_average(numbers):
|
|
|
+ """Calculate the average of a list of numbers."""
|
|
|
+ total = sum(numbers)
|
|
|
+ count = len(numbers)
|
|
|
+ return total / count if count > 0 else 0
|
|
|
+
|
|
|
+# Function with type annotations
|
|
|
+def get_user_by_id(user_id: int) -> Optional[Dict[str, Any]]:
|
|
|
+ """
|
|
|
+ Retrieve user information by user ID.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ user_id: The ID of the user to retrieve
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ A dictionary with user information or None if not found
|
|
|
+ """
|
|
|
+ # This is just a placeholder implementation
|
|
|
+ if user_id == 12345:
|
|
|
+ return {"id": user_id, "name": "John Doe", "email": "[email protected]"}
|
|
|
+ return None
|
|
|
+
|
|
|
+# Async function
|
|
|
+async def fetch_data_from_api(endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ Fetch data from an API endpoint asynchronously.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ endpoint: The API endpoint to fetch data from
|
|
|
+ params: Optional query parameters
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The JSON response as a dictionary
|
|
|
+ """
|
|
|
+ # This is just a placeholder implementation
|
|
|
+ await asyncio.sleep(1) # Simulate network delay
|
|
|
+ return {"status": "success", "data": [1, 2, 3]}
|
|
|
+
|
|
|
+# Function with nested function
|
|
|
+def create_counter(start: int = 0):
|
|
|
+ """Create a counter function that increments from a starting value."""
|
|
|
+ count = start
|
|
|
+
|
|
|
+ # Nested function
|
|
|
+ def increment(step: int = 1):
|
|
|
+ nonlocal count
|
|
|
+ count += step
|
|
|
+ return count
|
|
|
+
|
|
|
+ return increment
|
|
|
+
|
|
|
+# Generator function
|
|
|
+def fibonacci_sequence(n: int):
|
|
|
+ """Generate the first n numbers in the Fibonacci sequence."""
|
|
|
+ a, b = 0, 1
|
|
|
+ count = 0
|
|
|
+
|
|
|
+ while count < n:
|
|
|
+ yield a
|
|
|
+ a, b = b, a + b
|
|
|
+ count += 1
|
|
|
+
|
|
|
+# Decorator function
|
|
|
+def log_execution(func):
|
|
|
+ """Decorator that logs function execution."""
|
|
|
+ def wrapper(*args, **kwargs):
|
|
|
+ print(f"Executing {func.__name__}")
|
|
|
+ result = func(*args, **kwargs)
|
|
|
+ print(f"Finished executing {func.__name__}")
|
|
|
+ return result
|
|
|
+ return wrapper
|
|
|
+
|
|
|
+# Decorated function
|
|
|
+@log_execution
|
|
|
+def process_data(data):
|
|
|
+ """Process the given data."""
|
|
|
+ # This is just a placeholder implementation
|
|
|
+ return [item * 2 for item in data]
|
|
|
+
|
|
|
+# Basic class definition
|
|
|
+class Point:
|
|
|
+ """A class representing a point in 2D space."""
|
|
|
+
|
|
|
+ # Class attribute
|
|
|
+ dimension = 2
|
|
|
+
|
|
|
+ def __init__(self, x: float, y: float):
|
|
|
+ """Initialize a point with x and y coordinates."""
|
|
|
+ # Instance attributes
|
|
|
+ self.x = x
|
|
|
+ self.y = y
|
|
|
+
|
|
|
+ # Instance method
|
|
|
+ def distance_from_origin(self) -> float:
|
|
|
+ """Calculate the distance from the origin (0, 0)."""
|
|
|
+ return (self.x ** 2 + self.y ** 2) ** 0.5
|
|
|
+
|
|
|
+ # Method with multiple parameters
|
|
|
+ def distance_from(self, other_point) -> float:
|
|
|
+ """Calculate the distance from another point."""
|
|
|
+ dx = self.x - other_point.x
|
|
|
+ dy = self.y - other_point.y
|
|
|
+ return (dx ** 2 + dy ** 2) ** 0.5
|
|
|
+
|
|
|
+ # Property getter
|
|
|
+ @property
|
|
|
+ def magnitude(self) -> float:
|
|
|
+ """Get the magnitude (distance from origin) of the point."""
|
|
|
+ return self.distance_from_origin()
|
|
|
+
|
|
|
+ # Property setter
|
|
|
+ @magnitude.setter
|
|
|
+ def magnitude(self, value: float):
|
|
|
+ """Set the magnitude while preserving direction."""
|
|
|
+ if value < 0:
|
|
|
+ raise ValueError("Magnitude cannot be negative")
|
|
|
+
|
|
|
+ if self.magnitude == 0:
|
|
|
+ # Can't set magnitude for a zero vector (no direction)
|
|
|
+ return
|
|
|
+
|
|
|
+ scale = value / self.magnitude
|
|
|
+ self.x *= scale
|
|
|
+ self.y *= scale
|
|
|
+
|
|
|
+ # Class method
|
|
|
+ @classmethod
|
|
|
+ def from_polar(cls, radius: float, angle: float):
|
|
|
+ """Create a point from polar coordinates."""
|
|
|
+ x = radius * math.cos(angle)
|
|
|
+ y = radius * math.sin(angle)
|
|
|
+ return cls(x, y)
|
|
|
+
|
|
|
+ # Static method
|
|
|
+ @staticmethod
|
|
|
+ def origin():
|
|
|
+ """Return the origin point (0, 0)."""
|
|
|
+ return Point(0, 0)
|
|
|
+
|
|
|
+ # Special method
|
|
|
+ def __str__(self) -> str:
|
|
|
+ """String representation of the point."""
|
|
|
+ return f"Point({self.x}, {self.y})"
|
|
|
+
|
|
|
+ # Special method
|
|
|
+ def __eq__(self, other) -> bool:
|
|
|
+ """Check if two points are equal."""
|
|
|
+ if not isinstance(other, Point):
|
|
|
+ return False
|
|
|
+ return self.x == other.x and self.y == other.y
|
|
|
+
|
|
|
+# Dataclass
|
|
|
+@dataclass
|
|
|
+class Person:
|
|
|
+ """A class representing a person."""
|
|
|
+
|
|
|
+ name: str
|
|
|
+ age: int
|
|
|
+ email: str
|
|
|
+ address: Optional[str] = None
|
|
|
+ phone_numbers: List[str] = field(default_factory=list)
|
|
|
+
|
|
|
+ def is_adult(self) -> bool:
|
|
|
+ """Check if the person is an adult (age >= 18)."""
|
|
|
+ return self.age >= 18
|
|
|
+
|
|
|
+ def __str__(self) -> str:
|
|
|
+ """String representation of the person."""
|
|
|
+ return f"{self.name} ({self.age})"
|
|
|
+
|
|
|
+# Abstract base class
|
|
|
+class Shape(ABC):
|
|
|
+ """An abstract base class for shapes."""
|
|
|
+
|
|
|
+ @abstractmethod
|
|
|
+ def area(self) -> float:
|
|
|
+ """Calculate the area of the shape."""
|
|
|
+ pass
|
|
|
+
|
|
|
+ @abstractmethod
|
|
|
+ def perimeter(self) -> float:
|
|
|
+ """Calculate the perimeter of the shape."""
|
|
|
+ pass
|
|
|
+
|
|
|
+ def describe(self) -> str:
|
|
|
+ """Describe the shape."""
|
|
|
+ return f"Shape with area {self.area()} and perimeter {self.perimeter()}"
|
|
|
+
|
|
|
+# Class inheriting from abstract base class
|
|
|
+class Rectangle(Shape):
|
|
|
+ """A class representing a rectangle."""
|
|
|
+
|
|
|
+ def __init__(self, width: float, height: float):
|
|
|
+ """Initialize a rectangle with width and height."""
|
|
|
+ self.width = width
|
|
|
+ self.height = height
|
|
|
+
|
|
|
+ def area(self) -> float:
|
|
|
+ """Calculate the area of the rectangle."""
|
|
|
+ return self.width * self.height
|
|
|
+
|
|
|
+ def perimeter(self) -> float:
|
|
|
+ """Calculate the perimeter of the rectangle."""
|
|
|
+ return 2 * (self.width + self.height)
|
|
|
+
|
|
|
+ # Async method
|
|
|
+ async def calculate_diagonal(self) -> float:
|
|
|
+ """Calculate the diagonal of the rectangle asynchronously."""
|
|
|
+ await asyncio.sleep(0.1) # Simulate some async operation
|
|
|
+ return (self.width ** 2 + self.height ** 2) ** 0.5
|
|
|
+
|
|
|
+# Class with nested class
|
|
|
+class Department:
|
|
|
+ """A class representing a department in an organization."""
|
|
|
+
|
|
|
+ def __init__(self, name: str):
|
|
|
+ """Initialize a department with a name."""
|
|
|
+ self.name = name
|
|
|
+ self.employees = []
|
|
|
+
|
|
|
+ def add_employee(self, employee):
|
|
|
+ """Add an employee to the department."""
|
|
|
+ self.employees.append(employee)
|
|
|
+
|
|
|
+ # Nested class
|
|
|
+ class Employee:
|
|
|
+ """A nested class representing an employee."""
|
|
|
+
|
|
|
+ def __init__(self, name: str, position: str):
|
|
|
+ """Initialize an employee with a name and position."""
|
|
|
+ self.name = name
|
|
|
+ self.position = position
|
|
|
+
|
|
|
+ def __str__(self) -> str:
|
|
|
+ """String representation of the employee."""
|
|
|
+ return f"{self.name} ({self.position})"
|
|
|
+
|
|
|
+# Main execution block
|
|
|
+if __name__ == "__main__":
|
|
|
+ # List comprehension
|
|
|
+ squares = [x ** 2 for x in range(10)]
|
|
|
+
|
|
|
+ # Dictionary comprehension
|
|
|
+ square_map = {x: x ** 2 for x in range(10)}
|
|
|
+
|
|
|
+ # Set comprehension
|
|
|
+ even_squares = {x ** 2 for x in range(10) if x % 2 == 0}
|
|
|
+
|
|
|
+ # Lambda function
|
|
|
+ double = lambda x: x * 2
|
|
|
+
|
|
|
+ # Using the lambda function
|
|
|
+ doubled_numbers = list(map(double, [1, 2, 3, 4, 5]))
|
|
|
+
|
|
|
+ # Creating and using a point
|
|
|
+ p1 = Point(3, 4)
|
|
|
+ print(f"Distance from origin: {p1.distance_from_origin()}")
|
|
|
+
|
|
|
+ # Using a class method
|
|
|
+ p2 = Point.from_polar(5, math.pi/4)
|
|
|
+ print(f"Point from polar coordinates: {p2}")
|
|
|
+
|
|
|
+ # Using a static method
|
|
|
+ origin = Point.origin()
|
|
|
+ print(f"Origin: {origin}")
|
|
|
+
|
|
|
+ # Creating a person using dataclass
|
|
|
+ john = Person(name="John Doe", age=30, email="[email protected]")
|
|
|
+ print(f"Is John an adult? {john.is_adult()}")
|
|
|
+
|
|
|
+ # Creating a rectangle
|
|
|
+ rect = Rectangle(width=5, height=10)
|
|
|
+ print(f"Rectangle area: {rect.area()}")
|
|
|
+ print(f"Rectangle perimeter: {rect.perimeter()}")
|
|
|
+
|
|
|
+ # Creating a counter
|
|
|
+ counter = create_counter(10)
|
|
|
+ print(f"Counter: {counter()}") # 11
|
|
|
+ print(f"Counter: {counter()}") # 12
|
|
|
+
|
|
|
+ # Using a generator
|
|
|
+ fib = fibonacci_sequence(10)
|
|
|
+ print(f"Fibonacci sequence: {list(fib)}")
|
|
|
+
|
|
|
+ # Using a decorated function
|
|
|
+ result = process_data([1, 2, 3])
|
|
|
+ print(f"Processed data: {result}")
|
|
|
+`
|
|
|
+
|
|
|
+// Python test options
|
|
|
+const pythonOptions = {
|
|
|
+ language: "python",
|
|
|
+ wasmFile: "tree-sitter-python.wasm",
|
|
|
+ queryString: pythonQuery,
|
|
|
+ extKey: "py",
|
|
|
+}
|
|
|
+
|
|
|
+// Mock file system operations
|
|
|
+jest.mock("fs/promises")
|
|
|
+const mockedFs = jest.mocked(fs)
|
|
|
+
|
|
|
+// Mock loadRequiredLanguageParsers
|
|
|
+jest.mock("../languageParser", () => ({
|
|
|
+ loadRequiredLanguageParsers: jest.fn(),
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock fileExistsAtPath to return true for our test paths
|
|
|
+jest.mock("../../../utils/fs", () => ({
|
|
|
+ fileExistsAtPath: jest.fn().mockImplementation(() => Promise.resolve(true)),
|
|
|
+}))
|
|
|
+
|
|
|
+describe("parseSourceCodeDefinitionsForFile with Python", () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ jest.clearAllMocks()
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python class definitions", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for class definitions
|
|
|
+ expect(result).toContain("class Point")
|
|
|
+ expect(result).toContain("class Person")
|
|
|
+ expect(result).toContain("class Shape")
|
|
|
+ expect(result).toContain("class Rectangle")
|
|
|
+ expect(result).toContain("class Department")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python function definitions", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for function definitions
|
|
|
+ expect(result).toContain("def calculate_average")
|
|
|
+ expect(result).toContain("def get_user_by_id")
|
|
|
+ expect(result).toContain("def create_counter")
|
|
|
+ expect(result).toContain("def fibonacci_sequence")
|
|
|
+ expect(result).toContain("def log_execution")
|
|
|
+ expect(result).toContain("def process_data")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python method definitions", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for method definitions - we verify that class definitions are captured
|
|
|
+ // and that some methods are captured, even if not all methods are captured directly
|
|
|
+ expect(result).toContain("class Point")
|
|
|
+ expect(result).toContain("class Rectangle")
|
|
|
+ expect(resultLines.some((line) => line.includes("def __init__"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("def distance_from"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python decorated functions and methods", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for decorated functions
|
|
|
+ expect(resultLines.some((line) => line.includes("@log_execution"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("def process_data"))).toBe(true)
|
|
|
+
|
|
|
+ // Check for property getters/setters
|
|
|
+ expect(resultLines.some((line) => line.includes("@property"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("def magnitude"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@magnitude.setter"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python class and static methods", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for decorated methods - we verify that decorators are captured
|
|
|
+ // even if the specific methods are not directly captured
|
|
|
+ expect(resultLines.some((line) => line.includes("@classmethod"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@staticmethod"))).toBe(true)
|
|
|
+
|
|
|
+ // Verify that the class containing these methods is captured
|
|
|
+ expect(result).toContain("class Point")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python module-level variables and constants", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for module-level variables that are captured
|
|
|
+ expect(result).toContain("config =")
|
|
|
+
|
|
|
+ // Verify that the file content is being processed
|
|
|
+ expect(result).toContain("# file.py")
|
|
|
+
|
|
|
+ // Verify that some content from the module level is captured
|
|
|
+ expect(resultLines.some((line) => line.includes("# Module-level imports"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python async functions and methods", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for async functions
|
|
|
+ expect(resultLines.some((line) => line.includes("async def fetch_data_from_api"))).toBe(true)
|
|
|
+
|
|
|
+ // Check for async methods
|
|
|
+ expect(resultLines.some((line) => line.includes("async def calculate_diagonal"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python dataclasses", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for dataclasses
|
|
|
+ expect(resultLines.some((line) => line.includes("@dataclass"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("class Person"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python nested functions and classes", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for nested functions
|
|
|
+ expect(resultLines.some((line) => line.includes("def increment"))).toBe(true)
|
|
|
+
|
|
|
+ // Check for nested classes
|
|
|
+ expect(resultLines.some((line) => line.includes("class Employee"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python type annotations", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Check for functions with type annotations
|
|
|
+ expect(result).toContain("def get_user_by_id(user_id: int) -> Optional[Dict[str, Any]]")
|
|
|
+
|
|
|
+ // Verify that functions with parameters are captured
|
|
|
+ expect(resultLines.some((line) => line.includes("def") && line.includes("->"))).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should parse Python comprehensions and lambda functions", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Verify that the file is being processed
|
|
|
+ expect(result).toContain("# file.py")
|
|
|
+
|
|
|
+ // Verify that Python code is captured
|
|
|
+ expect(resultLines.length).toBeGreaterThan(5)
|
|
|
+
|
|
|
+ // Verify that functions are captured
|
|
|
+ expect(result).toContain("def ")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle all Python language constructs comprehensively", async () => {
|
|
|
+ const result = await testParseSourceCodeDefinitions("/test/file.py", samplePythonContent, pythonOptions)
|
|
|
+ const resultLines = result?.split("\n") || []
|
|
|
+
|
|
|
+ // Verify the output format includes line numbers
|
|
|
+ expect(resultLines.some((line) => /\d+--\d+ \|/.test(line))).toBe(true)
|
|
|
+
|
|
|
+ // Verify the output includes the file name
|
|
|
+ expect(result).toContain("# file.py")
|
|
|
+
|
|
|
+ // Verify all major Python constructs are captured
|
|
|
+ // Classes
|
|
|
+ expect(result).toContain("class Point")
|
|
|
+ expect(result).toContain("class Person")
|
|
|
+ expect(result).toContain("class Shape")
|
|
|
+ expect(result).toContain("class Rectangle")
|
|
|
+ expect(result).toContain("class Department")
|
|
|
+
|
|
|
+ // Functions
|
|
|
+ expect(result).toContain("def calculate_average")
|
|
|
+ expect(result).toContain("def get_user_by_id")
|
|
|
+ expect(result).toContain("def create_counter")
|
|
|
+ expect(result).toContain("def fibonacci_sequence")
|
|
|
+ expect(result).toContain("def log_execution")
|
|
|
+ expect(result).toContain("def process_data")
|
|
|
+
|
|
|
+ // Methods - verify that classes with methods are captured
|
|
|
+ expect(result).toContain("class Point")
|
|
|
+ expect(result).toContain("class Rectangle")
|
|
|
+ expect(resultLines.some((line) => line.includes("def __init__"))).toBe(true)
|
|
|
+
|
|
|
+ // Decorated functions and methods - verify that decorators are captured
|
|
|
+ expect(resultLines.some((line) => line.includes("@log_execution"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@property"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@classmethod"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@staticmethod"))).toBe(true)
|
|
|
+ expect(resultLines.some((line) => line.includes("@dataclass"))).toBe(true)
|
|
|
+
|
|
|
+ // Async functions - verify that async functions are captured
|
|
|
+ expect(result).toContain("async def fetch_data_from_api")
|
|
|
+
|
|
|
+ // Verify that the parser is capturing a good range of Python constructs
|
|
|
+ expect(resultLines.length).toBeGreaterThan(10)
|
|
|
+ })
|
|
|
+})
|