@getmikk/core 2.0.13 → 2.0.15
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 +4 -4
- package/package.json +2 -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 +278 -0
- package/src/cache/index.ts +1 -0
- package/src/contract/contract-generator.ts +31 -3
- package/src/contract/contract-reader.ts +1 -0
- package/src/contract/lock-compiler.ts +125 -12
- package/src/contract/schema.ts +4 -0
- package/src/error-handler.ts +2 -1
- package/src/graph/cluster-detector.ts +2 -4
- package/src/graph/dead-code-detector.ts +303 -117
- package/src/graph/graph-builder.ts +21 -161
- package/src/graph/impact-analyzer.ts +1 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/rich-function-index.ts +1080 -0
- package/src/graph/symbol-table.ts +252 -0
- package/src/hash/hash-store.ts +1 -0
- package/src/index.ts +4 -0
- package/src/parser/base-extractor.ts +19 -0
- package/src/parser/boundary-checker.ts +31 -12
- package/src/parser/error-recovery.ts +647 -0
- package/src/parser/function-body-extractor.ts +248 -0
- package/src/parser/go/go-extractor.ts +249 -676
- package/src/parser/index.ts +138 -295
- package/src/parser/language-registry.ts +57 -0
- package/src/parser/oxc-parser.ts +166 -28
- package/src/parser/oxc-resolver.ts +179 -11
- package/src/parser/parser-constants.ts +1 -0
- package/src/parser/rust/rust-extractor.ts +109 -0
- package/src/parser/tree-sitter/parser.ts +400 -66
- package/src/parser/tree-sitter/queries.ts +106 -10
- package/src/parser/types.ts +20 -1
- package/src/search/bm25.ts +21 -8
- package/src/search/direct-search.ts +472 -0
- package/src/search/embedding-provider.ts +249 -0
- package/src/search/index.ts +12 -0
- package/src/search/semantic-search.ts +435 -0
- package/src/security/index.ts +1 -0
- package/src/security/scanner.ts +342 -0
- package/src/utils/artifact-transaction.ts +1 -0
- package/src/utils/atomic-write.ts +1 -0
- package/src/utils/errors.ts +89 -4
- package/src/utils/fs.ts +150 -65
- package/src/utils/json.ts +1 -0
- package/src/utils/language-registry.ts +96 -5
- package/src/utils/minimatch.ts +49 -6
- package/src/utils/path.ts +26 -0
- package/tests/dead-code.test.ts +3 -2
- package/tests/direct-search.test.ts +435 -0
- package/tests/error-recovery.test.ts +143 -0
- package/tests/fixtures/simple-api/src/index.ts +1 -1
- package/tests/go-parser.test.ts +19 -335
- package/tests/js-parser.test.ts +18 -1089
- package/tests/language-registry-all.test.ts +276 -0
- package/tests/language-registry.test.ts +6 -4
- package/tests/parse-diagnostics.test.ts +9 -96
- package/tests/parser.test.ts +42 -771
- package/tests/polyglot-parser.test.ts +117 -0
- package/tests/rich-function-index.test.ts +703 -0
- package/tests/tree-sitter-parser.test.ts +108 -80
- package/tests/ts-parser.test.ts +8 -8
- package/tests/verification.test.ts +175 -0
- package/src/parser/base-parser.ts +0 -16
- package/src/parser/go/go-parser.ts +0 -43
- package/src/parser/javascript/js-extractor.ts +0 -278
- package/src/parser/javascript/js-parser.ts +0 -101
- package/src/parser/typescript/ts-extractor.ts +0 -447
- package/src/parser/typescript/ts-parser.ts +0 -36
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import type { RichFunction } from '../graph/rich-function-index.js'
|
|
4
|
+
|
|
5
|
+
export interface FunctionBody {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
file: string
|
|
9
|
+
startLine: number
|
|
10
|
+
endLine: number
|
|
11
|
+
body: string
|
|
12
|
+
fullSource: string
|
|
13
|
+
isComplete: boolean
|
|
14
|
+
trimmedLines: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BodyExtractOptions {
|
|
18
|
+
maxLines?: number
|
|
19
|
+
includeComments?: boolean
|
|
20
|
+
includeImports?: boolean
|
|
21
|
+
contextLines?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FunctionBodyExtractor {
|
|
25
|
+
private cache: Map<string, string> = new Map()
|
|
26
|
+
private bodyCache: Map<string, FunctionBody> = new Map()
|
|
27
|
+
|
|
28
|
+
async extractBody(fn: RichFunction, options: BodyExtractOptions = {}): Promise<FunctionBody | null> {
|
|
29
|
+
const cacheKey = `${fn.id}:${fn.startLine}:${fn.endLine}`
|
|
30
|
+
|
|
31
|
+
if (this.bodyCache.has(cacheKey)) {
|
|
32
|
+
return this.bodyCache.get(cacheKey)!
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { maxLines = 500, contextLines = 0 } = options
|
|
36
|
+
|
|
37
|
+
const source = await this.readSource(fn.file)
|
|
38
|
+
if (!source) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lines = source.split('\n')
|
|
43
|
+
|
|
44
|
+
const startLine = Math.max(0, fn.startLine - 1 - contextLines)
|
|
45
|
+
const endLine = Math.min(lines.length, fn.endLine + contextLines)
|
|
46
|
+
|
|
47
|
+
const bodyLines = lines.slice(startLine, endLine)
|
|
48
|
+
const body = bodyLines.join('\n')
|
|
49
|
+
|
|
50
|
+
const isComplete = fn.endLine <= lines.length && bodyLines.length >= fn.endLine - fn.startLine
|
|
51
|
+
|
|
52
|
+
const result: FunctionBody = {
|
|
53
|
+
id: fn.id,
|
|
54
|
+
name: fn.name,
|
|
55
|
+
file: fn.file,
|
|
56
|
+
startLine: fn.startLine,
|
|
57
|
+
endLine: fn.endLine,
|
|
58
|
+
body,
|
|
59
|
+
fullSource: source,
|
|
60
|
+
isComplete,
|
|
61
|
+
trimmedLines: lines.length - endLine,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.bodyCache.set(cacheKey, result)
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async extractBodies(fns: RichFunction[], options: BodyExtractOptions = {}): Promise<Map<string, FunctionBody>> {
|
|
69
|
+
const results = new Map<string, FunctionBody>()
|
|
70
|
+
|
|
71
|
+
for (const fn of fns) {
|
|
72
|
+
const body = await this.extractBody(fn, options)
|
|
73
|
+
if (body) {
|
|
74
|
+
results.set(fn.id, body)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return results
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async extractBodiesByIds(ids: string[], getFn: (id: string) => RichFunction | undefined, options: BodyExtractOptions = {}): Promise<Map<string, FunctionBody>> {
|
|
82
|
+
const results = new Map<string, FunctionBody>()
|
|
83
|
+
|
|
84
|
+
const fns = ids.map(id => getFn(id)).filter(Boolean) as RichFunction[]
|
|
85
|
+
const bodies = await this.extractBodies(fns, options)
|
|
86
|
+
|
|
87
|
+
for (const [id, body] of bodies) {
|
|
88
|
+
results.set(id, body)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return results
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async readSource(filePath: string): Promise<string | null> {
|
|
95
|
+
if (this.cache.has(filePath)) {
|
|
96
|
+
return this.cache.get(filePath)!
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!existsSync(filePath)) {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const content = await readFile(filePath, 'utf-8')
|
|
105
|
+
this.cache.set(filePath, content)
|
|
106
|
+
return content
|
|
107
|
+
} catch {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
clearCache(): void {
|
|
113
|
+
this.cache.clear()
|
|
114
|
+
this.bodyCache.clear()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getCachedCount(): number {
|
|
118
|
+
return this.cache.size
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getBodyCacheCount(): number {
|
|
122
|
+
return this.bodyCache.size
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ExtractResult {
|
|
127
|
+
success: boolean
|
|
128
|
+
body?: string
|
|
129
|
+
signature?: string
|
|
130
|
+
params?: string
|
|
131
|
+
returnType?: string
|
|
132
|
+
docComment?: string
|
|
133
|
+
error?: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function extractFunction(
|
|
137
|
+
filePath: string,
|
|
138
|
+
functionName: string,
|
|
139
|
+
options: BodyExtractOptions = {}
|
|
140
|
+
): Promise<ExtractResult> {
|
|
141
|
+
if (!existsSync(filePath)) {
|
|
142
|
+
return { success: false, error: 'File not found' }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const content = await readFile(filePath, 'utf-8')
|
|
147
|
+
const lines = content.split('\n')
|
|
148
|
+
|
|
149
|
+
let startLine = -1
|
|
150
|
+
let endLine = -1
|
|
151
|
+
let braceCount = 0
|
|
152
|
+
let inFunction = false
|
|
153
|
+
let foundOpening = false
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < lines.length; i++) {
|
|
156
|
+
const line = lines[i]
|
|
157
|
+
|
|
158
|
+
if (!inFunction && (line.includes(`function ${functionName}`) || line.includes(`const ${functionName}`) || line.includes(`${functionName}(`) || line.includes(`async ${functionName}`))) {
|
|
159
|
+
if (line.includes(`function ${functionName}`) || line.includes(`async function ${functionName}`) || line.match(new RegExp(`(const|let|var)\\s+${functionName}\\s*=`)) || line.match(new RegExp(`${functionName}\\s*\\(`))) {
|
|
160
|
+
inFunction = true
|
|
161
|
+
startLine = i
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (inFunction) {
|
|
166
|
+
for (const char of line) {
|
|
167
|
+
if (char === '{') {
|
|
168
|
+
braceCount++
|
|
169
|
+
foundOpening = true
|
|
170
|
+
} else if (char === '}') {
|
|
171
|
+
braceCount--
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (foundOpening && braceCount === 0) {
|
|
176
|
+
endLine = i + 1
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (startLine === -1 || endLine === -1) {
|
|
183
|
+
return { success: false, error: 'Function not found' }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const bodyLines = lines.slice(startLine, endLine)
|
|
187
|
+
const body = bodyLines.join('\n')
|
|
188
|
+
|
|
189
|
+
const maxLines = options.maxLines || 500
|
|
190
|
+
const trimmedBody = bodyLines.length > maxLines
|
|
191
|
+
? bodyLines.slice(0, maxLines).join('\n') + '\n// ... (truncated)'
|
|
192
|
+
: body
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
body: trimmedBody,
|
|
197
|
+
signature: extractSignature(bodyLines[0] || ''),
|
|
198
|
+
docComment: extractDocComment(lines, startLine),
|
|
199
|
+
}
|
|
200
|
+
} catch (err: any) {
|
|
201
|
+
return { success: false, error: err.message }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function extractSignature(line: string): string {
|
|
206
|
+
const match = line.match(/(?:async\s+)?(?:function\s+)?(?:\w+\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?/)
|
|
207
|
+
return match ? match[0] : line.trim()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function extractDocComment(lines: string[], startLine: number): string | undefined {
|
|
211
|
+
if (startLine === 0) return undefined
|
|
212
|
+
|
|
213
|
+
const prevLine = lines[startLine - 1]
|
|
214
|
+
if (prevLine && (prevLine.includes('/**') || prevLine.includes('///') || prevLine.startsWith('//'))) {
|
|
215
|
+
const commentLines: string[] = []
|
|
216
|
+
let i = startLine - 1
|
|
217
|
+
|
|
218
|
+
while (i >= 0) {
|
|
219
|
+
const line = lines[i]
|
|
220
|
+
if (line.includes('/**') || line.includes("'''")) {
|
|
221
|
+
commentLines.unshift(line)
|
|
222
|
+
break
|
|
223
|
+
} else if (line.trim().startsWith('*') || line.trim().startsWith('//')) {
|
|
224
|
+
commentLines.unshift(line)
|
|
225
|
+
} else {
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
i--
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return commentLines.join('\n') || undefined
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return undefined
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function trimBody(body: string, maxLines: number): string {
|
|
238
|
+
const lines = body.split('\n')
|
|
239
|
+
if (lines.length <= maxLines) return body
|
|
240
|
+
|
|
241
|
+
return lines.slice(0, maxLines).join('\n') + '\n// ...'
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function getBodyPreview(body: string, maxChars: number = 200): string {
|
|
245
|
+
const firstLine = body.split('\n')[0]
|
|
246
|
+
if (firstLine.length <= maxChars) return firstLine
|
|
247
|
+
return firstLine.slice(0, maxChars) + '...'
|
|
248
|
+
}
|