Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0be3a16b45 | ||
|
|
4ef9384e2f |
@@ -365,6 +365,25 @@ const BUILT_IN_NAMES = new Set([
|
||||
'mutex_lock', 'mutex_unlock', 'mutex_init',
|
||||
'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
|
||||
'get', 'put',
|
||||
// C# / .NET built-ins (IMPORTANT: keep in sync with parse-worker.ts BUILT_INS)
|
||||
'Console', 'WriteLine', 'ReadLine', 'Write',
|
||||
'Task', 'Run', 'Wait', 'WhenAll', 'WhenAny', 'FromResult', 'Delay', 'ContinueWith',
|
||||
'ConfigureAwait', 'GetAwaiter', 'GetResult',
|
||||
'ToString', 'GetType', 'Equals', 'GetHashCode', 'ReferenceEquals',
|
||||
'Add', 'Remove', 'Contains', 'Clear', 'Count', 'Any', 'All',
|
||||
'Where', 'Select', 'SelectMany', 'OrderBy', 'OrderByDescending', 'GroupBy',
|
||||
'First', 'FirstOrDefault', 'Single', 'SingleOrDefault', 'Last', 'LastOrDefault',
|
||||
'ToList', 'ToArray', 'ToDictionary', 'AsEnumerable', 'AsQueryable',
|
||||
'Aggregate', 'Sum', 'Average', 'Min', 'Max', 'Distinct', 'Skip', 'Take',
|
||||
'String', 'Format', 'IsNullOrEmpty', 'IsNullOrWhiteSpace', 'Concat', 'Join',
|
||||
'Trim', 'TrimStart', 'TrimEnd', 'Split', 'Replace', 'StartsWith', 'EndsWith',
|
||||
'Convert', 'ToInt32', 'ToDouble', 'ToBoolean', 'ToByte',
|
||||
'Math', 'Abs', 'Ceiling', 'Floor', 'Round', 'Pow', 'Sqrt',
|
||||
'Dispose', 'Close',
|
||||
'TryParse', 'Parse',
|
||||
'AddRange', 'RemoveAt', 'RemoveAll', 'FindAll', 'Exists', 'TrueForAll',
|
||||
'ContainsKey', 'TryGetValue', 'AddOrUpdate',
|
||||
'Throw', 'ThrowIfNull',
|
||||
// Swift/iOS built-ins and standard library
|
||||
'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
|
||||
'assert', 'assertionFailure', 'NSLog',
|
||||
|
||||
@@ -63,10 +63,18 @@ const ENTRY_POINT_PATTERNS: Record<string, RegExp[]> = {
|
||||
|
||||
// C#
|
||||
'csharp': [
|
||||
/^(Get|Post|Put|Delete)/, // ASP.NET conventions
|
||||
/Action$/, // MVC actions
|
||||
/^On[A-Z]/, // Event handlers
|
||||
/Async$/, // Async entry points
|
||||
/^(Get|Post|Put|Delete|Patch)/, // ASP.NET action methods
|
||||
/Action$/, // MVC actions
|
||||
/^On[A-Z]/, // Event handlers / Blazor lifecycle
|
||||
/Async$/, // Async entry points
|
||||
/^Configure$/, // Startup.Configure
|
||||
/^ConfigureServices$/, // Startup.ConfigureServices
|
||||
/^Handle$/, // MediatR / generic handler
|
||||
/^Execute$/, // Command pattern
|
||||
/^Invoke$/, // Middleware Invoke
|
||||
/^Map[A-Z]/, // Minimal API MapGet, MapPost
|
||||
/Service$/, // Service classes
|
||||
/^Seed/, // Database seeding
|
||||
],
|
||||
|
||||
// Go
|
||||
@@ -296,8 +304,13 @@ export function isTestFile(filePath: string): boolean {
|
||||
p.endsWith('test.swift') ||
|
||||
p.includes('uitests/') ||
|
||||
// C# test patterns
|
||||
p.endsWith('tests.cs') ||
|
||||
p.endsWith('test.cs') ||
|
||||
p.includes('.tests/') ||
|
||||
p.includes('tests.cs') ||
|
||||
p.includes('.test/') ||
|
||||
p.includes('.integrationtests/') ||
|
||||
p.includes('.unittests/') ||
|
||||
p.includes('/testproject/') ||
|
||||
// PHP/Laravel test patterns
|
||||
p.endsWith('test.php') ||
|
||||
p.endsWith('spec.php') ||
|
||||
|
||||
@@ -184,6 +184,34 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
|
||||
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-controller-file' };
|
||||
}
|
||||
|
||||
// ASP.NET Services
|
||||
if ((p.includes('/services/') || p.includes('/service/')) && p.endsWith('.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 1.8, reason: 'aspnet-service' };
|
||||
}
|
||||
|
||||
// ASP.NET Middleware
|
||||
if (p.includes('/middleware/') && p.endsWith('.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 2.5, reason: 'aspnet-middleware' };
|
||||
}
|
||||
|
||||
// SignalR Hubs
|
||||
if (p.includes('/hubs/') && p.endsWith('.cs')) {
|
||||
return { framework: 'signalr', entryPointMultiplier: 2.5, reason: 'signalr-hub' };
|
||||
}
|
||||
if (p.endsWith('hub.cs')) {
|
||||
return { framework: 'signalr', entryPointMultiplier: 2.5, reason: 'signalr-hub-file' };
|
||||
}
|
||||
|
||||
// Minimal API / Program.cs / Startup.cs
|
||||
if (p.endsWith('/program.cs') || p.endsWith('/startup.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-entry' };
|
||||
}
|
||||
|
||||
// Background services / Hosted services
|
||||
if ((p.includes('/backgroundservices/') || p.includes('/hostedservices/')) && p.endsWith('.cs')) {
|
||||
return { framework: 'aspnet', entryPointMultiplier: 2.0, reason: 'aspnet-background-service' };
|
||||
}
|
||||
|
||||
// Blazor pages
|
||||
if (p.includes('/pages/') && p.endsWith('.razor')) {
|
||||
return { framework: 'blazor', entryPointMultiplier: 2.5, reason: 'blazor-page' };
|
||||
@@ -385,7 +413,11 @@ export const FRAMEWORK_AST_PATTERNS = {
|
||||
'jaxrs': ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
|
||||
|
||||
// C# attributes
|
||||
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[Route]'],
|
||||
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[HttpPut]', '[HttpDelete]',
|
||||
'[Route]', '[Authorize]', '[AllowAnonymous]'],
|
||||
'signalr': ['[HubMethodName]', ': Hub', ': Hub<'],
|
||||
'blazor': ['@page', '[Parameter]', '@inject'],
|
||||
'efcore': ['DbContext', 'DbSet<', 'OnModelCreating'],
|
||||
|
||||
// Go patterns (function signatures)
|
||||
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
||||
@@ -435,6 +467,9 @@ const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE: Record<string, AstFrameworkPatternConf
|
||||
],
|
||||
csharp: [
|
||||
{ framework: 'aspnet', entryPointMultiplier: 3.2, reason: 'aspnet-attribute', patterns: FRAMEWORK_AST_PATTERNS.aspnet },
|
||||
{ framework: 'signalr', entryPointMultiplier: 2.8, reason: 'signalr-attribute', patterns: FRAMEWORK_AST_PATTERNS.signalr },
|
||||
{ framework: 'blazor', entryPointMultiplier: 2.5, reason: 'blazor-attribute', patterns: FRAMEWORK_AST_PATTERNS.blazor },
|
||||
{ framework: 'efcore', entryPointMultiplier: 2.0, reason: 'efcore-pattern', patterns: FRAMEWORK_AST_PATTERNS.efcore },
|
||||
],
|
||||
php: [
|
||||
{ framework: 'laravel', entryPointMultiplier: 3.0, reason: 'php-route-attribute', patterns: FRAMEWORK_AST_PATTERNS.laravel },
|
||||
|
||||
@@ -153,6 +153,152 @@ async function loadComposerConfig(repoRoot: string): Promise<ComposerConfig | nu
|
||||
}
|
||||
}
|
||||
|
||||
/** C# project config parsed from .csproj files */
|
||||
interface CSharpProjectConfig {
|
||||
/** Root namespace from <RootNamespace> or assembly name (default: project directory name) */
|
||||
rootNamespace: string;
|
||||
/** Directory containing the .csproj file */
|
||||
projectDir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse .csproj files to extract RootNamespace.
|
||||
* Scans the repo root for .csproj files and returns configs for each.
|
||||
*/
|
||||
async function loadCSharpProjectConfig(repoRoot: string): Promise<CSharpProjectConfig[]> {
|
||||
const configs: CSharpProjectConfig[] = [];
|
||||
// BFS scan for .csproj files up to 5 levels deep, cap at 100 dirs to avoid runaway scanning
|
||||
const scanQueue: { dir: string; depth: number }[] = [{ dir: repoRoot, depth: 0 }];
|
||||
const maxDepth = 5;
|
||||
const maxDirs = 100;
|
||||
let dirsScanned = 0;
|
||||
|
||||
while (scanQueue.length > 0 && dirsScanned < maxDirs) {
|
||||
const { dir, depth } = scanQueue.shift()!;
|
||||
dirsScanned++;
|
||||
try {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && depth < maxDepth) {
|
||||
// Skip common non-project directories
|
||||
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'bin' || entry.name === 'obj') continue;
|
||||
scanQueue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
|
||||
}
|
||||
if (entry.isFile() && entry.name.endsWith('.csproj')) {
|
||||
try {
|
||||
const csprojPath = path.join(dir, entry.name);
|
||||
const content = await fs.readFile(csprojPath, 'utf-8');
|
||||
const nsMatch = content.match(/<RootNamespace>\s*([^<]+)\s*<\/RootNamespace>/);
|
||||
const rootNamespace = nsMatch
|
||||
? nsMatch[1].trim()
|
||||
: entry.name.replace(/\.csproj$/, '');
|
||||
const projectDir = path.relative(repoRoot, dir).replace(/\\/g, '/');
|
||||
configs.push({ rootNamespace, projectDir });
|
||||
if (isDev) {
|
||||
console.log(`📦 Loaded C# project: ${entry.name} (namespace: ${rootNamespace}, dir: ${projectDir})`);
|
||||
}
|
||||
} catch {
|
||||
// Can't read .csproj
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Can't read directory
|
||||
}
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a C# using directive to file paths.
|
||||
* C# `using` directives import namespaces (not files), so one using can resolve
|
||||
* to multiple .cs files in a directory — similar to Go package imports.
|
||||
*
|
||||
* e.g. "MyApp.Services" -> all .cs files in "src/Services/"
|
||||
* e.g. "MyApp.Services.UserService" -> "src/Services/UserService.cs" (single file)
|
||||
*
|
||||
* Strategy:
|
||||
* 1. Strip root namespace prefix from each known .csproj project
|
||||
* 2. Convert remaining namespace to path: Dots -> /
|
||||
* 3. Try as single file first (ClassName import), then as directory (namespace import)
|
||||
*/
|
||||
function resolveCSharpImport(
|
||||
importPath: string,
|
||||
csharpConfigs: CSharpProjectConfig[],
|
||||
normalizedFileList: string[],
|
||||
allFileList: string[],
|
||||
index?: SuffixIndex,
|
||||
): string[] {
|
||||
const namespacePath = importPath.replace(/\./g, '/');
|
||||
const results: string[] = [];
|
||||
|
||||
for (const config of csharpConfigs) {
|
||||
const nsPath = config.rootNamespace.replace(/\./g, '/');
|
||||
let relative: string;
|
||||
if (namespacePath.startsWith(nsPath + '/')) {
|
||||
relative = namespacePath.slice(nsPath.length + 1);
|
||||
} else if (namespacePath === nsPath) {
|
||||
// The import IS the root namespace — resolve to all .cs files in project root
|
||||
relative = '';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dirPrefix = config.projectDir
|
||||
? (relative ? config.projectDir + '/' + relative : config.projectDir)
|
||||
: relative;
|
||||
|
||||
// 1. Try as single file: relative.cs (e.g., "Models/DlqMessage.cs")
|
||||
if (relative) {
|
||||
const candidate = dirPrefix + '.cs';
|
||||
if (index) {
|
||||
const result = index.get(candidate) || index.getInsensitive(candidate);
|
||||
if (result) return [result];
|
||||
}
|
||||
// Also try suffix match
|
||||
const suffixResult = index?.get(relative + '.cs') || index?.getInsensitive(relative + '.cs');
|
||||
if (suffixResult) return [suffixResult];
|
||||
}
|
||||
|
||||
// 2. Try as directory: all .cs files directly inside (namespace import)
|
||||
if (index) {
|
||||
const dirFiles = index.getFilesInDir(dirPrefix, '.cs');
|
||||
for (const f of dirFiles) {
|
||||
const normalized = f.replace(/\\/g, '/');
|
||||
// Check it's a direct child by finding the dirPrefix and ensuring no deeper slashes
|
||||
const prefixIdx = normalized.indexOf(dirPrefix + '/');
|
||||
if (prefixIdx < 0) continue;
|
||||
const afterDir = normalized.substring(prefixIdx + dirPrefix.length + 1);
|
||||
if (!afterDir.includes('/')) {
|
||||
results.push(f);
|
||||
}
|
||||
}
|
||||
if (results.length > 0) return results;
|
||||
}
|
||||
|
||||
// 3. Linear scan fallback for directory matching
|
||||
if (results.length === 0) {
|
||||
const dirTrail = dirPrefix + '/';
|
||||
for (let i = 0; i < normalizedFileList.length; i++) {
|
||||
const normalized = normalizedFileList[i];
|
||||
if (!normalized.endsWith('.cs')) continue;
|
||||
const prefixIdx = normalized.indexOf(dirTrail);
|
||||
if (prefixIdx < 0) continue;
|
||||
const afterDir = normalized.substring(prefixIdx + dirTrail.length);
|
||||
if (!afterDir.includes('/')) {
|
||||
results.push(allFileList[i]);
|
||||
}
|
||||
}
|
||||
if (results.length > 0) return results;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: suffix matching without namespace stripping (single file)
|
||||
const pathParts = namespacePath.split('/').filter(Boolean);
|
||||
const fallback = suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
||||
return fallback ? [fallback] : [];
|
||||
}
|
||||
|
||||
/** Swift Package Manager module config */
|
||||
interface SwiftPackageConfig {
|
||||
/** Map of target name -> source directory path (e.g., "SiuperModel" -> "Package/Sources/SiuperModel") */
|
||||
@@ -749,6 +895,7 @@ export const processImports = async (
|
||||
const goModule = await loadGoModulePath(effectiveRoot);
|
||||
const composerConfig = await loadComposerConfig(effectiveRoot);
|
||||
const swiftPackageConfig = await loadSwiftPackageConfig(effectiveRoot);
|
||||
const csharpConfigs = await loadCSharpProjectConfig(effectiveRoot);
|
||||
|
||||
// Helper: add an IMPORTS edge + update import map
|
||||
const addImportEdge = (filePath: string, resolvedPath: string) => {
|
||||
@@ -889,6 +1036,15 @@ export const processImports = async (
|
||||
// Fall through if no files found (package might be external)
|
||||
}
|
||||
|
||||
// ---- C#: handle namespace-based imports (using directives) ----
|
||||
if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
|
||||
const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
|
||||
for (const resolvedFile of resolvedFiles) {
|
||||
addImportEdge(file.path, resolvedFile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- PHP: handle namespace-based imports (use statements) ----
|
||||
if (language === SupportedLanguages.PHP) {
|
||||
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
||||
@@ -968,6 +1124,7 @@ export const processImportsFromExtracted = async (
|
||||
const goModule = await loadGoModulePath(effectiveRoot);
|
||||
const composerConfig = await loadComposerConfig(effectiveRoot);
|
||||
const swiftPackageConfig = await loadSwiftPackageConfig(effectiveRoot);
|
||||
const csharpConfigs = await loadCSharpProjectConfig(effectiveRoot);
|
||||
|
||||
const addImportEdge = (filePath: string, resolvedPath: string) => {
|
||||
const sourceId = generateId('File', filePath);
|
||||
@@ -1081,6 +1238,15 @@ export const processImportsFromExtracted = async (
|
||||
}
|
||||
}
|
||||
|
||||
// C#: handle namespace-based imports (using directives)
|
||||
if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
|
||||
const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
|
||||
for (const resolvedFile of resolvedFiles) {
|
||||
addImportEdge(filePath, resolvedFile);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// PHP: handle namespace-based imports (use statements)
|
||||
if (language === SupportedLanguages.PHP) {
|
||||
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
||||
|
||||
@@ -282,9 +282,9 @@ export const CSHARP_QUERIES = `
|
||||
|
||||
; Heritage
|
||||
(class_declaration name: (identifier) @heritage.class
|
||||
(base_list (simple_base_type (identifier) @heritage.extends))) @heritage
|
||||
(base_list (identifier) @heritage.extends)) @heritage
|
||||
(class_declaration name: (identifier) @heritage.class
|
||||
(base_list (simple_base_type (generic_name (identifier) @heritage.extends)))) @heritage
|
||||
(base_list (generic_name (identifier) @heritage.extends))) @heritage
|
||||
`;
|
||||
|
||||
// Rust queries - works with tree-sitter-rust
|
||||
|
||||
@@ -399,6 +399,25 @@ const BUILT_INS = new Set([
|
||||
'mutex_lock', 'mutex_unlock', 'mutex_init',
|
||||
'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
|
||||
'get', 'put',
|
||||
// C# / .NET built-ins (IMPORTANT: keep in sync with call-processor.ts BUILT_IN_NAMES)
|
||||
'Console', 'WriteLine', 'ReadLine', 'Write',
|
||||
'Task', 'Run', 'Wait', 'WhenAll', 'WhenAny', 'FromResult', 'Delay', 'ContinueWith',
|
||||
'ConfigureAwait', 'GetAwaiter', 'GetResult',
|
||||
'ToString', 'GetType', 'Equals', 'GetHashCode', 'ReferenceEquals',
|
||||
'Add', 'Remove', 'Contains', 'Clear', 'Count', 'Any', 'All',
|
||||
'Where', 'Select', 'SelectMany', 'OrderBy', 'OrderByDescending', 'GroupBy',
|
||||
'First', 'FirstOrDefault', 'Single', 'SingleOrDefault', 'Last', 'LastOrDefault',
|
||||
'ToList', 'ToArray', 'ToDictionary', 'AsEnumerable', 'AsQueryable',
|
||||
'Aggregate', 'Sum', 'Average', 'Min', 'Max', 'Distinct', 'Skip', 'Take',
|
||||
'String', 'Format', 'IsNullOrEmpty', 'IsNullOrWhiteSpace', 'Concat', 'Join',
|
||||
'Trim', 'TrimStart', 'TrimEnd', 'Split', 'Replace', 'StartsWith', 'EndsWith',
|
||||
'Convert', 'ToInt32', 'ToDouble', 'ToBoolean', 'ToByte',
|
||||
'Math', 'Abs', 'Ceiling', 'Floor', 'Round', 'Pow', 'Sqrt',
|
||||
'Dispose', 'Close',
|
||||
'TryParse', 'Parse',
|
||||
'AddRange', 'RemoveAt', 'RemoveAll', 'FindAll', 'Exists', 'TrueForAll',
|
||||
'ContainsKey', 'TryGetValue', 'AddOrUpdate',
|
||||
'Throw', 'ThrowIfNull',
|
||||
// PHP built-ins
|
||||
'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
|
||||
'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
|
||||
|
||||
+38
-5
@@ -1,22 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
public class Calculator
|
||||
public interface ICalculator
|
||||
{
|
||||
int Add(int a, int b);
|
||||
}
|
||||
|
||||
public class Calculator : ICalculator
|
||||
{
|
||||
public int Result { get; private set; }
|
||||
|
||||
public Calculator() { Result = 0; }
|
||||
|
||||
public int Add(int a, int b)
|
||||
{
|
||||
return a + b;
|
||||
Result = a + b;
|
||||
LogResult(Result);
|
||||
return Result;
|
||||
}
|
||||
|
||||
private int Multiply(int a, int b)
|
||||
private void LogResult(int value)
|
||||
{
|
||||
return a * b;
|
||||
Console.WriteLine(value);
|
||||
}
|
||||
|
||||
private int Multiply(int a, int b) { return a * b; }
|
||||
}
|
||||
|
||||
internal class Helper
|
||||
{
|
||||
public void DoWork() { }
|
||||
public void DoWork()
|
||||
{
|
||||
var calc = new Calculator();
|
||||
calc.Add(1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Operation
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply
|
||||
}
|
||||
|
||||
public record CalculationResult(int Value, Operation Op);
|
||||
|
||||
public struct Point
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +153,12 @@ describe('Tree-sitter multi-language parsing', () => {
|
||||
it('parses class, method, and property declarations', async () => {
|
||||
await loadLanguage(SupportedLanguages.CSharp);
|
||||
const content = readFixture('simple.cs');
|
||||
try {
|
||||
const { matches } = parseAndQuery(parser, content, LANGUAGE_QUERIES[SupportedLanguages.CSharp]);
|
||||
const defs = extractDefinitions(matches);
|
||||
expect(defs.length).toBeGreaterThan(0);
|
||||
} catch (e: any) {
|
||||
// Some tree-sitter-c-sharp versions don't support all query node types
|
||||
expect(e.message).toContain('TSQueryError');
|
||||
}
|
||||
const { matches } = parseAndQuery(parser, content, LANGUAGE_QUERIES[SupportedLanguages.CSharp]);
|
||||
const defs = extractDefinitions(matches);
|
||||
expect(defs.length).toBeGreaterThan(0);
|
||||
const defTypes = defs.map(d => d.type);
|
||||
expect(defTypes).toContain('definition.class');
|
||||
expect(defTypes).toContain('definition.method');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user