Spaces:
Running
Running
| import { describe, it, expect } from 'vitest'; | |
| import { parseTypeScript } from './parser'; | |
| describe('parseTypeScript', () => { | |
| describe('function declarations', () => { | |
| it('extracts a regular function declaration', () => { | |
| const content = `function greet(name) { | |
| return 'hello ' + name; | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('greet'); | |
| expect(result.functions[0].params).toEqual(['name']); | |
| expect(result.functions[0].isExported).toBe(false); | |
| expect(result.functions[0].startLine).toBe(1); | |
| expect(result.functions[0].endLine).toBe(3); | |
| }); | |
| it('extracts an async function declaration', () => { | |
| const content = `async function fetchData(url, options) { | |
| const res = await fetch(url, options); | |
| return res.json(); | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('fetchData'); | |
| expect(result.functions[0].params).toEqual(['url', 'options']); | |
| expect(result.functions[0].isExported).toBe(false); | |
| }); | |
| it('extracts an exported function declaration', () => { | |
| const content = `export function calculate(a, b) { | |
| return a + b; | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('calculate'); | |
| expect(result.functions[0].isExported).toBe(true); | |
| expect(result.exports).toContain('calculate'); | |
| }); | |
| it('extracts an export default function', () => { | |
| const content = `export default function main() { | |
| console.log('hello'); | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('main'); | |
| expect(result.functions[0].isExported).toBe(true); | |
| }); | |
| it('extracts a generator function', () => { | |
| const content = `function* generate(items) { | |
| for (const item of items) { | |
| yield item; | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('generate'); | |
| expect(result.functions[0].params).toEqual(['items']); | |
| }); | |
| }); | |
| describe('arrow functions', () => { | |
| it('extracts a const arrow function', () => { | |
| const content = `const add = (a, b) => { | |
| return a + b; | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('add'); | |
| expect(result.functions[0].params).toEqual(['a', 'b']); | |
| expect(result.functions[0].isExported).toBe(false); | |
| }); | |
| it('extracts an exported arrow function', () => { | |
| const content = `export const multiply = (x, y) => { | |
| return x * y; | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('multiply'); | |
| expect(result.functions[0].isExported).toBe(true); | |
| expect(result.exports).toContain('multiply'); | |
| }); | |
| it('extracts an async arrow function', () => { | |
| const content = `const loadData = async (id) => { | |
| const res = await fetch('/api/' + id); | |
| return res.json(); | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('loadData'); | |
| expect(result.functions[0].params).toEqual(['id']); | |
| }); | |
| it('extracts an arrow function with no params', () => { | |
| const content = `const noop = () => { | |
| // do nothing | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('noop'); | |
| expect(result.functions[0].params).toEqual([]); | |
| }); | |
| it('extracts arrow function with TypeScript type annotations', () => { | |
| const content = `export const process = (input: string, count: number): boolean => { | |
| return input.length > count; | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('process'); | |
| expect(result.functions[0].params).toEqual(['input', 'count']); | |
| }); | |
| }); | |
| describe('class declarations', () => { | |
| it('extracts a class with methods', () => { | |
| const content = `class UserService { | |
| constructor(db) { | |
| this.db = db; | |
| } | |
| getUser(id) { | |
| return this.db.find(id); | |
| } | |
| async createUser(data) { | |
| return this.db.insert(data); | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('UserService'); | |
| expect(result.classes[0].isExported).toBe(false); | |
| expect(result.classes[0].methods).toContain('constructor'); | |
| expect(result.classes[0].methods).toContain('getUser'); | |
| expect(result.classes[0].methods).toContain('createUser'); | |
| expect(result.classes[0].startLine).toBe(1); | |
| }); | |
| it('extracts an exported class', () => { | |
| const content = `export class ApiClient { | |
| fetch(url) { | |
| return fetch(url); | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('ApiClient'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('fetch'); | |
| expect(result.exports).toContain('ApiClient'); | |
| }); | |
| it('extracts an export default class', () => { | |
| const content = `export default class App { | |
| run() { | |
| console.log('running'); | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('App'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| }); | |
| it('extracts an abstract class', () => { | |
| const content = `abstract class BaseRepository { | |
| abstract find(id); | |
| save(entity) { | |
| return this.persist(entity); | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('BaseRepository'); | |
| expect(result.classes[0].methods).toContain('find'); | |
| expect(result.classes[0].methods).toContain('save'); | |
| }); | |
| it('extracts getter and setter methods', () => { | |
| const content = `class Config { | |
| get value() { | |
| return this._value; | |
| } | |
| set value(v) { | |
| this._value = v; | |
| } | |
| }`; | |
| const result = parseTypeScript(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].methods).toContain('value'); | |
| }); | |
| }); | |
| describe('import statements', () => { | |
| it('extracts named imports', () => { | |
| const content = `import { readFile, writeFile } from 'fs';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('fs'); | |
| expect(result.imports[0].names).toEqual(['readFile', 'writeFile']); | |
| }); | |
| it('extracts default imports', () => { | |
| const content = `import express from 'express';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('express'); | |
| expect(result.imports[0].names).toEqual(['express']); | |
| }); | |
| it('extracts namespace imports', () => { | |
| const content = `import * as path from 'path';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('path'); | |
| expect(result.imports[0].names).toEqual(['path']); | |
| }); | |
| it('extracts side-effect imports', () => { | |
| const content = `import './styles.css';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('./styles.css'); | |
| expect(result.imports[0].names).toEqual([]); | |
| }); | |
| it('extracts require calls', () => { | |
| const content = `const fs = require('fs');`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('fs'); | |
| expect(result.imports[0].names).toEqual(['fs']); | |
| }); | |
| it('extracts destructured require calls', () => { | |
| const content = `const { join, resolve } = require('path');`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('path'); | |
| expect(result.imports[0].names).toEqual(['join', 'resolve']); | |
| }); | |
| it('extracts import with alias', () => { | |
| const content = `import { readFile as read, writeFile as write } from 'fs';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('fs'); | |
| expect(result.imports[0].names).toEqual(['read', 'write']); | |
| }); | |
| it('extracts mixed default and named imports', () => { | |
| const content = `import React, { useState, useEffect } from 'react';`; | |
| const result = parseTypeScript(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('react'); | |
| expect(result.imports[0].names).toContain('React'); | |
| expect(result.imports[0].names).toContain('useState'); | |
| expect(result.imports[0].names).toContain('useEffect'); | |
| }); | |
| }); | |
| describe('export statements', () => { | |
| it('extracts named exports', () => { | |
| const content = `const a = 1; | |
| const b = 2; | |
| export { a, b };`; | |
| const result = parseTypeScript(content); | |
| expect(result.exports).toContain('a'); | |
| expect(result.exports).toContain('b'); | |
| }); | |
| it('extracts export default identifier', () => { | |
| const content = `const config = {}; | |
| export default config;`; | |
| const result = parseTypeScript(content); | |
| expect(result.exports).toContain('config'); | |
| }); | |
| it('extracts export const/let/var', () => { | |
| const content = `export const API_URL = 'http://localhost'; | |
| export let counter = 0; | |
| export var legacy = true;`; | |
| const result = parseTypeScript(content); | |
| expect(result.exports).toContain('API_URL'); | |
| expect(result.exports).toContain('counter'); | |
| expect(result.exports).toContain('legacy'); | |
| }); | |
| it('extracts re-exports with alias', () => { | |
| const content = `export { default as MyComponent } from './component';`; | |
| const result = parseTypeScript(content); | |
| expect(result.exports).toContain('MyComponent'); | |
| }); | |
| }); | |
| describe('mixed content', () => { | |
| it('handles a file with multiple declarations', () => { | |
| const content = `import { readFileSync } from 'fs'; | |
| import path from 'path'; | |
| export interface Config { | |
| name: string; | |
| } | |
| export function loadConfig(filePath) { | |
| const content = readFileSync(filePath, 'utf-8'); | |
| return JSON.parse(content); | |
| } | |
| const DEFAULT_PATH = './config.json'; | |
| export const getDefaultConfig = () => { | |
| return loadConfig(DEFAULT_PATH); | |
| } | |
| export class ConfigManager { | |
| constructor(config) { | |
| this.config = config; | |
| } | |
| get(key) { | |
| return this.config[key]; | |
| } | |
| set(key, value) { | |
| this.config[key] = value; | |
| } | |
| } | |
| export default ConfigManager;`; | |
| const result = parseTypeScript(content); | |
| // Imports | |
| expect(result.imports).toHaveLength(2); | |
| expect(result.imports[0].source).toBe('fs'); | |
| expect(result.imports[1].source).toBe('path'); | |
| // Functions | |
| expect(result.functions.length).toBeGreaterThanOrEqual(2); | |
| const loadConfigFn = result.functions.find((f) => f.name === 'loadConfig'); | |
| expect(loadConfigFn).toBeDefined(); | |
| expect(loadConfigFn.isExported).toBe(true); | |
| const getDefaultFn = result.functions.find((f) => f.name === 'getDefaultConfig'); | |
| expect(getDefaultFn).toBeDefined(); | |
| expect(getDefaultFn.isExported).toBe(true); | |
| // Classes | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('ConfigManager'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('constructor'); | |
| expect(result.classes[0].methods).toContain('get'); | |
| expect(result.classes[0].methods).toContain('set'); | |
| // Exports | |
| expect(result.exports).toContain('loadConfig'); | |
| expect(result.exports).toContain('getDefaultConfig'); | |
| expect(result.exports).toContain('ConfigManager'); | |
| }); | |
| it('handles empty content', () => { | |
| const result = parseTypeScript(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| it('handles content with only comments', () => { | |
| const content = `// This is a comment | |
| /* Multi-line | |
| comment */ | |
| // Another comment`; | |
| const result = parseTypeScript(content); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |
| import { parsePython } from './parser'; | |
| describe('parsePython', () => { | |
| describe('function definitions', () => { | |
| it('extracts a regular function definition', () => { | |
| const content = `def greet(name): | |
| return 'hello ' + name | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('greet'); | |
| expect(result.functions[0].params).toEqual(['name']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| expect(result.functions[0].startLine).toBe(1); | |
| expect(result.functions[0].endLine).toBe(2); | |
| }); | |
| it('extracts an async function definition', () => { | |
| const content = `async def fetch_data(url, timeout=30): | |
| response = await aiohttp.get(url, timeout=timeout) | |
| return response.json() | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('fetch_data'); | |
| expect(result.functions[0].params).toEqual(['url', 'timeout']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| }); | |
| it('extracts function with type annotations', () => { | |
| const content = `def process(input: str, count: int = 0) -> bool: | |
| return len(input) > count | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('process'); | |
| expect(result.functions[0].params).toEqual(['input', 'count']); | |
| }); | |
| it('marks private functions (underscore prefix) as not exported', () => { | |
| const content = `def _helper(x): | |
| return x * 2 | |
| def public_func(y): | |
| return _helper(y) | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(2); | |
| const helper = result.functions.find((f) => f.name === '_helper'); | |
| expect(helper).toBeDefined(); | |
| expect(helper.isExported).toBe(false); | |
| const pub = result.functions.find((f) => f.name === 'public_func'); | |
| expect(pub).toBeDefined(); | |
| expect(pub.isExported).toBe(true); | |
| }); | |
| it('extracts function with *args and **kwargs', () => { | |
| const content = `def variadic(a, *args, **kwargs): | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].params).toEqual(['a', 'args', 'kwargs']); | |
| }); | |
| it('handles multiple functions', () => { | |
| const content = `def first(): | |
| pass | |
| def second(x): | |
| return x | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(2); | |
| expect(result.functions[0].name).toBe('first'); | |
| expect(result.functions[1].name).toBe('second'); | |
| }); | |
| }); | |
| describe('class definitions', () => { | |
| it('extracts a class with methods', () => { | |
| const content = `class UserService: | |
| def __init__(self, db): | |
| self.db = db | |
| def get_user(self, user_id): | |
| return self.db.find(user_id) | |
| async def create_user(self, data): | |
| return await self.db.insert(data) | |
| `; | |
| const result = parsePython(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('UserService'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('__init__'); | |
| expect(result.classes[0].methods).toContain('get_user'); | |
| expect(result.classes[0].methods).toContain('create_user'); | |
| expect(result.classes[0].startLine).toBe(1); | |
| }); | |
| it('extracts a class with inheritance', () => { | |
| const content = `class ApiClient(BaseClient): | |
| def fetch(self, url): | |
| return self.session.get(url) | |
| `; | |
| const result = parsePython(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('ApiClient'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('fetch'); | |
| }); | |
| it('marks private classes as not exported', () => { | |
| const content = `class _InternalHelper: | |
| def do_work(self): | |
| pass | |
| class PublicApi: | |
| def run(self): | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.classes).toHaveLength(2); | |
| const internal = result.classes.find((c) => c.name === '_InternalHelper'); | |
| expect(internal).toBeDefined(); | |
| expect(internal.isExported).toBe(false); | |
| const pub = result.classes.find((c) => c.name === 'PublicApi'); | |
| expect(pub).toBeDefined(); | |
| expect(pub.isExported).toBe(true); | |
| }); | |
| it('extracts class with multiple base classes', () => { | |
| const content = `class MyView(View, Mixin): | |
| def get(self, request): | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('MyView'); | |
| expect(result.classes[0].methods).toContain('get'); | |
| }); | |
| }); | |
| describe('import statements', () => { | |
| it('extracts simple import', () => { | |
| const content = `import os`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('os'); | |
| expect(result.imports[0].names).toEqual(['os']); | |
| }); | |
| it('extracts import with alias', () => { | |
| const content = `import numpy as np`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('numpy'); | |
| expect(result.imports[0].names).toEqual(['np']); | |
| }); | |
| it('extracts from...import', () => { | |
| const content = `from os.path import join, dirname`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('os.path'); | |
| expect(result.imports[0].names).toEqual(['join', 'dirname']); | |
| }); | |
| it('extracts from...import with alias', () => { | |
| const content = `from collections import OrderedDict as OD`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('collections'); | |
| expect(result.imports[0].names).toEqual(['OD']); | |
| }); | |
| it('extracts from...import wildcard', () => { | |
| const content = `from module import *`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('module'); | |
| expect(result.imports[0].names).toEqual(['*']); | |
| }); | |
| it('extracts multi-line from...import with parentheses', () => { | |
| const content = `from flask import ( | |
| Flask, | |
| request, | |
| jsonify | |
| )`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('flask'); | |
| expect(result.imports[0].names).toEqual(['Flask', 'request', 'jsonify']); | |
| }); | |
| it('extracts multiple comma-separated imports', () => { | |
| const content = `import os, sys`; | |
| const result = parsePython(content); | |
| expect(result.imports).toHaveLength(2); | |
| expect(result.imports[0].source).toBe('os'); | |
| expect(result.imports[0].names).toEqual(['os']); | |
| expect(result.imports[1].source).toBe('sys'); | |
| expect(result.imports[1].names).toEqual(['sys']); | |
| }); | |
| }); | |
| describe('indentation-based end detection', () => { | |
| it('detects function end based on indentation', () => { | |
| const content = `def first(): | |
| x = 1 | |
| return x | |
| def second(): | |
| return 2 | |
| `; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(2); | |
| expect(result.functions[0].name).toBe('first'); | |
| expect(result.functions[0].startLine).toBe(1); | |
| expect(result.functions[0].endLine).toBe(3); | |
| expect(result.functions[1].name).toBe('second'); | |
| expect(result.functions[1].startLine).toBe(5); | |
| expect(result.functions[1].endLine).toBe(6); | |
| }); | |
| it('detects class end based on indentation', () => { | |
| const content = `class MyClass: | |
| def method_a(self): | |
| return 1 | |
| def method_b(self): | |
| return 2 | |
| def standalone(): | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].startLine).toBe(1); | |
| expect(result.classes[0].endLine).toBe(6); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('standalone'); | |
| }); | |
| it('handles function at end of file without trailing newline', () => { | |
| const content = `def last_func(): | |
| return 42`; | |
| const result = parsePython(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].endLine).toBe(2); | |
| }); | |
| }); | |
| describe('exports and __all__', () => { | |
| it('collects public names as exports by default', () => { | |
| const content = `def public_one(): | |
| pass | |
| def _private(): | |
| pass | |
| class PublicClass: | |
| pass | |
| class _PrivateClass: | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.exports).toContain('public_one'); | |
| expect(result.exports).toContain('PublicClass'); | |
| expect(result.exports).not.toContain('_private'); | |
| expect(result.exports).not.toContain('_PrivateClass'); | |
| }); | |
| it('uses __all__ when defined', () => { | |
| const content = `__all__ = ['exported_func', 'ExportedClass'] | |
| def exported_func(): | |
| pass | |
| def other_func(): | |
| pass | |
| class ExportedClass: | |
| pass | |
| `; | |
| const result = parsePython(content); | |
| expect(result.exports).toEqual(['exported_func', 'ExportedClass']); | |
| }); | |
| }); | |
| describe('mixed content', () => { | |
| it('handles a typical Python module', () => { | |
| const content = `import os | |
| from pathlib import Path | |
| class Config: | |
| def __init__(self, path: str): | |
| self.path = path | |
| def load(self): | |
| return Path(self.path).read_text() | |
| def create_config(path: str) -> Config: | |
| return Config(path) | |
| def _validate(data): | |
| return bool(data) | |
| `; | |
| const result = parsePython(content); | |
| // Imports | |
| expect(result.imports).toHaveLength(2); | |
| expect(result.imports[0].source).toBe('os'); | |
| expect(result.imports[1].source).toBe('pathlib'); | |
| expect(result.imports[1].names).toEqual(['Path']); | |
| // Classes | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Config'); | |
| expect(result.classes[0].methods).toContain('__init__'); | |
| expect(result.classes[0].methods).toContain('load'); | |
| // Functions (only top-level, not class methods) | |
| const topFuncs = result.functions; | |
| expect(topFuncs).toHaveLength(2); | |
| expect(topFuncs[0].name).toBe('create_config'); | |
| expect(topFuncs[1].name).toBe('_validate'); | |
| // Exports | |
| expect(result.exports).toContain('Config'); | |
| expect(result.exports).toContain('create_config'); | |
| expect(result.exports).not.toContain('_validate'); | |
| }); | |
| it('handles empty content', () => { | |
| const result = parsePython(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| it('handles content with only comments', () => { | |
| const content = `# This is a comment | |
| # Another comment | |
| """ | |
| Module docstring | |
| """`; | |
| const result = parsePython(content); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |
| import { parseGo, parseRust, parseJava, parseGeneric } from './parser'; | |
| describe('parseGo', () => { | |
| describe('function declarations', () => { | |
| it('extracts a regular function', () => { | |
| const content = `func main() { | |
| fmt.Println("hello") | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('main'); | |
| expect(result.functions[0].params).toEqual([]); | |
| expect(result.functions[0].startLine).toBe(1); | |
| expect(result.functions[0].endLine).toBe(3); | |
| }); | |
| it('extracts a function with parameters', () => { | |
| const content = `func Add(a int, b int) int { | |
| return a + b | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('Add'); | |
| expect(result.functions[0].params).toEqual(['a', 'b']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| }); | |
| it('extracts a method with receiver', () => { | |
| const content = `func (s *Server) Start(port int) error { | |
| return s.listen(port) | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('Start'); | |
| expect(result.functions[0].params).toEqual(['port']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| }); | |
| it('marks lowercase functions as unexported', () => { | |
| const content = `func helper(x int) int { | |
| return x * 2 | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.functions[0].isExported).toBe(false); | |
| expect(result.exports).not.toContain('helper'); | |
| }); | |
| it('marks uppercase functions as exported', () => { | |
| const content = `func HandleRequest(w http.ResponseWriter, r *http.Request) { | |
| w.Write([]byte("ok")) | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.functions[0].isExported).toBe(true); | |
| expect(result.exports).toContain('HandleRequest'); | |
| }); | |
| }); | |
| describe('type declarations', () => { | |
| it('extracts a struct type', () => { | |
| const content = `type Server struct { | |
| host string | |
| port int | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Server'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].startLine).toBe(1); | |
| expect(result.classes[0].endLine).toBe(4); | |
| }); | |
| it('extracts an interface type with methods', () => { | |
| const content = `type Reader interface { | |
| Read(p []byte) (int, error) | |
| Close() error | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Reader'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('Read'); | |
| expect(result.classes[0].methods).toContain('Close'); | |
| }); | |
| it('marks lowercase types as unexported', () => { | |
| const content = `type config struct { | |
| name string | |
| }`; | |
| const result = parseGo(content); | |
| expect(result.classes[0].isExported).toBe(false); | |
| expect(result.exports).not.toContain('config'); | |
| }); | |
| }); | |
| describe('import statements', () => { | |
| it('extracts a single import', () => { | |
| const content = `import "fmt"`; | |
| const result = parseGo(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('fmt'); | |
| expect(result.imports[0].names).toEqual(['fmt']); | |
| }); | |
| it('extracts a block import', () => { | |
| const content = `import ( | |
| "fmt" | |
| "net/http" | |
| "os" | |
| )`; | |
| const result = parseGo(content); | |
| expect(result.imports).toHaveLength(3); | |
| expect(result.imports[0].source).toBe('fmt'); | |
| expect(result.imports[1].source).toBe('net/http'); | |
| expect(result.imports[1].names).toEqual(['http']); | |
| expect(result.imports[2].source).toBe('os'); | |
| }); | |
| it('extracts aliased imports', () => { | |
| const content = `import ( | |
| mux "github.com/gorilla/mux" | |
| )`; | |
| const result = parseGo(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('github.com/gorilla/mux'); | |
| expect(result.imports[0].names).toEqual(['mux']); | |
| }); | |
| }); | |
| describe('empty content', () => { | |
| it('handles empty content', () => { | |
| const result = parseGo(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |
| describe('parseRust', () => { | |
| describe('function declarations', () => { | |
| it('extracts a public function', () => { | |
| const content = `pub fn add(a: i32, b: i32) -> i32 { | |
| a + b | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('add'); | |
| expect(result.functions[0].params).toEqual(['a', 'b']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| expect(result.functions[0].startLine).toBe(1); | |
| expect(result.functions[0].endLine).toBe(3); | |
| }); | |
| it('extracts a private function', () => { | |
| const content = `fn helper(x: u32) -> u32 { | |
| x * 2 | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('helper'); | |
| expect(result.functions[0].isExported).toBe(false); | |
| }); | |
| it('extracts an async function', () => { | |
| const content = `pub async fn fetch_data(url: &str) -> Result<String, Error> { | |
| reqwest::get(url).await?.text().await | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('fetch_data'); | |
| expect(result.functions[0].params).toEqual(['url']); | |
| expect(result.functions[0].isExported).toBe(true); | |
| }); | |
| }); | |
| describe('struct and enum declarations', () => { | |
| it('extracts a public struct', () => { | |
| const content = `pub struct Config { | |
| pub name: String, | |
| pub port: u16, | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Config'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.exports).toContain('Config'); | |
| }); | |
| it('extracts an enum', () => { | |
| const content = `pub enum Status { | |
| Active, | |
| Inactive, | |
| Pending, | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Status'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| }); | |
| it('extracts a private struct', () => { | |
| const content = `struct Internal { | |
| data: Vec<u8>, | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Internal'); | |
| expect(result.classes[0].isExported).toBe(false); | |
| }); | |
| }); | |
| describe('impl blocks', () => { | |
| it('extracts methods from impl block', () => { | |
| const content = `pub struct Server { | |
| port: u16, | |
| } | |
| impl Server { | |
| pub fn new(port: u16) -> Self { | |
| Server { port } | |
| } | |
| pub fn start(&self) { | |
| println!("Starting on port {}", self.port); | |
| } | |
| fn internal_helper(&self) { | |
| // private | |
| } | |
| }`; | |
| const result = parseRust(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Server'); | |
| expect(result.classes[0].methods).toContain('new'); | |
| expect(result.classes[0].methods).toContain('start'); | |
| expect(result.classes[0].methods).toContain('internal_helper'); | |
| }); | |
| }); | |
| describe('use statements', () => { | |
| it('extracts a simple use statement', () => { | |
| const content = `use std::collections::HashMap;`; | |
| const result = parseRust(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('std::collections::HashMap'); | |
| expect(result.imports[0].names).toEqual(['HashMap']); | |
| }); | |
| it('extracts grouped use statements', () => { | |
| const content = `use std::collections::{HashMap, HashSet};`; | |
| const result = parseRust(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('std::collections'); | |
| expect(result.imports[0].names).toEqual(['HashMap', 'HashSet']); | |
| }); | |
| it('extracts crate use statements', () => { | |
| const content = `use crate::config::Settings;`; | |
| const result = parseRust(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('crate::config::Settings'); | |
| expect(result.imports[0].names).toEqual(['Settings']); | |
| }); | |
| }); | |
| describe('empty content', () => { | |
| it('handles empty content', () => { | |
| const result = parseRust(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |
| describe('parseJava', () => { | |
| describe('class declarations', () => { | |
| it('extracts a public class with methods', () => { | |
| const content = `public class UserService { | |
| public User getUser(int id) { | |
| return db.find(id); | |
| } | |
| private void validate(User user) { | |
| // validation logic | |
| } | |
| }`; | |
| const result = parseJava(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('UserService'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| expect(result.classes[0].methods).toContain('getUser'); | |
| expect(result.classes[0].methods).toContain('validate'); | |
| }); | |
| it('extracts an interface', () => { | |
| const content = `public interface Repository { | |
| Object find(int id); | |
| void save(Object entity); | |
| }`; | |
| const result = parseJava(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('Repository'); | |
| expect(result.classes[0].isExported).toBe(true); | |
| }); | |
| it('extracts a private class', () => { | |
| const content = `class InternalHelper { | |
| void doWork() { | |
| // work | |
| } | |
| }`; | |
| const result = parseJava(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('InternalHelper'); | |
| expect(result.classes[0].isExported).toBe(false); | |
| }); | |
| }); | |
| describe('method extraction', () => { | |
| it('extracts public methods as exported functions', () => { | |
| const content = `public class Api { | |
| public String getData(String key) { | |
| return map.get(key); | |
| } | |
| public void setData(String key, String value) { | |
| map.put(key, value); | |
| } | |
| }`; | |
| const result = parseJava(content); | |
| const getDataFn = result.functions.find((f) => f.name === 'getData'); | |
| expect(getDataFn).toBeDefined(); | |
| expect(getDataFn.isExported).toBe(true); | |
| expect(getDataFn.params).toEqual(['key']); | |
| const setDataFn = result.functions.find((f) => f.name === 'setData'); | |
| expect(setDataFn).toBeDefined(); | |
| expect(setDataFn.params).toEqual(['key', 'value']); | |
| }); | |
| }); | |
| describe('import statements', () => { | |
| it('extracts import statements', () => { | |
| const content = `import java.util.List; | |
| import java.util.Map; | |
| import com.example.service.UserService;`; | |
| const result = parseJava(content); | |
| expect(result.imports).toHaveLength(3); | |
| expect(result.imports[0].source).toBe('java.util.List'); | |
| expect(result.imports[0].names).toEqual(['List']); | |
| expect(result.imports[1].source).toBe('java.util.Map'); | |
| expect(result.imports[1].names).toEqual(['Map']); | |
| expect(result.imports[2].source).toBe('com.example.service.UserService'); | |
| expect(result.imports[2].names).toEqual(['UserService']); | |
| }); | |
| it('extracts static imports', () => { | |
| const content = `import static org.junit.Assert.assertEquals;`; | |
| const result = parseJava(content); | |
| expect(result.imports).toHaveLength(1); | |
| expect(result.imports[0].source).toBe('org.junit.Assert.assertEquals'); | |
| expect(result.imports[0].names).toEqual(['assertEquals']); | |
| }); | |
| }); | |
| describe('empty content', () => { | |
| it('handles empty content', () => { | |
| const result = parseJava(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |
| describe('parseGeneric', () => { | |
| describe('PHP patterns', () => { | |
| it('extracts PHP functions', () => { | |
| const content = `<?php | |
| function calculateTotal($items, $tax) { | |
| $sum = 0; | |
| foreach ($items as $item) { | |
| $sum += $item; | |
| } | |
| return $sum * (1 + $tax); | |
| }`; | |
| const result = parseGeneric(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('calculateTotal'); | |
| expect(result.functions[0].params).toEqual(['items', 'tax']); | |
| }); | |
| it('extracts PHP classes', () => { | |
| const content = `<?php | |
| class UserController { | |
| public function index() { | |
| return view('users.index'); | |
| } | |
| public function store($request) { | |
| // store logic | |
| } | |
| }`; | |
| const result = parseGeneric(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('UserController'); | |
| expect(result.classes[0].methods).toContain('index'); | |
| expect(result.classes[0].methods).toContain('store'); | |
| }); | |
| it('extracts PHP use statements', () => { | |
| const content = `<?php | |
| use App\\Models\\User; | |
| use Illuminate\\Http\\Request;`; | |
| const result = parseGeneric(content); | |
| expect(result.imports).toHaveLength(2); | |
| expect(result.imports[0].names).toEqual(['User']); | |
| expect(result.imports[1].names).toEqual(['Request']); | |
| }); | |
| }); | |
| describe('Ruby patterns', () => { | |
| it('extracts Ruby methods', () => { | |
| const content = `def calculate(x, y) | |
| x + y | |
| end`; | |
| const result = parseGeneric(content); | |
| expect(result.functions).toHaveLength(1); | |
| expect(result.functions[0].name).toBe('calculate'); | |
| expect(result.functions[0].params).toEqual(['x', 'y']); | |
| }); | |
| it('extracts Ruby classes', () => { | |
| const content = `class UserService | |
| def initialize(db) | |
| @db = db | |
| end | |
| def find_user(id) | |
| @db.find(id) | |
| end | |
| end`; | |
| const result = parseGeneric(content); | |
| expect(result.classes).toHaveLength(1); | |
| expect(result.classes[0].name).toBe('UserService'); | |
| expect(result.classes[0].methods).toContain('initialize'); | |
| expect(result.classes[0].methods).toContain('find_user'); | |
| }); | |
| it('extracts Ruby require statements', () => { | |
| const content = `require 'json' | |
| require_relative 'helpers/utils'`; | |
| const result = parseGeneric(content); | |
| expect(result.imports).toHaveLength(2); | |
| expect(result.imports[0].source).toBe('json'); | |
| expect(result.imports[0].names).toEqual(['json']); | |
| expect(result.imports[1].source).toBe('helpers/utils'); | |
| expect(result.imports[1].names).toEqual(['utils']); | |
| }); | |
| }); | |
| describe('fallback behavior', () => { | |
| it('returns empty result for unrecognized content', () => { | |
| const content = `-- This is some SQL or unknown language | |
| SELECT * FROM users WHERE id = 1;`; | |
| const result = parseGeneric(content); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| it('handles empty content', () => { | |
| const result = parseGeneric(''); | |
| expect(result.functions).toEqual([]); | |
| expect(result.classes).toEqual([]); | |
| expect(result.imports).toEqual([]); | |
| expect(result.exports).toEqual([]); | |
| }); | |
| }); | |
| }); | |