@getmikk/core 1.7.1 → 1.8.1
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 +82 -412
- package/package.json +3 -1
- package/src/contract/contract-reader.ts +2 -2
- package/src/contract/lock-compiler.ts +15 -14
- package/src/contract/lock-reader.ts +14 -14
- package/src/contract/schema.ts +3 -3
- package/src/index.ts +2 -1
- package/src/parser/base-parser.ts +1 -1
- package/src/parser/boundary-checker.ts +74 -212
- package/src/parser/go/go-extractor.ts +10 -10
- package/src/parser/go/go-parser.ts +2 -2
- package/src/parser/index.ts +45 -31
- package/src/parser/javascript/js-extractor.ts +9 -9
- package/src/parser/javascript/js-parser.ts +2 -2
- package/src/parser/tree-sitter/parser.ts +228 -0
- package/src/parser/tree-sitter/queries.ts +181 -0
- package/src/parser/types.ts +1 -1
- package/src/parser/typescript/ts-extractor.ts +15 -15
- package/src/parser/typescript/ts-parser.ts +1 -1
- package/src/parser/typescript/ts-resolver.ts +2 -2
- package/src/search/bm25.ts +206 -0
- package/src/search/index.ts +3 -0
- package/src/utils/fs.ts +95 -31
- package/src/utils/minimatch.ts +23 -14
- package/test-output.txt +0 -0
- package/tests/go-parser.test.ts +10 -10
- package/tests/js-parser.test.ts +34 -19
- package/tests/parser.test.ts +5 -5
- package/tests/tree-sitter-parser.test.ts +168 -0
- package/tests/ts-parser.test.ts +49 -1
- package/out.log +0 -0
package/src/utils/minimatch.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* minimatch — glob matching with plain-path prefix fallback.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - Pattern with no glob chars (*, ?, {, [) → directory prefix match
|
|
6
|
+
* "src/auth" matches "src/auth/jwt.ts" and "src/auth" itself
|
|
7
|
+
* - "**" matches any depth
|
|
8
|
+
* - "*" matches within a single directory segment
|
|
4
9
|
*/
|
|
5
10
|
export function minimatch(filePath: string, pattern: string): boolean {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const normalizedPattern = pattern.replace(/\\/g, '/')
|
|
11
|
+
const normalizedPath = filePath.replace(/\\/g, '/')
|
|
12
|
+
const normalizedPattern = pattern.replace(/\\/g, '/')
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.replace(/\*/g, '[^/]*')
|
|
14
|
+
// Plain path (no glob chars) → prefix match
|
|
15
|
+
if (!/[*?{[]/.test(normalizedPattern)) {
|
|
16
|
+
const bare = normalizedPattern.replace(/\/$/, '')
|
|
17
|
+
return normalizedPath === bare || normalizedPath.startsWith(bare + '/')
|
|
18
|
+
}
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// Convert glob to regex
|
|
21
|
+
const regexStr = normalizedPattern
|
|
22
|
+
.replace(/\./g, '\\.')
|
|
23
|
+
.replace(/\*\*\//g, '(?:.+/)?')
|
|
24
|
+
.replace(/\*\*/g, '.*')
|
|
25
|
+
.replace(/\*/g, '[^/]*')
|
|
26
|
+
|
|
27
|
+
return new RegExp(`^${regexStr}$`).test(normalizedPath)
|
|
28
|
+
}
|
package/test-output.txt
CHANGED
|
Binary file
|
package/tests/go-parser.test.ts
CHANGED
|
@@ -320,9 +320,9 @@ describe('GoParser', () => {
|
|
|
320
320
|
expect(parser.getSupportedExtensions()).toContain('.go')
|
|
321
321
|
})
|
|
322
322
|
|
|
323
|
-
test('parse returns a well-formed ParsedFile', () => {
|
|
323
|
+
test('parse returns a well-formed ParsedFile', async () => {
|
|
324
324
|
const parser = new GoParser()
|
|
325
|
-
const result = parser.parse('auth/service.go', SIMPLE_GO)
|
|
325
|
+
const result = await parser.parse('auth/service.go', SIMPLE_GO)
|
|
326
326
|
expect(result.path).toBe('auth/service.go')
|
|
327
327
|
expect(result.language).toBe('go')
|
|
328
328
|
expect(Array.isArray(result.functions)).toBe(true)
|
|
@@ -334,31 +334,31 @@ describe('GoParser', () => {
|
|
|
334
334
|
expect(result.hash.length).toBeGreaterThan(0)
|
|
335
335
|
})
|
|
336
336
|
|
|
337
|
-
test('parse populates functions from a real Go service', () => {
|
|
337
|
+
test('parse populates functions from a real Go service', async () => {
|
|
338
338
|
const parser = new GoParser()
|
|
339
|
-
const result = parser.parse('auth/service.go', SIMPLE_GO)
|
|
339
|
+
const result = await parser.parse('auth/service.go', SIMPLE_GO)
|
|
340
340
|
// Top-level funcs: hashPassword, keyFunc
|
|
341
341
|
expect(result.functions.some(f => f.name === 'hashPassword')).toBe(true)
|
|
342
342
|
expect(result.functions.some(f => f.name === 'keyFunc')).toBe(true)
|
|
343
343
|
})
|
|
344
344
|
|
|
345
|
-
test('parse populates classes for structs with methods', () => {
|
|
345
|
+
test('parse populates classes for structs with methods', async () => {
|
|
346
346
|
const parser = new GoParser()
|
|
347
|
-
const result = parser.parse('auth/service.go', SIMPLE_GO)
|
|
347
|
+
const result = await parser.parse('auth/service.go', SIMPLE_GO)
|
|
348
348
|
const authService = result.classes.find(c => c.name === 'AuthService')
|
|
349
349
|
expect(authService).toBeDefined()
|
|
350
350
|
expect(authService!.methods.length).toBeGreaterThan(0)
|
|
351
351
|
})
|
|
352
352
|
|
|
353
|
-
test('parse detects routes in a Gin router file', () => {
|
|
353
|
+
test('parse detects routes in a Gin router file', async () => {
|
|
354
354
|
const parser = new GoParser()
|
|
355
|
-
const result = parser.parse('api/routes.go', ROUTES_GO)
|
|
355
|
+
const result = await parser.parse('api/routes.go', ROUTES_GO)
|
|
356
356
|
expect(result.routes.length).toBeGreaterThanOrEqual(4)
|
|
357
357
|
})
|
|
358
358
|
|
|
359
|
-
test('resolveImports passes through without crashing on no go.mod', () => {
|
|
359
|
+
test('resolveImports passes through without crashing on no go.mod', async () => {
|
|
360
360
|
const parser = new GoParser()
|
|
361
|
-
const files = [parser.parse('utils/format.go', TOPLEVEL_GO)]
|
|
361
|
+
const files = [await parser.parse('utils/format.go', TOPLEVEL_GO)]
|
|
362
362
|
// Should not throw even without go.mod
|
|
363
363
|
const resolved = parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
|
|
364
364
|
expect(resolved.length).toBe(1)
|
package/tests/js-parser.test.ts
CHANGED
|
@@ -468,6 +468,21 @@ describe('JavaScriptExtractor', () => {
|
|
|
468
468
|
const greetExports = exports.filter(e => e.name === 'greet')
|
|
469
469
|
expect(greetExports.length).toBe(1)
|
|
470
470
|
})
|
|
471
|
+
|
|
472
|
+
test('malformed code gracefully degrades without crashing', () => {
|
|
473
|
+
const malformed = `
|
|
474
|
+
function breakMe() {
|
|
475
|
+
const x =
|
|
476
|
+
if (true) {
|
|
477
|
+
// missing braces, missing assignments
|
|
478
|
+
`
|
|
479
|
+
const ext = new JavaScriptExtractor('src/malformed.js', malformed)
|
|
480
|
+
// TS compiler API is very fault tolerant, so it might extract breakMe anyway,
|
|
481
|
+
// but the key assertion is that it doesn't throw.
|
|
482
|
+
expect(() => ext.extractFunctions()).not.toThrow()
|
|
483
|
+
const fn = ext.extractFunctions().find(f => f.name === 'breakMe')
|
|
484
|
+
expect(fn).toBeDefined()
|
|
485
|
+
})
|
|
471
486
|
})
|
|
472
487
|
})
|
|
473
488
|
|
|
@@ -482,47 +497,47 @@ describe('JavaScriptParser', () => {
|
|
|
482
497
|
expect(exts).toContain('.jsx')
|
|
483
498
|
})
|
|
484
499
|
|
|
485
|
-
test('parse returns language: javascript', () => {
|
|
486
|
-
const result = parser.parse('src/index.js', CJS_MODULE)
|
|
500
|
+
test('parse returns language: javascript', async () => {
|
|
501
|
+
const result = await parser.parse('src/index.js', CJS_MODULE)
|
|
487
502
|
expect(result.language).toBe('javascript')
|
|
488
503
|
})
|
|
489
504
|
|
|
490
|
-
test('parse includes hash and parsedAt', () => {
|
|
491
|
-
const result = parser.parse('src/index.js', CJS_MODULE)
|
|
505
|
+
test('parse includes hash and parsedAt', async () => {
|
|
506
|
+
const result = await parser.parse('src/index.js', CJS_MODULE)
|
|
492
507
|
expect(typeof result.hash).toBe('string')
|
|
493
508
|
expect(result.hash.length).toBe(64) // SHA-256 hex
|
|
494
509
|
expect(typeof result.parsedAt).toBe('number')
|
|
495
510
|
})
|
|
496
511
|
|
|
497
|
-
test('CJS-exported functions are marked isExported via cross-reference', () => {
|
|
498
|
-
const result = parser.parse('src/auth.js', CJS_MODULE)
|
|
499
|
-
const hashPw = result.functions.find(f => f.name === 'hashPassword')
|
|
512
|
+
test('CJS-exported functions are marked isExported via cross-reference', async () => {
|
|
513
|
+
const result = await parser.parse('src/auth.js', CJS_MODULE)
|
|
514
|
+
const hashPw = result.functions.find((f: any) => f.name === 'hashPassword')
|
|
500
515
|
expect(hashPw).toBeDefined()
|
|
501
516
|
expect(hashPw!.isExported).toBe(true)
|
|
502
517
|
})
|
|
503
518
|
|
|
504
|
-
test('resolveImports resolves relative paths with .js extension probing', () => {
|
|
505
|
-
const files = [
|
|
519
|
+
test('resolveImports resolves relative paths with .js extension probing', async () => {
|
|
520
|
+
const files = await Promise.all([
|
|
506
521
|
parser.parse('src/auth.js', CJS_MODULE),
|
|
507
522
|
parser.parse('src/loader.js', ESM_MODULE),
|
|
508
|
-
]
|
|
523
|
+
])
|
|
509
524
|
const resolved = parser.resolveImports(files, '/project')
|
|
510
|
-
const authFile = resolved.find(f => f.path === 'src/auth.js')!
|
|
511
|
-
const dbImport = authFile.imports.find(i => i.source === './db')
|
|
525
|
+
const authFile = resolved.find((f: any) => f.path === 'src/auth.js')!
|
|
526
|
+
const dbImport = authFile.imports.find((i: any) => i.source === './db')
|
|
512
527
|
expect(dbImport!.resolvedPath).toMatch(/src\/db/)
|
|
513
528
|
expect(dbImport!.resolvedPath).toMatch(/\.js$/)
|
|
514
529
|
})
|
|
515
530
|
|
|
516
|
-
test('resolveImports leaves external packages unresolved (empty resolvedPath)', () => {
|
|
517
|
-
const files = [parser.parse('src/auth.js', CJS_MODULE)]
|
|
531
|
+
test('resolveImports leaves external packages unresolved (empty resolvedPath)', async () => {
|
|
532
|
+
const files = [await parser.parse('src/auth.js', CJS_MODULE)]
|
|
518
533
|
const resolved = parser.resolveImports(files, '/project')
|
|
519
534
|
const file = resolved[0]
|
|
520
|
-
const cryptoImp = file.imports.find(i => i.source === 'crypto')
|
|
535
|
+
const cryptoImp = file.imports.find((i: any) => i.source === 'crypto')
|
|
521
536
|
expect(cryptoImp!.resolvedPath).toBe('')
|
|
522
537
|
})
|
|
523
538
|
|
|
524
|
-
test('parse .jsx file language is javascript', () => {
|
|
525
|
-
const result = parser.parse('src/UserCard.jsx', JSX_COMPONENT)
|
|
539
|
+
test('parse .jsx file language is javascript', async () => {
|
|
540
|
+
const result = await parser.parse('src/UserCard.jsx', JSX_COMPONENT)
|
|
526
541
|
expect(result.language).toBe('javascript')
|
|
527
542
|
})
|
|
528
543
|
})
|
|
@@ -610,7 +625,7 @@ describe('getParser — JS extensions', () => {
|
|
|
610
625
|
expect(p.getSupportedExtensions()).toContain('.ts')
|
|
611
626
|
})
|
|
612
627
|
|
|
613
|
-
test('still throws UnsupportedLanguageError for .
|
|
614
|
-
expect(() => getParser('src/app.
|
|
628
|
+
test('still throws UnsupportedLanguageError for .xyz', () => {
|
|
629
|
+
expect(() => getParser('src/app.xyz')).toThrow()
|
|
615
630
|
})
|
|
616
631
|
})
|
package/tests/parser.test.ts
CHANGED
|
@@ -134,13 +134,13 @@ describe('TypeScriptExtractor', () => {
|
|
|
134
134
|
describe('TypeScriptParser', () => {
|
|
135
135
|
const parser = new TypeScriptParser()
|
|
136
136
|
|
|
137
|
-
it('returns correct language', () => {
|
|
138
|
-
const result = parser.parse('src/test.ts', 'const x = 1')
|
|
137
|
+
it('returns correct language', async () => {
|
|
138
|
+
const result = await parser.parse('src/test.ts', 'const x = 1')
|
|
139
139
|
expect(result.language).toBe('typescript')
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
-
it('parses a complete file', () => {
|
|
143
|
-
const result = parser.parse('src/auth.ts', `
|
|
142
|
+
it('parses a complete file', async () => {
|
|
143
|
+
const result = await parser.parse('src/auth.ts', `
|
|
144
144
|
import { jwtDecode } from '../utils/jwt'
|
|
145
145
|
export function verifyToken(token: string): boolean {
|
|
146
146
|
return jwtDecode(token).exp > Date.now()
|
|
@@ -213,6 +213,6 @@ describe('getParser', () => {
|
|
|
213
213
|
})
|
|
214
214
|
|
|
215
215
|
it('throws for unsupported extensions', () => {
|
|
216
|
-
expect(() => getParser('src/auth.
|
|
216
|
+
expect(() => getParser('src/auth.xyz')).toThrow(UnsupportedLanguageError)
|
|
217
217
|
})
|
|
218
218
|
})
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { TreeSitterParser } from '../src/parser/tree-sitter/parser.js'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
describe('TreeSitterParser', () => {
|
|
6
|
+
test('parses Python correctly', async () => {
|
|
7
|
+
const parser = new TreeSitterParser()
|
|
8
|
+
const pyContent = `
|
|
9
|
+
import os
|
|
10
|
+
from sys import argv
|
|
11
|
+
|
|
12
|
+
class User:
|
|
13
|
+
def __init__(self, name: str):
|
|
14
|
+
self.name = name
|
|
15
|
+
|
|
16
|
+
def get_name(self) -> str:
|
|
17
|
+
return self.name
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
u = User("Alice")
|
|
21
|
+
print(u.get_name())
|
|
22
|
+
`
|
|
23
|
+
const result = await parser.parse('test.py', pyContent)
|
|
24
|
+
|
|
25
|
+
expect(result.classes.length).toBe(1)
|
|
26
|
+
expect(result.classes[0].name).toBe('User')
|
|
27
|
+
|
|
28
|
+
expect(result.functions.length).toBe(3) // __init__, get_name, main
|
|
29
|
+
const mainFn = result.functions.find((f: any) => f.name === 'main')
|
|
30
|
+
expect(mainFn).toBeDefined()
|
|
31
|
+
|
|
32
|
+
expect(result.imports.length).toBe(2)
|
|
33
|
+
expect(result.imports.map((i: any) => i.source)).toContain('os')
|
|
34
|
+
expect(result.imports.map((i: any) => i.source)).toContain('sys')
|
|
35
|
+
|
|
36
|
+
expect(result.functions[0].calls).toContain('User')
|
|
37
|
+
expect(result.functions[0].calls).toContain('print')
|
|
38
|
+
expect(result.functions[0].calls).toContain('get_name')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('parses Java correctly', async () => {
|
|
42
|
+
const parser = new TreeSitterParser()
|
|
43
|
+
const javaContent = `
|
|
44
|
+
import java.util.List;
|
|
45
|
+
|
|
46
|
+
public class App {
|
|
47
|
+
public static void main(String[] args) {
|
|
48
|
+
System.out.println("Hello");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private int calculate(int a, int b) {
|
|
52
|
+
return a + b;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
const result = await parser.parse('App.java', javaContent)
|
|
57
|
+
|
|
58
|
+
expect(result.classes.length).toBe(1)
|
|
59
|
+
expect(result.classes[0].name).toBe('App')
|
|
60
|
+
|
|
61
|
+
expect(result.functions.length).toBe(2) // main, calculate
|
|
62
|
+
|
|
63
|
+
expect(result.imports.length).toBe(1)
|
|
64
|
+
expect(result.imports[0].source).toBe('java.util.List')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('parses Go correctly', async () => {
|
|
68
|
+
const parser = new TreeSitterParser()
|
|
69
|
+
const goContent = `
|
|
70
|
+
package main
|
|
71
|
+
|
|
72
|
+
import (
|
|
73
|
+
"fmt"
|
|
74
|
+
"net/http"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
type Server struct {
|
|
78
|
+
port int
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func (s *Server) Start() {
|
|
82
|
+
fmt.Println("Starting")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func main() {
|
|
86
|
+
s := Server{port: 8080}
|
|
87
|
+
s.Start()
|
|
88
|
+
}
|
|
89
|
+
`
|
|
90
|
+
const result = await parser.parse('main.go', goContent)
|
|
91
|
+
|
|
92
|
+
expect(result.classes.length).toBe(1)
|
|
93
|
+
// struct maps to class in our universal AST
|
|
94
|
+
expect(result.classes[0].name).toBe('Server')
|
|
95
|
+
|
|
96
|
+
expect(result.functions.length).toBe(2) // Start, main
|
|
97
|
+
|
|
98
|
+
expect(result.imports.length).toBe(2)
|
|
99
|
+
expect(result.imports.map((i: any) => i.source)).toContain('fmt')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// EDGE CASES & ERROR HANDLING
|
|
103
|
+
|
|
104
|
+
test('handles empty files gracefully', async () => {
|
|
105
|
+
const parser = new TreeSitterParser()
|
|
106
|
+
const result = await parser.parse('empty.py', '')
|
|
107
|
+
|
|
108
|
+
expect(result.functions.length).toBe(0)
|
|
109
|
+
expect(result.classes.length).toBe(0)
|
|
110
|
+
expect(result.imports.length).toBe(0)
|
|
111
|
+
expect(result.path).toBe('empty.py')
|
|
112
|
+
expect(result.language).toBe('python')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('handles syntax errors in Python code gracefully', async () => {
|
|
116
|
+
const parser = new TreeSitterParser()
|
|
117
|
+
const badPyContent = `
|
|
118
|
+
def good_function():
|
|
119
|
+
print("This is fine")
|
|
120
|
+
|
|
121
|
+
def bad_function(
|
|
122
|
+
print("Missing closing paren and colon"
|
|
123
|
+
|
|
124
|
+
class GoodClass:
|
|
125
|
+
pass
|
|
126
|
+
`
|
|
127
|
+
// Tree-sitter is fault-tolerant, so it should not crash.
|
|
128
|
+
const result = await parser.parse('malformed.py', badPyContent)
|
|
129
|
+
|
|
130
|
+
// We assert that it successfully returns a ParsedFile object without throwing any errors
|
|
131
|
+
expect(result.path).toBe('malformed.py')
|
|
132
|
+
expect(result.language).toBe('python')
|
|
133
|
+
expect(Array.isArray(result.functions)).toBe(true)
|
|
134
|
+
expect(Array.isArray(result.classes)).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('handles unsupported extensions gracefully', async () => {
|
|
138
|
+
const parser = new TreeSitterParser()
|
|
139
|
+
const result = await parser.parse('unknown.xyz', 'some weird content')
|
|
140
|
+
|
|
141
|
+
// Should return a fallback ParsedFile without crashing
|
|
142
|
+
expect(result.functions.length).toBe(0)
|
|
143
|
+
expect(result.classes.length).toBe(0)
|
|
144
|
+
// TreeSitter fallback is 'unknown' for unsupported extensions
|
|
145
|
+
expect(result.language).toBe('unknown')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('handles syntax errors in Java gracefully', async () => {
|
|
149
|
+
const parser = new TreeSitterParser()
|
|
150
|
+
const badJavaContent = `
|
|
151
|
+
public class Main {
|
|
152
|
+
public void goodMethod() {
|
|
153
|
+
int x = 5;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public void badMethod( { // Syntax error here
|
|
157
|
+
System.out.println("Hello"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`
|
|
161
|
+
// Ensure no crash on parse
|
|
162
|
+
const result = await parser.parse('Main.java', badJavaContent)
|
|
163
|
+
|
|
164
|
+
expect(result.path).toBe('Main.java')
|
|
165
|
+
expect(result.language).toBe('java')
|
|
166
|
+
expect(Array.isArray(result.classes)).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
})
|
package/tests/ts-parser.test.ts
CHANGED
|
@@ -74,7 +74,7 @@ describe('ts-parser config "extends" resolution', () => {
|
|
|
74
74
|
`)
|
|
75
75
|
|
|
76
76
|
// Parse and resolve imports
|
|
77
|
-
const parsed = parser.parse(filePath, await fs.readFile(filePath, 'utf-8'))
|
|
77
|
+
const parsed = await parser.parse(filePath, await fs.readFile(filePath, 'utf-8'))
|
|
78
78
|
const resolved = parser.resolveImports([parsed], FIXTURE_DIR)[0]
|
|
79
79
|
|
|
80
80
|
// Check if the aliases mapped correctly using all 3 layers of paths
|
|
@@ -91,3 +91,51 @@ describe('ts-parser config "extends" resolution', () => {
|
|
|
91
91
|
expect(impApp?.resolvedPath).toBe('src/app/local.ts')
|
|
92
92
|
})
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
describe('TypeScriptParser Edge Cases & Fault Tolerance', () => {
|
|
96
|
+
const parser = new TypeScriptParser()
|
|
97
|
+
|
|
98
|
+
it('handles completely empty files', async () => {
|
|
99
|
+
const result = await parser.parse('src/empty.ts', '')
|
|
100
|
+
expect(result.functions).toHaveLength(0)
|
|
101
|
+
expect(result.classes).toHaveLength(0)
|
|
102
|
+
expect(result.language).toBe('typescript')
|
|
103
|
+
expect(result.hash).toBeDefined()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('gracefully degrades on severe syntax errors', async () => {
|
|
107
|
+
const malformedCode = `
|
|
108
|
+
export interface Broke {
|
|
109
|
+
val: string
|
|
110
|
+
// missing closing brace
|
|
111
|
+
|
|
112
|
+
function doThing() {
|
|
113
|
+
const x =
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Injectable()
|
|
117
|
+
export class HalfClass implements {
|
|
118
|
+
`
|
|
119
|
+
const result = await parser.parse('src/broken.ts', malformedCode)
|
|
120
|
+
|
|
121
|
+
// Should not crash, and should extract whatever it can
|
|
122
|
+
expect(result.functions.length).toBeGreaterThanOrEqual(0)
|
|
123
|
+
expect(result.classes.length).toBeGreaterThanOrEqual(0)
|
|
124
|
+
// Must maintain object structure
|
|
125
|
+
expect(Array.isArray(result.imports)).toBe(true)
|
|
126
|
+
expect(typeof result.hash).toBe('string')
|
|
127
|
+
expect(result.language).toBe('typescript')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('handles files with only comments', async () => {
|
|
131
|
+
const commentsCode = `
|
|
132
|
+
/**
|
|
133
|
+
* This file is just documentation
|
|
134
|
+
*/
|
|
135
|
+
// End of file
|
|
136
|
+
`
|
|
137
|
+
const result = await parser.parse('src/docs.ts', commentsCode)
|
|
138
|
+
expect(result.functions).toHaveLength(0)
|
|
139
|
+
expect(result.hash).toBeDefined()
|
|
140
|
+
})
|
|
141
|
+
})
|
package/out.log
DELETED
|
Binary file
|