Enhances code extraction and project indexing
Adds support for Dart and Liquid languages with tree-sitter parsing. Improves accuracy of code symbol extraction for existing languages. Indexes project files to enhance code navigation features. Migrates build system to facilitate code contributions. Removes git hook functionality. Integrates Sentry for error tracking and reporting. Enhances project initialization and configuration loading.
This commit is contained in:
@@ -83,6 +83,10 @@ describe('Language Detection', () => {
|
||||
expect(detectLanguage('build.gradle.kts')).toBe('kotlin');
|
||||
});
|
||||
|
||||
it('should detect Dart files', () => {
|
||||
expect(detectLanguage('main.dart')).toBe('dart');
|
||||
});
|
||||
|
||||
it('should return unknown for unsupported extensions', () => {
|
||||
expect(detectLanguage('styles.css')).toBe('unknown');
|
||||
expect(detectLanguage('data.json')).toBe('unknown');
|
||||
@@ -110,6 +114,7 @@ describe('Language Support', () => {
|
||||
expect(languages).toContain('ruby');
|
||||
expect(languages).toContain('swift');
|
||||
expect(languages).toContain('kotlin');
|
||||
expect(languages).toContain('dart');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -532,6 +537,950 @@ suspend fun loadData(): List<String> {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dart Extraction', () => {
|
||||
it('should extract class declarations', () => {
|
||||
const code = `
|
||||
class UserService {
|
||||
final Database _db;
|
||||
|
||||
Future<User> findById(String id) async {
|
||||
return await _db.query(id);
|
||||
}
|
||||
|
||||
void _privateMethod() {}
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('service.dart', code);
|
||||
|
||||
const classNode = result.nodes.find((n) => n.kind === 'class');
|
||||
expect(classNode).toBeDefined();
|
||||
expect(classNode?.name).toBe('UserService');
|
||||
expect(classNode?.visibility).toBe('public');
|
||||
|
||||
const methodNodes = result.nodes.filter((n) => n.kind === 'method');
|
||||
expect(methodNodes.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
const findById = methodNodes.find((m) => m.name === 'findById');
|
||||
expect(findById).toBeDefined();
|
||||
expect(findById?.isAsync).toBe(true);
|
||||
|
||||
const privateMethod = methodNodes.find((m) => m.name === '_privateMethod');
|
||||
expect(privateMethod).toBeDefined();
|
||||
expect(privateMethod?.visibility).toBe('private');
|
||||
});
|
||||
|
||||
it('should extract top-level function declarations', () => {
|
||||
const code = `
|
||||
void topLevelFunction(String name) {
|
||||
print(name);
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('utils.dart', code);
|
||||
|
||||
const funcNode = result.nodes.find((n) => n.kind === 'function');
|
||||
expect(funcNode).toBeDefined();
|
||||
expect(funcNode?.name).toBe('topLevelFunction');
|
||||
expect(funcNode?.language).toBe('dart');
|
||||
});
|
||||
|
||||
it('should extract enum declarations', () => {
|
||||
const code = `
|
||||
enum Status { active, inactive, pending }
|
||||
`;
|
||||
const result = extractFromSource('models.dart', code);
|
||||
|
||||
const enumNode = result.nodes.find((n) => n.kind === 'enum');
|
||||
expect(enumNode).toBeDefined();
|
||||
expect(enumNode?.name).toBe('Status');
|
||||
});
|
||||
|
||||
it('should extract mixin declarations', () => {
|
||||
const code = `
|
||||
mixin LoggerMixin {
|
||||
void log(String message) {}
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('mixins.dart', code);
|
||||
|
||||
const classNode = result.nodes.find((n) => n.kind === 'class');
|
||||
expect(classNode).toBeDefined();
|
||||
expect(classNode?.name).toBe('LoggerMixin');
|
||||
|
||||
const methodNode = result.nodes.find((n) => n.kind === 'method');
|
||||
expect(methodNode).toBeDefined();
|
||||
expect(methodNode?.name).toBe('log');
|
||||
});
|
||||
|
||||
it('should extract extension declarations', () => {
|
||||
const code = `
|
||||
extension StringExt on String {
|
||||
bool get isBlank => trim().isEmpty;
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('extensions.dart', code);
|
||||
|
||||
const classNode = result.nodes.find((n) => n.kind === 'class');
|
||||
expect(classNode).toBeDefined();
|
||||
expect(classNode?.name).toBe('StringExt');
|
||||
});
|
||||
|
||||
it('should detect static methods', () => {
|
||||
const code = `
|
||||
class Utils {
|
||||
static void doWork() {}
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('utils.dart', code);
|
||||
|
||||
const methodNode = result.nodes.find((n) => n.kind === 'method');
|
||||
expect(methodNode).toBeDefined();
|
||||
expect(methodNode?.name).toBe('doWork');
|
||||
expect(methodNode?.isStatic).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect async functions', () => {
|
||||
const code = `
|
||||
Future<String> fetchData() async {
|
||||
return await http.get('/data');
|
||||
}
|
||||
`;
|
||||
const result = extractFromSource('api.dart', code);
|
||||
|
||||
const funcNode = result.nodes.find((n) => n.kind === 'function');
|
||||
expect(funcNode).toBeDefined();
|
||||
expect(funcNode?.name).toBe('fetchData');
|
||||
expect(funcNode?.isAsync).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect private visibility via underscore convention', () => {
|
||||
const code = `
|
||||
void _privateHelper() {}
|
||||
|
||||
void publicFunction() {}
|
||||
`;
|
||||
const result = extractFromSource('helpers.dart', code);
|
||||
|
||||
const functions = result.nodes.filter((n) => n.kind === 'function');
|
||||
const privateFunc = functions.find((f) => f.name === '_privateHelper');
|
||||
const publicFunc = functions.find((f) => f.name === 'publicFunction');
|
||||
|
||||
expect(privateFunc?.visibility).toBe('private');
|
||||
expect(publicFunc?.visibility).toBe('public');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Extraction', () => {
|
||||
describe('TypeScript/JavaScript imports', () => {
|
||||
it('should extract default imports', () => {
|
||||
const code = `import React from 'react';`;
|
||||
const result = extractFromSource('app.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('react');
|
||||
expect(importNode?.signature).toBe("import React from 'react';");
|
||||
});
|
||||
|
||||
it('should extract named imports', () => {
|
||||
const code = `import { Bug, Database } from '@phosphor-icons/react';`;
|
||||
const result = extractFromSource('icons.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('@phosphor-icons/react');
|
||||
expect(importNode?.signature).toContain('Bug');
|
||||
expect(importNode?.signature).toContain('Database');
|
||||
});
|
||||
|
||||
it('should extract namespace imports', () => {
|
||||
const code = `import * as Icons from '@phosphor-icons/react';`;
|
||||
const result = extractFromSource('icons.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('@phosphor-icons/react');
|
||||
expect(importNode?.signature).toContain('* as Icons');
|
||||
});
|
||||
|
||||
it('should extract side-effect imports', () => {
|
||||
const code = `import './styles.css';`;
|
||||
const result = extractFromSource('app.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('./styles.css');
|
||||
});
|
||||
|
||||
it('should extract mixed imports (default + named)', () => {
|
||||
const code = `import React, { useState, useEffect } from 'react';`;
|
||||
const result = extractFromSource('app.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('react');
|
||||
expect(importNode?.signature).toContain('React');
|
||||
expect(importNode?.signature).toContain('useState');
|
||||
expect(importNode?.signature).toContain('useEffect');
|
||||
});
|
||||
|
||||
it('should extract multiple import statements', () => {
|
||||
const code = `
|
||||
import React from 'react';
|
||||
import { Button } from './components';
|
||||
import './styles.css';
|
||||
`;
|
||||
const result = extractFromSource('app.tsx', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('react');
|
||||
expect(names).toContain('./components');
|
||||
expect(names).toContain('./styles.css');
|
||||
});
|
||||
|
||||
it('should extract type imports', () => {
|
||||
const code = `import type { FC, ReactNode } from 'react';`;
|
||||
const result = extractFromSource('types.ts', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('react');
|
||||
expect(importNode?.signature).toContain('type');
|
||||
expect(importNode?.signature).toContain('FC');
|
||||
});
|
||||
|
||||
it('should extract aliased named imports', () => {
|
||||
const code = `import { useState as useStateAlias } from 'react';`;
|
||||
const result = extractFromSource('hooks.ts', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('react');
|
||||
expect(importNode?.signature).toContain('useState');
|
||||
expect(importNode?.signature).toContain('useStateAlias');
|
||||
});
|
||||
|
||||
it('should extract relative path imports', () => {
|
||||
const code = `import { helper } from '../utils/helper';`;
|
||||
const result = extractFromSource('components/Button.tsx', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('../utils/helper');
|
||||
expect(importNode?.signature).toContain('helper');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Python imports', () => {
|
||||
it('should extract simple import statement', () => {
|
||||
const code = `import json`;
|
||||
const result = extractFromSource('utils.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('json');
|
||||
});
|
||||
|
||||
it('should extract from import statement', () => {
|
||||
const code = `from os import path`;
|
||||
const result = extractFromSource('utils.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('os');
|
||||
expect(importNode?.signature).toContain('path');
|
||||
});
|
||||
|
||||
it('should extract multiple imports from same module', () => {
|
||||
const code = `from typing import List, Dict, Optional`;
|
||||
const result = extractFromSource('types.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('typing');
|
||||
expect(importNode?.signature).toContain('List');
|
||||
expect(importNode?.signature).toContain('Dict');
|
||||
});
|
||||
|
||||
it('should extract multiple import statements', () => {
|
||||
const code = `
|
||||
import os
|
||||
import sys
|
||||
`;
|
||||
const result = extractFromSource('main.py', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(2);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('os');
|
||||
expect(names).toContain('sys');
|
||||
});
|
||||
|
||||
it('should extract aliased import', () => {
|
||||
const code = `import numpy as np`;
|
||||
const result = extractFromSource('data.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('numpy');
|
||||
expect(importNode?.signature).toContain('as np');
|
||||
});
|
||||
|
||||
it('should extract relative import', () => {
|
||||
const code = `from .utils import helper`;
|
||||
const result = extractFromSource('module.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('.utils');
|
||||
expect(importNode?.signature).toContain('helper');
|
||||
});
|
||||
|
||||
it('should extract wildcard import', () => {
|
||||
const code = `from typing import *`;
|
||||
const result = extractFromSource('types.py', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('typing');
|
||||
expect(importNode?.signature).toContain('*');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rust imports', () => {
|
||||
it('should extract simple use declaration', () => {
|
||||
const code = `use std::io;`;
|
||||
const result = extractFromSource('main.rs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('std');
|
||||
expect(importNode?.signature).toBe('use std::io;');
|
||||
});
|
||||
|
||||
it('should extract scoped use list', () => {
|
||||
const code = `use std::{ffi::OsStr, io, path::Path};`;
|
||||
const result = extractFromSource('main.rs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('std');
|
||||
expect(importNode?.signature).toContain('ffi::OsStr');
|
||||
expect(importNode?.signature).toContain('path::Path');
|
||||
});
|
||||
|
||||
it('should extract crate imports', () => {
|
||||
const code = `use crate::error::Error;`;
|
||||
const result = extractFromSource('lib.rs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('crate');
|
||||
});
|
||||
|
||||
it('should extract super imports', () => {
|
||||
const code = `use super::utils;`;
|
||||
const result = extractFromSource('submod.rs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('super');
|
||||
});
|
||||
|
||||
it('should extract external crate imports', () => {
|
||||
const code = `use serde::{Serialize, Deserialize};`;
|
||||
const result = extractFromSource('types.rs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('serde');
|
||||
expect(importNode?.signature).toContain('Serialize');
|
||||
expect(importNode?.signature).toContain('Deserialize');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Go imports', () => {
|
||||
it('should extract single import', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
`;
|
||||
const result = extractFromSource('main.go', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('fmt');
|
||||
});
|
||||
|
||||
it('should extract grouped imports', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"encoding/json"
|
||||
)
|
||||
`;
|
||||
const result = extractFromSource('main.go', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('fmt');
|
||||
expect(names).toContain('os');
|
||||
expect(names).toContain('encoding/json');
|
||||
});
|
||||
|
||||
it('should extract aliased import', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
import f "fmt"
|
||||
`;
|
||||
const result = extractFromSource('main.go', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('fmt');
|
||||
expect(importNode?.signature).toContain('f');
|
||||
});
|
||||
|
||||
it('should extract dot import', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
import . "math"
|
||||
`;
|
||||
const result = extractFromSource('main.go', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('math');
|
||||
expect(importNode?.signature).toContain('.');
|
||||
});
|
||||
|
||||
it('should extract blank import', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
`;
|
||||
const result = extractFromSource('main.go', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('github.com/go-sql-driver/mysql');
|
||||
expect(importNode?.signature).toContain('_');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Swift imports', () => {
|
||||
it('should extract simple import', () => {
|
||||
const code = `import Foundation`;
|
||||
const result = extractFromSource('main.swift', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('Foundation');
|
||||
expect(importNode?.signature).toBe('import Foundation');
|
||||
});
|
||||
|
||||
it('should extract @testable import', () => {
|
||||
const code = `@testable import Alamofire`;
|
||||
const result = extractFromSource('Tests.swift', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('Alamofire');
|
||||
expect(importNode?.signature).toContain('@testable');
|
||||
});
|
||||
|
||||
it('should extract @preconcurrency import', () => {
|
||||
const code = `@preconcurrency import Security`;
|
||||
const result = extractFromSource('Auth.swift', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('Security');
|
||||
});
|
||||
|
||||
it('should extract multiple imports', () => {
|
||||
const code = `
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Alamofire
|
||||
`;
|
||||
const result = extractFromSource('App.swift', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('Foundation');
|
||||
expect(names).toContain('UIKit');
|
||||
expect(names).toContain('Alamofire');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kotlin imports', () => {
|
||||
it('should extract simple import', () => {
|
||||
const code = `import java.io.IOException`;
|
||||
const result = extractFromSource('Main.kt', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.io.IOException');
|
||||
expect(importNode?.signature).toBe('import java.io.IOException');
|
||||
});
|
||||
|
||||
it('should extract aliased import', () => {
|
||||
const code = `import okhttp3.Request.Builder as RequestBuilder`;
|
||||
const result = extractFromSource('Utils.kt', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('okhttp3.Request.Builder');
|
||||
expect(importNode?.signature).toContain('as RequestBuilder');
|
||||
});
|
||||
|
||||
it('should extract wildcard import', () => {
|
||||
const code = `import java.util.concurrent.TimeUnit.*`;
|
||||
const result = extractFromSource('Time.kt', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.util.concurrent.TimeUnit');
|
||||
expect(importNode?.signature).toContain('.*');
|
||||
});
|
||||
|
||||
it('should extract multiple imports', () => {
|
||||
const code = `
|
||||
import java.io.IOException
|
||||
import kotlin.test.assertFailsWith
|
||||
import okhttp3.OkHttpClient
|
||||
`;
|
||||
const result = extractFromSource('Test.kt', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('java.io.IOException');
|
||||
expect(names).toContain('kotlin.test.assertFailsWith');
|
||||
expect(names).toContain('okhttp3.OkHttpClient');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Java imports', () => {
|
||||
it('should extract simple import', () => {
|
||||
const code = `import java.util.List;`;
|
||||
const result = extractFromSource('Main.java', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.util.List');
|
||||
expect(importNode?.signature).toBe('import java.util.List;');
|
||||
});
|
||||
|
||||
it('should extract static import', () => {
|
||||
const code = `import static java.util.Collections.emptyList;`;
|
||||
const result = extractFromSource('Utils.java', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.util.Collections.emptyList');
|
||||
expect(importNode?.signature).toContain('static');
|
||||
});
|
||||
|
||||
it('should extract wildcard import', () => {
|
||||
const code = `import java.util.*;`;
|
||||
const result = extractFromSource('App.java', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.util');
|
||||
expect(importNode?.signature).toContain('.*');
|
||||
});
|
||||
|
||||
it('should extract nested class import', () => {
|
||||
const code = `import java.util.Map.Entry;`;
|
||||
const result = extractFromSource('MapUtil.java', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('java.util.Map.Entry');
|
||||
});
|
||||
|
||||
it('should extract multiple imports', () => {
|
||||
const code = `
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
`;
|
||||
const result = extractFromSource('Service.java', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('java.util.List');
|
||||
expect(names).toContain('java.util.Map');
|
||||
expect(names).toContain('java.io.IOException');
|
||||
});
|
||||
});
|
||||
|
||||
describe('C# imports', () => {
|
||||
it('should extract simple using', () => {
|
||||
const code = `using System;`;
|
||||
const result = extractFromSource('Program.cs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('System');
|
||||
expect(importNode?.signature).toBe('using System;');
|
||||
});
|
||||
|
||||
it('should extract qualified using', () => {
|
||||
const code = `using System.Collections.Generic;`;
|
||||
const result = extractFromSource('Utils.cs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('System.Collections.Generic');
|
||||
});
|
||||
|
||||
it('should extract static using', () => {
|
||||
const code = `using static System.Console;`;
|
||||
const result = extractFromSource('App.cs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('System.Console');
|
||||
expect(importNode?.signature).toContain('static');
|
||||
});
|
||||
|
||||
it('should extract alias using', () => {
|
||||
const code = `using MyList = System.Collections.Generic.List<int>;`;
|
||||
const result = extractFromSource('Types.cs', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('System.Collections.Generic.List<int>');
|
||||
expect(importNode?.signature).toContain('MyList =');
|
||||
});
|
||||
|
||||
it('should extract multiple usings', () => {
|
||||
const code = `
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
`;
|
||||
const result = extractFromSource('Service.cs', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('System');
|
||||
expect(names).toContain('System.Threading.Tasks');
|
||||
expect(names).toContain('Microsoft.Extensions.DependencyInjection');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PHP imports', () => {
|
||||
it('should extract simple use', () => {
|
||||
const code = `<?php use PHPUnit\\Framework\\TestCase;`;
|
||||
const result = extractFromSource('Test.php', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('PHPUnit\\Framework\\TestCase');
|
||||
});
|
||||
|
||||
it('should extract aliased use', () => {
|
||||
const code = `<?php use Mockery as m;`;
|
||||
const result = extractFromSource('Test.php', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('Mockery');
|
||||
expect(importNode?.signature).toContain('as m');
|
||||
});
|
||||
|
||||
it('should extract function use', () => {
|
||||
const code = `<?php use function Illuminate\\Support\\env;`;
|
||||
const result = extractFromSource('helpers.php', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('Illuminate\\Support\\env');
|
||||
expect(importNode?.signature).toContain('function');
|
||||
});
|
||||
|
||||
it('should extract grouped use', () => {
|
||||
const code = `<?php use Illuminate\\Database\\{Model, Builder};`;
|
||||
const result = extractFromSource('Models.php', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(2);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('Illuminate\\Database\\Model');
|
||||
expect(names).toContain('Illuminate\\Database\\Builder');
|
||||
});
|
||||
|
||||
it('should extract multiple uses', () => {
|
||||
const code = `<?php
|
||||
use Illuminate\\Support\\Collection;
|
||||
use Illuminate\\Support\\Str;
|
||||
use Closure;
|
||||
`;
|
||||
const result = extractFromSource('Service.php', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('Illuminate\\Support\\Collection');
|
||||
expect(names).toContain('Illuminate\\Support\\Str');
|
||||
expect(names).toContain('Closure');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ruby imports', () => {
|
||||
it('should extract require', () => {
|
||||
const code = `require 'json'`;
|
||||
const result = extractFromSource('app.rb', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('json');
|
||||
expect(importNode?.signature).toBe("require 'json'");
|
||||
});
|
||||
|
||||
it('should extract require with path', () => {
|
||||
const code = `require 'active_support/core_ext/string'`;
|
||||
const result = extractFromSource('config.rb', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('active_support/core_ext/string');
|
||||
});
|
||||
|
||||
it('should extract require_relative', () => {
|
||||
const code = `require_relative '../test_helper'`;
|
||||
const result = extractFromSource('test/my_test.rb', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('../test_helper');
|
||||
expect(importNode?.signature).toContain('require_relative');
|
||||
});
|
||||
|
||||
it('should not extract non-require calls', () => {
|
||||
const code = `puts 'hello'`;
|
||||
const result = extractFromSource('app.rb', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should extract multiple requires', () => {
|
||||
const code = `
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
require_relative 'helper'
|
||||
`;
|
||||
const result = extractFromSource('lib.rb', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('json');
|
||||
expect(names).toContain('yaml');
|
||||
expect(names).toContain('helper');
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ imports', () => {
|
||||
it('should extract system include', () => {
|
||||
const code = `#include <iostream>`;
|
||||
const result = extractFromSource('main.cpp', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('iostream');
|
||||
expect(importNode?.signature).toBe('#include <iostream>');
|
||||
});
|
||||
|
||||
it('should extract system include with path', () => {
|
||||
const code = `#include <nlohmann/json.hpp>`;
|
||||
const result = extractFromSource('app.cpp', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('nlohmann/json.hpp');
|
||||
});
|
||||
|
||||
it('should extract local include', () => {
|
||||
const code = `#include "myheader.h"`;
|
||||
const result = extractFromSource('main.cpp', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('myheader.h');
|
||||
});
|
||||
|
||||
it('should extract C header', () => {
|
||||
const code = `#include <stdio.h>`;
|
||||
const result = extractFromSource('main.c', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('stdio.h');
|
||||
});
|
||||
|
||||
it('should extract multiple includes', () => {
|
||||
const code = `
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "config.h"
|
||||
`;
|
||||
const result = extractFromSource('app.cpp', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('iostream');
|
||||
expect(names).toContain('vector');
|
||||
expect(names).toContain('config.h');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dart imports', () => {
|
||||
it('should extract dart: import', () => {
|
||||
const code = `import 'dart:async';`;
|
||||
const result = extractFromSource('main.dart', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('dart:async');
|
||||
expect(importNode?.signature).toBe("import 'dart:async';");
|
||||
});
|
||||
|
||||
it('should extract package import', () => {
|
||||
const code = `import 'package:flutter/material.dart';`;
|
||||
const result = extractFromSource('app.dart', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('package:flutter/material.dart');
|
||||
});
|
||||
|
||||
it('should extract aliased import', () => {
|
||||
const code = `import 'package:http/http.dart' as http;`;
|
||||
const result = extractFromSource('api.dart', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('package:http/http.dart');
|
||||
expect(importNode?.signature).toContain('as http');
|
||||
});
|
||||
|
||||
it('should extract multiple imports', () => {
|
||||
const code = `
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
`;
|
||||
const result = extractFromSource('main.dart', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('dart:async');
|
||||
expect(names).toContain('dart:convert');
|
||||
expect(names).toContain('package:flutter/material.dart');
|
||||
});
|
||||
|
||||
it('should extract relative import', () => {
|
||||
const code = `import '../utils/helpers.dart';`;
|
||||
const result = extractFromSource('lib/main.dart', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('../utils/helpers.dart');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Liquid imports', () => {
|
||||
it('should extract render tag', () => {
|
||||
const code = `{% render 'loading-spinner' %}`;
|
||||
const result = extractFromSource('template.liquid', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('loading-spinner');
|
||||
expect(importNode?.signature).toContain('render');
|
||||
});
|
||||
|
||||
it('should extract section tag', () => {
|
||||
const code = `{% section 'header' %}`;
|
||||
const result = extractFromSource('layout/theme.liquid', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('header');
|
||||
expect(importNode?.signature).toContain('section');
|
||||
});
|
||||
|
||||
it('should extract include tag', () => {
|
||||
const code = `{% include 'icon-cart' %}`;
|
||||
const result = extractFromSource('snippets/header.liquid', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('icon-cart');
|
||||
expect(importNode?.signature).toContain('include');
|
||||
});
|
||||
|
||||
it('should extract render with whitespace control', () => {
|
||||
const code = `{%- render 'price' -%}`;
|
||||
const result = extractFromSource('snippets/product.liquid', code);
|
||||
|
||||
const importNode = result.nodes.find((n) => n.kind === 'import');
|
||||
expect(importNode).toBeDefined();
|
||||
expect(importNode?.name).toBe('price');
|
||||
});
|
||||
|
||||
it('should extract multiple imports', () => {
|
||||
const code = `
|
||||
{% section 'header' %}
|
||||
{% render 'loading-spinner' %}
|
||||
{% render 'cart-drawer' %}
|
||||
`;
|
||||
const result = extractFromSource('layout/theme.liquid', code);
|
||||
|
||||
const importNodes = result.nodes.filter((n) => n.kind === 'import');
|
||||
expect(importNodes.length).toBe(3);
|
||||
|
||||
const names = importNodes.map((n) => n.name);
|
||||
expect(names).toContain('header');
|
||||
expect(names).toContain('loading-spinner');
|
||||
expect(names).toContain('cart-drawer');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Full Indexing', () => {
|
||||
let tempDir: string;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user