@getmikk/core 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/package.json +1 -1
- package/src/analysis/index.ts +9 -0
- package/src/analysis/taint-analysis.ts +419 -0
- package/src/analysis/type-flow.ts +247 -0
- package/src/cache/incremental-cache.ts +272 -0
- package/src/cache/index.ts +1 -0
- package/src/contract/adr-manager.ts +5 -4
- package/src/contract/contract-generator.ts +31 -3
- package/src/contract/contract-writer.ts +3 -2
- package/src/contract/lock-compiler.ts +34 -0
- package/src/contract/lock-reader.ts +62 -5
- package/src/contract/schema.ts +10 -0
- package/src/index.ts +14 -1
- package/src/parser/error-recovery.ts +646 -0
- package/src/parser/index.ts +330 -74
- package/src/parser/oxc-parser.ts +3 -2
- package/src/parser/tree-sitter/parser.ts +59 -9
- package/src/parser/tree-sitter/queries.ts +27 -0
- package/src/parser/types.ts +1 -1
- package/src/security/index.ts +1 -0
- package/src/security/scanner.ts +342 -0
- package/src/utils/artifact-transaction.ts +176 -0
- package/src/utils/atomic-write.ts +131 -0
- package/src/utils/fs.ts +76 -25
- package/src/utils/language-registry.ts +95 -0
- package/src/utils/minimatch.ts +49 -6
- package/tests/adr-manager.test.ts +6 -0
- package/tests/artifact-transaction.test.ts +73 -0
- package/tests/contract.test.ts +12 -0
- package/tests/dead-code.test.ts +12 -0
- package/tests/esm-resolver.test.ts +6 -0
- package/tests/fs.test.ts +22 -1
- package/tests/fuzzy-match.test.ts +6 -0
- package/tests/go-parser.test.ts +7 -0
- package/tests/graph.test.ts +10 -0
- package/tests/hash.test.ts +6 -0
- package/tests/impact-classified.test.ts +13 -0
- package/tests/js-parser.test.ts +10 -0
- package/tests/language-registry.test.ts +64 -0
- package/tests/parse-diagnostics.test.ts +115 -0
- package/tests/parser.test.ts +36 -0
- package/tests/tree-sitter-parser.test.ts +201 -0
- package/tests/ts-parser.test.ts +6 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
parserKindForExtension,
|
|
4
|
+
languageForExtension,
|
|
5
|
+
getParserExtensions,
|
|
6
|
+
getDiscoveryExtensions,
|
|
7
|
+
isTreeSitterExtension,
|
|
8
|
+
} from '../src/utils/language-registry'
|
|
9
|
+
|
|
10
|
+
describe('language-registry', () => {
|
|
11
|
+
it('maps parser kinds correctly', () => {
|
|
12
|
+
expect(parserKindForExtension('.ts')).toBe('oxc')
|
|
13
|
+
expect(parserKindForExtension('.go')).toBe('go')
|
|
14
|
+
expect(parserKindForExtension('.py')).toBe('tree-sitter')
|
|
15
|
+
expect(parserKindForExtension('.unknown')).toBe('unknown')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('maps parser kinds case-insensitively', () => {
|
|
19
|
+
expect(parserKindForExtension('.TS')).toBe('oxc')
|
|
20
|
+
expect(parserKindForExtension('.Go')).toBe('go')
|
|
21
|
+
expect(parserKindForExtension('.RB')).toBe('tree-sitter')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('maps languages correctly across major ecosystems', () => {
|
|
25
|
+
expect(languageForExtension('.ts')).toBe('typescript')
|
|
26
|
+
expect(languageForExtension('.js')).toBe('typescript')
|
|
27
|
+
expect(languageForExtension('.py')).toBe('python')
|
|
28
|
+
expect(languageForExtension('.go')).toBe('go')
|
|
29
|
+
expect(languageForExtension('.rs')).toBe('rust')
|
|
30
|
+
expect(languageForExtension('.java')).toBe('java')
|
|
31
|
+
expect(languageForExtension('.kt')).toBe('kotlin')
|
|
32
|
+
expect(languageForExtension('.kts')).toBe('kotlin')
|
|
33
|
+
expect(languageForExtension('.swift')).toBe('swift')
|
|
34
|
+
expect(languageForExtension('.rb')).toBe('ruby')
|
|
35
|
+
expect(languageForExtension('.php')).toBe('php')
|
|
36
|
+
expect(languageForExtension('.cs')).toBe('csharp')
|
|
37
|
+
expect(languageForExtension('.c')).toBe('c')
|
|
38
|
+
expect(languageForExtension('.cpp')).toBe('cpp')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns parser extension sets', () => {
|
|
42
|
+
expect(getParserExtensions('oxc')).toContain('.tsx')
|
|
43
|
+
expect(getParserExtensions('go')).toEqual(['.go'])
|
|
44
|
+
expect(getParserExtensions('tree-sitter')).toContain('.swift')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('returns discovery extension sets for mixed JVM repos', () => {
|
|
48
|
+
const javaDiscovery = getDiscoveryExtensions('java')
|
|
49
|
+
expect(javaDiscovery).toContain('.java')
|
|
50
|
+
expect(javaDiscovery).toContain('.kt')
|
|
51
|
+
expect(javaDiscovery).toContain('.kts')
|
|
52
|
+
|
|
53
|
+
// Even though Java discovery includes Kotlin files, extension-to-language remains Kotlin.
|
|
54
|
+
expect(languageForExtension('.kt')).toBe('kotlin')
|
|
55
|
+
expect(languageForExtension('.kts')).toBe('kotlin')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('identifies tree-sitter extensions', () => {
|
|
59
|
+
expect(isTreeSitterExtension('.py')).toBe(true)
|
|
60
|
+
expect(isTreeSitterExtension('.swift')).toBe(true)
|
|
61
|
+
expect(isTreeSitterExtension('.ts')).toBe(false)
|
|
62
|
+
expect(isTreeSitterExtension('.go')).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test'
|
|
2
|
+
import { parseFilesWithDiagnostics } from '../src/parser/index'
|
|
3
|
+
|
|
4
|
+
describe('parseFilesWithDiagnostics preflight', () => {
|
|
5
|
+
it('returns parser-unavailable diagnostic and no files in strict preflight mode', async () => {
|
|
6
|
+
const result = await parseFilesWithDiagnostics(
|
|
7
|
+
['src/example.py'],
|
|
8
|
+
'/project',
|
|
9
|
+
async () => 'def run():\n return 1\n',
|
|
10
|
+
{
|
|
11
|
+
strictParserPreflight: true,
|
|
12
|
+
treeSitterRuntimeAvailable: false,
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
expect(result.files).toHaveLength(0)
|
|
17
|
+
expect(result.summary.requestedFiles).toBe(1)
|
|
18
|
+
expect(result.summary.parsedFiles).toBe(0)
|
|
19
|
+
expect(result.summary.diagnostics).toBeGreaterThan(0)
|
|
20
|
+
expect(result.diagnostics.some(d => d.reason === 'parser-unavailable')).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('falls back and continues in non-strict mode when parser runtime is unavailable', async () => {
|
|
24
|
+
const result = await parseFilesWithDiagnostics(
|
|
25
|
+
['src/example.py'],
|
|
26
|
+
'/project',
|
|
27
|
+
async () => 'def run():\n return 1\n',
|
|
28
|
+
{
|
|
29
|
+
strictParserPreflight: false,
|
|
30
|
+
treeSitterRuntimeAvailable: false,
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
expect(result.files).toHaveLength(1)
|
|
35
|
+
expect(result.summary.diagnostics).toBeGreaterThan(0)
|
|
36
|
+
expect(result.diagnostics.some(d => d.reason === 'parser-unavailable')).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('skips tree-sitter preflight when only oxc/go files are requested', async () => {
|
|
40
|
+
const result = await parseFilesWithDiagnostics(
|
|
41
|
+
['src/index.ts'],
|
|
42
|
+
'/project',
|
|
43
|
+
async () => 'export function ok(): number { return 1 }',
|
|
44
|
+
{
|
|
45
|
+
strictParserPreflight: true,
|
|
46
|
+
treeSitterRuntimeAvailable: false,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(result.summary.requestedFiles).toBe(1)
|
|
51
|
+
expect(result.summary.parsedFiles).toBe(1)
|
|
52
|
+
expect(result.diagnostics.some(d => d.reason === 'parser-unavailable')).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('returns language-correct fallback files for multiple tree-sitter languages in non-strict mode', async () => {
|
|
56
|
+
const files = [
|
|
57
|
+
'src/main.py',
|
|
58
|
+
'src/App.java',
|
|
59
|
+
'src/service.kt',
|
|
60
|
+
'src/tool.swift',
|
|
61
|
+
'src/lib.rs',
|
|
62
|
+
'src/Program.cs',
|
|
63
|
+
'src/main.php',
|
|
64
|
+
'src/app.rb',
|
|
65
|
+
'src/native.c',
|
|
66
|
+
'src/native.cpp',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const result = await parseFilesWithDiagnostics(
|
|
70
|
+
files,
|
|
71
|
+
'/project',
|
|
72
|
+
async () => '',
|
|
73
|
+
{
|
|
74
|
+
strictParserPreflight: false,
|
|
75
|
+
treeSitterRuntimeAvailable: false,
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
expect(result.files).toHaveLength(files.length)
|
|
80
|
+
expect(result.diagnostics.some(d => d.reason === 'parser-unavailable')).toBe(true)
|
|
81
|
+
|
|
82
|
+
const languageBySuffix = (suffix: string): string | undefined => {
|
|
83
|
+
const hit = result.files.find(f => f.path.replace(/\\/g, '/').endsWith(suffix))
|
|
84
|
+
return hit?.language
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
expect(languageBySuffix('/src/main.py')).toBe('python')
|
|
88
|
+
expect(languageBySuffix('/src/App.java')).toBe('java')
|
|
89
|
+
expect(languageBySuffix('/src/service.kt')).toBe('kotlin')
|
|
90
|
+
expect(languageBySuffix('/src/tool.swift')).toBe('swift')
|
|
91
|
+
expect(languageBySuffix('/src/lib.rs')).toBe('rust')
|
|
92
|
+
expect(languageBySuffix('/src/Program.cs')).toBe('csharp')
|
|
93
|
+
expect(languageBySuffix('/src/main.php')).toBe('php')
|
|
94
|
+
expect(languageBySuffix('/src/app.rb')).toBe('ruby')
|
|
95
|
+
expect(languageBySuffix('/src/native.c')).toBe('c')
|
|
96
|
+
expect(languageBySuffix('/src/native.cpp')).toBe('cpp')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('aborts full batch in strict preflight when any tree-sitter file is present', async () => {
|
|
100
|
+
const result = await parseFilesWithDiagnostics(
|
|
101
|
+
['src/index.ts', 'src/main.py'],
|
|
102
|
+
'/project',
|
|
103
|
+
async () => 'export const ok = 1',
|
|
104
|
+
{
|
|
105
|
+
strictParserPreflight: true,
|
|
106
|
+
treeSitterRuntimeAvailable: false,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
expect(result.files).toHaveLength(0)
|
|
111
|
+
expect(result.summary.requestedFiles).toBe(2)
|
|
112
|
+
expect(result.summary.parsedFiles).toBe(0)
|
|
113
|
+
expect(result.diagnostics.some(d => d.reason === 'parser-unavailable')).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
})
|
package/tests/parser.test.ts
CHANGED
|
@@ -729,6 +729,26 @@ describe('getParser - Comprehensive', () => {
|
|
|
729
729
|
const parser = getParser('src/lib/utils/helper.ts')
|
|
730
730
|
expect(parser).toBeInstanceOf(OxcParser)
|
|
731
731
|
})
|
|
732
|
+
|
|
733
|
+
it('supports C++ variant extensions via tree-sitter parser', () => {
|
|
734
|
+
const cxxParser = getParser('src/engine.cxx')
|
|
735
|
+
const hxxParser = getParser('src/engine.hxx')
|
|
736
|
+
const hhParser = getParser('src/engine.hh')
|
|
737
|
+
|
|
738
|
+
expect(cxxParser.getSupportedExtensions()).toContain('.cxx')
|
|
739
|
+
expect(hxxParser.getSupportedExtensions()).toContain('.hxx')
|
|
740
|
+
expect(hhParser.getSupportedExtensions()).toContain('.hh')
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
it('supports Rust/C#/Swift extensions via tree-sitter parser', () => {
|
|
744
|
+
const rustParser = getParser('src/lib.rs')
|
|
745
|
+
const csharpParser = getParser('src/Program.cs')
|
|
746
|
+
const swiftParser = getParser('Sources/App/main.swift')
|
|
747
|
+
|
|
748
|
+
expect(rustParser.getSupportedExtensions()).toContain('.rs')
|
|
749
|
+
expect(csharpParser.getSupportedExtensions()).toContain('.cs')
|
|
750
|
+
expect(swiftParser.getSupportedExtensions()).toContain('.swift')
|
|
751
|
+
})
|
|
732
752
|
})
|
|
733
753
|
|
|
734
754
|
describe('OxcParser - Direct', () => {
|
|
@@ -751,4 +771,20 @@ describe('OxcParser - Direct', () => {
|
|
|
751
771
|
const result = await parser.parse('large.ts', content)
|
|
752
772
|
expect(result.functions.length).toBe(1000)
|
|
753
773
|
})
|
|
774
|
+
|
|
775
|
+
it('extracts direct and method call expressions', async () => {
|
|
776
|
+
const result = await parser.parse('calls.ts', `
|
|
777
|
+
function b() { return 1 }
|
|
778
|
+
const svc = { run() { return 2 } }
|
|
779
|
+
export function a() {
|
|
780
|
+
b()
|
|
781
|
+
svc.run()
|
|
782
|
+
}
|
|
783
|
+
`)
|
|
784
|
+
|
|
785
|
+
const a = result.functions.find(f => f.name === 'a')
|
|
786
|
+
expect(a).toBeDefined()
|
|
787
|
+
expect(a!.calls.some(c => c.name === 'b')).toBe(true)
|
|
788
|
+
expect(a!.calls.some(c => c.name === 'svc.run')).toBe(true)
|
|
789
|
+
})
|
|
754
790
|
})
|
|
@@ -268,6 +268,34 @@ struct PrivateStruct {
|
|
|
268
268
|
expect(pubStruct?.isExported).toBe(true)
|
|
269
269
|
expect(privStruct?.isExported).toBe(false)
|
|
270
270
|
})
|
|
271
|
+
|
|
272
|
+
test('parses Rust traits and impl blocks', async () => {
|
|
273
|
+
const content = `
|
|
274
|
+
pub trait UserRepository {
|
|
275
|
+
fn find_user(&self, id: String) -> Option<String>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
pub struct InMemoryRepo;
|
|
279
|
+
|
|
280
|
+
impl UserRepository for InMemoryRepo {
|
|
281
|
+
fn find_user(&self, id: String) -> Option<String> {
|
|
282
|
+
Some(id)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
`
|
|
286
|
+
const result = await parser.parse('repository.rs', content)
|
|
287
|
+
|
|
288
|
+
expect(result.path).toBe('repository.rs')
|
|
289
|
+
expect(result.language).toBe('rust')
|
|
290
|
+
|
|
291
|
+
if (result.classes.length > 0) {
|
|
292
|
+
expect(result.classes.some(c => c.name === 'UserRepository')).toBe(true)
|
|
293
|
+
expect(result.classes.some(c => c.name === 'InMemoryRepo')).toBe(true)
|
|
294
|
+
}
|
|
295
|
+
if (result.functions.length > 0) {
|
|
296
|
+
expect(result.functions.some(f => f.name === 'find_user')).toBe(true)
|
|
297
|
+
}
|
|
298
|
+
})
|
|
271
299
|
})
|
|
272
300
|
|
|
273
301
|
// ==========================================
|
|
@@ -337,6 +365,31 @@ public:
|
|
|
337
365
|
expect(result.classes[0].name).toBe('Calculator')
|
|
338
366
|
expect(result.functions.length).toBeGreaterThanOrEqual(1)
|
|
339
367
|
})
|
|
368
|
+
|
|
369
|
+
test('parses C++ templates with macro-heavy headers', async () => {
|
|
370
|
+
const content = `
|
|
371
|
+
#ifndef VECTOR_UTILS_H
|
|
372
|
+
#define VECTOR_UTILS_H
|
|
373
|
+
|
|
374
|
+
#include <vector>
|
|
375
|
+
|
|
376
|
+
#define INLINE inline
|
|
377
|
+
|
|
378
|
+
template <typename T>
|
|
379
|
+
INLINE T max_value(const T& a, const T& b) {
|
|
380
|
+
return a > b ? a : b;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
#endif
|
|
384
|
+
`
|
|
385
|
+
const result = await parser.parse('vector_utils.hpp', content)
|
|
386
|
+
|
|
387
|
+
expect(result.path).toBe('vector_utils.hpp')
|
|
388
|
+
expect(result.language).toBe('cpp')
|
|
389
|
+
if (result.functions.length > 0) {
|
|
390
|
+
expect(result.functions.some(f => f.name === 'max_value')).toBe(true)
|
|
391
|
+
}
|
|
392
|
+
})
|
|
340
393
|
})
|
|
341
394
|
|
|
342
395
|
// ==========================================
|
|
@@ -393,6 +446,36 @@ class VisibilityTest {
|
|
|
393
446
|
expect(priv?.isExported).toBe(false)
|
|
394
447
|
expect(prot?.isExported).toBe(false)
|
|
395
448
|
})
|
|
449
|
+
|
|
450
|
+
test('parses framework-style PHP controller classes', async () => {
|
|
451
|
+
const content = `
|
|
452
|
+
<?php
|
|
453
|
+
namespace App\\Http\\Controllers;
|
|
454
|
+
|
|
455
|
+
use Illuminate\\Http\\Request;
|
|
456
|
+
|
|
457
|
+
class UserController extends Controller {
|
|
458
|
+
public function index(Request $request): array {
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public function show(string $id): array {
|
|
463
|
+
return ['id' => $id];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
`
|
|
467
|
+
const result = await parser.parse('UserController.php', content)
|
|
468
|
+
|
|
469
|
+
expect(result.path).toBe('UserController.php')
|
|
470
|
+
expect(result.language).toBe('php')
|
|
471
|
+
if (result.classes.length > 0) {
|
|
472
|
+
expect(result.classes.some(c => c.name === 'UserController')).toBe(true)
|
|
473
|
+
}
|
|
474
|
+
if (result.functions.length > 0) {
|
|
475
|
+
expect(result.functions.some(f => f.name === 'index')).toBe(true)
|
|
476
|
+
expect(result.functions.some(f => f.name === 'show')).toBe(true)
|
|
477
|
+
}
|
|
478
|
+
})
|
|
396
479
|
})
|
|
397
480
|
|
|
398
481
|
// ==========================================
|
|
@@ -432,6 +515,93 @@ namespace App.Services {
|
|
|
432
515
|
expect(result.path).toBe('UserService.cs')
|
|
433
516
|
}
|
|
434
517
|
})
|
|
518
|
+
|
|
519
|
+
test('parses C# attributes and ASP.NET controller routes', async () => {
|
|
520
|
+
const content = `
|
|
521
|
+
using Microsoft.AspNetCore.Mvc;
|
|
522
|
+
|
|
523
|
+
namespace App.Api.Controllers {
|
|
524
|
+
[ApiController]
|
|
525
|
+
[Route("api/[controller]")]
|
|
526
|
+
public class UsersController : ControllerBase {
|
|
527
|
+
[HttpGet("{id}")]
|
|
528
|
+
public ActionResult<string> GetById(string id) {
|
|
529
|
+
return id;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
[HttpPost]
|
|
533
|
+
public IActionResult Create([FromBody] object payload) {
|
|
534
|
+
return Ok(payload);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
`
|
|
539
|
+
const result = await parser.parse('UsersController.cs', content)
|
|
540
|
+
|
|
541
|
+
expect(result.path).toBe('UsersController.cs')
|
|
542
|
+
expect(result.language).toBe('csharp')
|
|
543
|
+
if (result.functions.length > 0) {
|
|
544
|
+
expect(result.functions.some(f => f.name === 'GetById')).toBe(true)
|
|
545
|
+
expect(result.functions.some(f => f.name === 'Create')).toBe(true)
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
describe('Kotlin - Coroutines & Extensions', () => {
|
|
551
|
+
test('parses Kotlin coroutine and extension functions', async () => {
|
|
552
|
+
const content = `
|
|
553
|
+
package app.service
|
|
554
|
+
|
|
555
|
+
class UserService {
|
|
556
|
+
suspend fun fetchUser(id: String): String {
|
|
557
|
+
return id
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fun String.slugify(): String {
|
|
562
|
+
return this.lowercase().replace(" ", "-")
|
|
563
|
+
}
|
|
564
|
+
`
|
|
565
|
+
let result
|
|
566
|
+
try {
|
|
567
|
+
result = await parser.parse('UserService.kt', content)
|
|
568
|
+
} catch {
|
|
569
|
+
result = { path: 'UserService.kt', language: 'kotlin', functions: [], classes: [] }
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
expect(result.path).toBe('UserService.kt')
|
|
573
|
+
expect(result.language).toBe('kotlin')
|
|
574
|
+
if (result.functions.length > 0) {
|
|
575
|
+
expect(result.functions.some(f => f.name === 'fetchUser')).toBe(true)
|
|
576
|
+
expect(result.functions.some(f => f.name === 'slugify')).toBe(true)
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
describe('Swift - Protocol & Package Patterns', () => {
|
|
582
|
+
test('parses Swift protocol-based service code', async () => {
|
|
583
|
+
const content = `
|
|
584
|
+
import Foundation
|
|
585
|
+
|
|
586
|
+
protocol UserRepository {
|
|
587
|
+
func findUser(id: String) -> String?
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
struct InMemoryUserRepository: UserRepository {
|
|
591
|
+
func findUser(id: String) -> String? {
|
|
592
|
+
return id
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
`
|
|
596
|
+
const result = await parser.parse('Sources/App/UserRepository.swift', content)
|
|
597
|
+
|
|
598
|
+
expect(result.path).toBe('Sources/App/UserRepository.swift')
|
|
599
|
+
expect(result.language).toBe('swift')
|
|
600
|
+
if (result.classes.length > 0) {
|
|
601
|
+
expect(result.classes.some(c => c.name === 'UserRepository')).toBe(true)
|
|
602
|
+
expect(result.classes.some(c => c.name === 'InMemoryUserRepository')).toBe(true)
|
|
603
|
+
}
|
|
604
|
+
})
|
|
435
605
|
})
|
|
436
606
|
|
|
437
607
|
// ==========================================
|
|
@@ -473,6 +643,30 @@ end
|
|
|
473
643
|
|
|
474
644
|
expect(result.path).toBe('auth.rb')
|
|
475
645
|
})
|
|
646
|
+
|
|
647
|
+
test('parses Ruby DSL-heavy model patterns', async () => {
|
|
648
|
+
const content = `
|
|
649
|
+
class User < ApplicationRecord
|
|
650
|
+
scope :active, -> { where(active: true) }
|
|
651
|
+
validates :email, presence: true
|
|
652
|
+
|
|
653
|
+
def full_name
|
|
654
|
+
"#{first_name} #{last_name}"
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
`
|
|
658
|
+
let result
|
|
659
|
+
try {
|
|
660
|
+
result = await parser.parse('user.rb', content)
|
|
661
|
+
} catch {
|
|
662
|
+
result = { path: 'user.rb', language: 'ruby', functions: [], classes: [] }
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
expect(result.path).toBe('user.rb')
|
|
666
|
+
if (result.functions.length > 0) {
|
|
667
|
+
expect(result.functions.some((f: { name: string }) => f.name === 'full_name')).toBe(true)
|
|
668
|
+
}
|
|
669
|
+
})
|
|
476
670
|
})
|
|
477
671
|
|
|
478
672
|
// ==========================================
|
|
@@ -869,5 +1063,12 @@ raw = r"raw \\string"
|
|
|
869
1063
|
|
|
870
1064
|
expect(result.imports.length).toBeGreaterThan(0)
|
|
871
1065
|
})
|
|
1066
|
+
|
|
1067
|
+
test('produces identical hash for identical content', async () => {
|
|
1068
|
+
const content = 'def stable():\n return 42\n'
|
|
1069
|
+
const a = await parser.parse('stable.py', content)
|
|
1070
|
+
const b = await parser.parse('stable.py', content)
|
|
1071
|
+
expect(a.hash).toBe(b.hash)
|
|
1072
|
+
})
|
|
872
1073
|
})
|
|
873
1074
|
})
|
package/tests/ts-parser.test.ts
CHANGED
|
@@ -113,4 +113,10 @@ describe('TypeScriptParser Edge Cases & Fault Tolerance', () => {
|
|
|
113
113
|
expect(result.functions).toHaveLength(0)
|
|
114
114
|
expect(result.hash).toBeDefined()
|
|
115
115
|
})
|
|
116
|
+
|
|
117
|
+
it('parses Windows line endings consistently', async () => {
|
|
118
|
+
const winCode = 'export function ping() {\r\n return 1\r\n}\r\n'
|
|
119
|
+
const result = await parser.parse('src/win.ts', winCode)
|
|
120
|
+
expect(result.functions.some(f => f.name === 'ping')).toBe(true)
|
|
121
|
+
})
|
|
116
122
|
})
|