@getmikk/core 1.8.2 → 1.9.0

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.
Files changed (43) hide show
  1. package/package.json +3 -1
  2. package/src/constants.ts +285 -0
  3. package/src/contract/contract-generator.ts +7 -0
  4. package/src/contract/index.ts +2 -3
  5. package/src/contract/lock-compiler.ts +74 -42
  6. package/src/contract/lock-reader.ts +24 -4
  7. package/src/contract/schema.ts +27 -1
  8. package/src/error-handler.ts +430 -0
  9. package/src/graph/cluster-detector.ts +45 -20
  10. package/src/graph/confidence-engine.ts +60 -0
  11. package/src/graph/dead-code-detector.ts +27 -5
  12. package/src/graph/graph-builder.ts +298 -238
  13. package/src/graph/impact-analyzer.ts +131 -114
  14. package/src/graph/index.ts +4 -0
  15. package/src/graph/memory-manager.ts +345 -0
  16. package/src/graph/query-engine.ts +79 -0
  17. package/src/graph/risk-engine.ts +86 -0
  18. package/src/graph/types.ts +89 -64
  19. package/src/parser/boundary-checker.ts +3 -1
  20. package/src/parser/change-detector.ts +99 -0
  21. package/src/parser/go/go-extractor.ts +28 -9
  22. package/src/parser/go/go-parser.ts +2 -0
  23. package/src/parser/index.ts +88 -38
  24. package/src/parser/javascript/js-extractor.ts +1 -1
  25. package/src/parser/javascript/js-parser.ts +2 -0
  26. package/src/parser/oxc-parser.ts +675 -0
  27. package/src/parser/oxc-resolver.ts +83 -0
  28. package/src/parser/tree-sitter/parser.ts +27 -15
  29. package/src/parser/types.ts +100 -73
  30. package/src/parser/typescript/ts-extractor.ts +241 -537
  31. package/src/parser/typescript/ts-parser.ts +16 -171
  32. package/src/parser/typescript/ts-resolver.ts +11 -1
  33. package/src/search/bm25.ts +5 -2
  34. package/src/utils/minimatch.ts +1 -1
  35. package/tests/contract.test.ts +2 -2
  36. package/tests/dead-code.test.ts +7 -7
  37. package/tests/esm-resolver.test.ts +75 -0
  38. package/tests/graph.test.ts +20 -20
  39. package/tests/helpers.ts +11 -6
  40. package/tests/impact-classified.test.ts +37 -41
  41. package/tests/parser.test.ts +7 -5
  42. package/tests/ts-parser.test.ts +27 -52
  43. package/test-output.txt +0 -373
@@ -1,78 +1,74 @@
1
1
  import { describe, it, expect } from 'bun:test'
2
2
  import { ImpactAnalyzer } from '../src/graph/impact-analyzer'
3
3
  import { buildTestGraph } from './helpers'
4
- import { GraphBuilder } from '../src/graph/graph-builder'
5
4
 
6
5
  describe('ImpactAnalyzer - Classified', () => {
7
6
 
8
7
  it('classifies impacts based on depth and module boundaries', () => {
9
- // Build graph with module boundaries:
10
- // A (module: m1) calls B (module: m1) calls C (module: m2) calls D (module: m3)
11
- // We are changing D. What is the impact?
12
- // D is changed
13
- // C is depth 1, crosses boundary -> CRITICAL
14
- // B is depth 2 -> MEDIUM
15
- // A is depth 3 -> LOW
16
-
8
+ // Calibration for Mikk 2.0 Quantitative Risk:
9
+ // AuthService (Auth keyword: 30) calls BStateAuthDB (State+Auth+DB keywords: 60) calls DBManager (m2, DB keyword: 20) calls PublicAPI (m3)
10
+
17
11
  const graph = buildTestGraph([
18
- ['A', 'B'],
19
- ['B', 'C'],
20
- ['C', 'D'],
21
- ['D', 'nothing']
12
+ ['AuthService', 'BStateAuthDB'],
13
+ ['BStateAuthDB', 'DBManager'],
14
+ ['DBManager', 'PublicAPI'],
15
+ ['PublicAPI', 'nothing']
22
16
  ])
23
17
 
24
18
  // Assign modules manually for the test
25
- graph.nodes.get('src/A.ts')!.moduleId = 'm1'
26
- graph.nodes.get('fn:src/A.ts:A')!.moduleId = 'm1'
27
-
28
- graph.nodes.get('src/B.ts')!.moduleId = 'm1'
29
- graph.nodes.get('fn:src/B.ts:B')!.moduleId = 'm1'
30
-
31
- graph.nodes.get('src/C.ts')!.moduleId = 'm2'
32
- graph.nodes.get('fn:src/C.ts:C')!.moduleId = 'm2'
33
-
34
- graph.nodes.get('src/D.ts')!.moduleId = 'm3'
35
- graph.nodes.get('fn:src/D.ts:D')!.moduleId = 'm3'
19
+ for (const id of graph.nodes.keys()) {
20
+ if (id.includes('authservice')) graph.nodes.get(id)!.moduleId = 'm1'
21
+ if (id.includes('bstateauthdb')) {
22
+ graph.nodes.get(id)!.moduleId = 'm1'
23
+ graph.nodes.get(id)!.metadata!.isExported = true
24
+ }
25
+ if (id.includes('dbmanager')) graph.nodes.get(id)!.moduleId = 'm2'
26
+ if (id.includes('publicapi')) graph.nodes.get(id)!.moduleId = 'm3'
27
+ }
36
28
 
37
29
  const analyzer = new ImpactAnalyzer(graph)
38
- const result = analyzer.analyze(['fn:src/D.ts:D'])
30
+ const result = analyzer.analyze(['fn:src/publicapi.ts:publicapi'])
39
31
 
40
32
  expect(result.impacted.length).toBe(3)
41
33
 
34
+ // Critical: DBManager (boosted to 80)
42
35
  expect(result.classified.critical).toHaveLength(1)
43
- expect(result.classified.critical[0].nodeId).toBe('fn:src/C.ts:C')
44
-
45
- expect(result.classified.high).toHaveLength(0) // No depth 1 in same module
36
+ expect(result.classified.critical[0].nodeId).toBe('fn:src/dbmanager.ts:dbmanager')
46
37
 
47
- expect(result.classified.medium).toHaveLength(1)
48
- expect(result.classified.medium[0].nodeId).toBe('fn:src/B.ts:B')
38
+ // High: BStateAuthDB (depth 2)
39
+ // Base: 3.5 + Auth(30) + DB(20) + Exported(15) = 68.5 -> HIGH.
40
+ expect(result.classified.high).toHaveLength(1)
41
+ expect(result.classified.high[0].nodeId).toBe('fn:src/bstateauthdb.ts:bstateauthdb')
49
42
 
43
+ // Low: AuthService (30 risk)
50
44
  expect(result.classified.low).toHaveLength(1)
51
- expect(result.classified.low[0].nodeId).toBe('fn:src/A.ts:A')
45
+ expect(result.classified.low[0].nodeId).toBe('fn:src/authservice.ts:authservice')
52
46
  })
53
47
 
54
48
  it('classifies same-module depth-1 impact as HIGH, not CRITICAL', () => {
55
- // E (module: m4) calls F (module: m4) calls G (module: m4)
56
- // change G
57
- // F is depth 1, same module -> HIGH
58
- // E is depth 2 -> MEDIUM
49
+ // HighRiskAuthService calls AuthDBInstance calls G
59
50
  const graph = buildTestGraph([
60
- ['E', 'F'],
61
- ['F', 'G'],
51
+ ['HighRiskAuthService', 'AuthDBInstance'],
52
+ ['AuthDBInstance', 'G'],
62
53
  ['G', 'nothing']
63
54
  ])
64
55
 
65
56
  for (const id of graph.nodes.keys()) {
66
57
  graph.nodes.get(id)!.moduleId = 'm4'
58
+ if (id.includes('authdbinstance')) {
59
+ graph.nodes.get(id)!.metadata!.isExported = true
60
+ }
67
61
  }
68
62
 
69
63
  const analyzer = new ImpactAnalyzer(graph)
70
- const result = analyzer.analyze(['fn:src/G.ts:G'])
64
+ const result = analyzer.analyze(['fn:src/g.ts:g'])
71
65
 
72
66
  expect(result.classified.critical).toHaveLength(0)
73
67
  expect(result.classified.high).toHaveLength(1)
74
- expect(result.classified.high[0].nodeId).toBe('fn:src/F.ts:F') // depth 1
75
- expect(result.classified.medium).toHaveLength(1)
76
- expect(result.classified.medium[0].nodeId).toBe('fn:src/E.ts:E') // depth 2
68
+ expect(result.classified.high[0].nodeId).toBe('fn:src/authdbinstance.ts:authdbinstance')
69
+
70
+ // HighRiskAuthService: depth 2, risk score 35.5 -> LOW
71
+ expect(result.classified.low).toHaveLength(1)
72
+ expect(result.classified.low[0].nodeId).toBe('fn:src/highriskauthservice.ts:highriskauthservice')
77
73
  })
78
74
  })
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'bun:test'
2
2
  import { TypeScriptParser } from '../src/parser/typescript/ts-parser'
3
+ import { OxcParser } from '../src/parser/oxc-parser'
3
4
  import { TypeScriptExtractor } from '../src/parser/typescript/ts-extractor'
4
5
  import { TypeScriptResolver } from '../src/parser/typescript/ts-resolver'
5
6
  import { getParser } from '../src/parser/index'
@@ -53,7 +54,7 @@ describe('TypeScriptExtractor', () => {
53
54
  }
54
55
  `)
55
56
  const fns = extractor.extractFunctions()
56
- expect(fns[0].calls).toContain('jwtDecode')
57
+ expect(fns[0].calls.some(c => c.name === 'jwtDecode')).toBe(true)
57
58
  })
58
59
 
59
60
  it('extracts imports', () => {
@@ -147,6 +148,7 @@ describe('TypeScriptParser', () => {
147
148
  }
148
149
  `)
149
150
  expect(result.functions).toHaveLength(1)
151
+ expect(result.functions[0].calls.some(c => c.name === 'jwtDecode')).toBe(true)
150
152
  expect(result.imports).toHaveLength(1)
151
153
  expect(result.exports).toHaveLength(1)
152
154
  expect(result.hash).toBeDefined()
@@ -202,14 +204,14 @@ describe('TypeScriptResolver', () => {
202
204
  })
203
205
 
204
206
  describe('getParser', () => {
205
- it('returns TypeScriptParser for .ts files', () => {
207
+ it('returns OxcParser for .ts files', () => {
206
208
  const parser = getParser('src/auth.ts')
207
- expect(parser).toBeInstanceOf(TypeScriptParser)
209
+ expect(parser).toBeInstanceOf(OxcParser)
208
210
  })
209
211
 
210
- it('returns TypeScriptParser for .tsx files', () => {
212
+ it('returns OxcParser for .tsx files', () => {
211
213
  const parser = getParser('src/App.tsx')
212
- expect(parser).toBeInstanceOf(TypeScriptParser)
214
+ expect(parser).toBeInstanceOf(OxcParser)
213
215
  })
214
216
 
215
217
  it('throws for unsupported extensions', () => {
@@ -1,56 +1,34 @@
1
1
  import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
2
2
  import * as path from 'node:path'
3
3
  import * as fs from 'node:fs/promises'
4
- import { TypeScriptParser } from '../src/parser/typescript/ts-parser'
5
- import { getParser } from '../src/parser/index'
4
+ import { OxcParser } from '../src/parser/oxc-parser'
6
5
 
7
- describe('ts-parser config "extends" resolution', () => {
6
+ describe('ts-parser config resolution', () => {
8
7
  const FIXTURE_DIR = path.join(process.cwd(), '.test-fixture-tsconfig')
9
8
 
10
9
  beforeAll(async () => {
11
- // Create a temporary directory structure to test tsconfig extends
12
10
  await fs.mkdir(FIXTURE_DIR, { recursive: true })
13
- await fs.mkdir(path.join(FIXTURE_DIR, 'node_modules', '@tsconfig', 'node20'), { recursive: true })
14
-
15
- // 1. node_modules package tsconfig
11
+ // Use a single tsconfig with all paths to verify OxcResolver's basic path mapping
16
12
  await fs.writeFile(
17
- path.join(FIXTURE_DIR, 'node_modules', '@tsconfig', 'node20', 'tsconfig.json'),
18
- JSON.stringify({
19
- compilerOptions: {
20
- target: 'es2022',
21
- module: 'commonjs',
22
- paths: { '@base/*': ['src/base/*'] }
23
- }
24
- })
25
- )
26
-
27
- // 2. Local tsconfig.base.json extending the node_modules one
28
- await fs.writeFile(
29
- path.join(FIXTURE_DIR, 'tsconfig.base.json'),
13
+ path.join(FIXTURE_DIR, 'tsconfig.json'),
30
14
  JSON.stringify({
31
- extends: '@tsconfig/node20/tsconfig.json',
32
15
  compilerOptions: {
33
16
  baseUrl: '.',
34
17
  paths: {
35
- '@lib/*': ['src/lib/*']
18
+ '@base/*': ['src/base/*'],
19
+ '@lib/*': ['src/lib/*'],
20
+ '@app/*': ['src/app/*']
36
21
  }
37
22
  }
38
23
  })
39
24
  )
40
25
 
41
- // 3. Project tsconfig.json extending local base
42
26
  await fs.writeFile(
43
- path.join(FIXTURE_DIR, 'tsconfig.json'),
44
- `{
45
- "extends": "./tsconfig.base.json",
46
- // Comments should be ignored!
47
- /* Block comments too */
48
- "compilerOptions": {
49
- "paths": {
50
- "@app/*": ["src/app/*"]
51
- }
52
- }
53
- }`
27
+ path.join(FIXTURE_DIR, 'package.json'),
28
+ JSON.stringify({
29
+ name: 'test-fixture',
30
+ type: 'module'
31
+ })
54
32
  )
55
33
  })
56
34
 
@@ -58,14 +36,18 @@ describe('ts-parser config "extends" resolution', () => {
58
36
  await fs.rm(FIXTURE_DIR, { recursive: true, force: true })
59
37
  })
60
38
 
61
- it('recursively merges compiler paths from extended configs', async () => {
62
- // We'll test this indirectly by creating a dummy file and parsing it
63
- // and letting ts-parser resolve its imports based on the merged tsconfig
64
- const parser = new TypeScriptParser()
39
+ it('resolves compiler paths from tsconfig', async () => {
40
+ const parser = new OxcParser()
65
41
 
66
- // Write a test source file
67
42
  const srcDir = path.join(FIXTURE_DIR, 'src', 'app')
68
43
  await fs.mkdir(srcDir, { recursive: true })
44
+ await fs.mkdir(path.join(FIXTURE_DIR, 'src', 'base'), { recursive: true })
45
+ await fs.mkdir(path.join(FIXTURE_DIR, 'src', 'lib'), { recursive: true })
46
+
47
+ await fs.writeFile(path.join(FIXTURE_DIR, 'src', 'base', 'core.ts'), 'export const c = 1')
48
+ await fs.writeFile(path.join(FIXTURE_DIR, 'src', 'lib', 'shared.ts'), 'export const b = 1')
49
+ await fs.writeFile(path.join(FIXTURE_DIR, 'src', 'app', 'local.ts'), 'export const a = 1')
50
+
69
51
  const filePath = path.join(srcDir, 'index.ts')
70
52
  await fs.writeFile(filePath, `
71
53
  import { a } from '@app/local'
@@ -77,23 +59,19 @@ describe('ts-parser config "extends" resolution', () => {
77
59
  const parsed = await parser.parse(filePath, await fs.readFile(filePath, 'utf-8'))
78
60
  const resolved = parser.resolveImports([parsed], FIXTURE_DIR)[0]
79
61
 
80
- // Check if the aliases mapped correctly using all 3 layers of paths
81
- // Base config mapping: @base/* -> src/base/*
82
- const impBase = resolved.imports.find(i => i.source === '@base/core')
83
- expect(impBase?.resolvedPath).toBe('src/base/core.ts')
62
+ const impApp = resolved.imports.find(i => i.source === '@app/local')
63
+ expect(impApp?.resolvedPath).toBe(path.join(FIXTURE_DIR, 'src/app/local.ts').replace(/\\/g, '/'))
84
64
 
85
- // Mid config mapping: @lib/* -> src/lib/*
86
65
  const impLib = resolved.imports.find(i => i.source === '@lib/shared')
87
- expect(impLib?.resolvedPath).toBe('src/lib/shared.ts')
66
+ expect(impLib?.resolvedPath).toBe(path.join(FIXTURE_DIR, 'src/lib/shared.ts').replace(/\\/g, '/'))
88
67
 
89
- // Top config mapping: @app/* -> src/app/*
90
- const impApp = resolved.imports.find(i => i.source === '@app/local')
91
- expect(impApp?.resolvedPath).toBe('src/app/local.ts')
68
+ const impBase = resolved.imports.find(i => i.source === '@base/core')
69
+ expect(impBase?.resolvedPath).toBe(path.join(FIXTURE_DIR, 'src/base/core.ts').replace(/\\/g, '/'))
92
70
  })
93
71
  })
94
72
 
95
73
  describe('TypeScriptParser Edge Cases & Fault Tolerance', () => {
96
- const parser = new TypeScriptParser()
74
+ const parser = new OxcParser()
97
75
 
98
76
  it('handles completely empty files', async () => {
99
77
  const result = await parser.parse('src/empty.ts', '')
@@ -117,11 +95,8 @@ describe('TypeScriptParser Edge Cases & Fault Tolerance', () => {
117
95
  export class HalfClass implements {
118
96
  `
119
97
  const result = await parser.parse('src/broken.ts', malformedCode)
120
-
121
- // Should not crash, and should extract whatever it can
122
98
  expect(result.functions.length).toBeGreaterThanOrEqual(0)
123
99
  expect(result.classes.length).toBeGreaterThanOrEqual(0)
124
- // Must maintain object structure
125
100
  expect(Array.isArray(result.imports)).toBe(true)
126
101
  expect(typeof result.hash).toBe('string')
127
102
  expect(result.language).toBe('typescript')