@getmikk/core 2.0.10 → 2.0.12
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/package.json +9 -7
- package/src/contract/contract-generator.ts +0 -2
- package/src/contract/contract-writer.ts +0 -1
- package/src/contract/lock-compiler.ts +1 -3
- package/src/graph/confidence-engine.ts +41 -20
- package/src/graph/impact-analyzer.ts +21 -6
- package/src/parser/index.ts +71 -1
- package/src/parser/oxc-resolver.ts +31 -3
- package/src/parser/tree-sitter/parser.ts +369 -150
- package/src/parser/tree-sitter/queries.ts +102 -16
- package/src/parser/tree-sitter/resolver.ts +261 -0
- package/src/search/bm25.ts +16 -2
- package/src/utils/fs.ts +31 -1
- package/tests/fixtures/python-service/src/auth.py +36 -0
- package/tests/graph.test.ts +2 -1
- package/tests/js-parser.test.ts +444 -0
- package/tests/parser.test.ts +718 -184
- package/tests/tree-sitter-parser.test.ts +827 -130
package/tests/js-parser.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, test, expect } from 'bun:test'
|
|
|
2
2
|
import { JavaScriptExtractor } from '../src/parser/javascript/js-extractor'
|
|
3
3
|
import { JavaScriptParser } from '../src/parser/javascript/js-parser'
|
|
4
4
|
import { JavaScriptResolver } from '../src/parser/javascript/js-resolver'
|
|
5
|
+
import { TypeScriptExtractor } from '../src/parser/typescript/ts-extractor'
|
|
5
6
|
import { getParser } from '../src/parser/index'
|
|
6
7
|
|
|
7
8
|
// ─── Sample JS source files ───────────────────────────────────────────────────
|
|
@@ -649,3 +650,446 @@ describe('getParser — JS extensions', () => {
|
|
|
649
650
|
expect(() => getParser('src/app.xyz')).toThrow()
|
|
650
651
|
})
|
|
651
652
|
})
|
|
653
|
+
|
|
654
|
+
// ==========================================
|
|
655
|
+
// ADDITIONAL COMPREHENSIVE TESTS
|
|
656
|
+
// ==========================================
|
|
657
|
+
|
|
658
|
+
describe('JavaScript - Additional Edge Cases', () => {
|
|
659
|
+
|
|
660
|
+
describe('Dynamic Imports', () => {
|
|
661
|
+
test('handles dynamic import()', () => {
|
|
662
|
+
const src = `
|
|
663
|
+
const module = await import('./dynamic')
|
|
664
|
+
const mod = await import('lodash')
|
|
665
|
+
`
|
|
666
|
+
const ext = new JavaScriptExtractor('src/app.js', src)
|
|
667
|
+
const imports = ext.extractImports()
|
|
668
|
+
expect(imports.length).toBeGreaterThanOrEqual(0)
|
|
669
|
+
})
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
describe('Class Syntax', () => {
|
|
673
|
+
test('extracts ES6 classes', () => {
|
|
674
|
+
const src = `
|
|
675
|
+
class User {
|
|
676
|
+
constructor(name) {
|
|
677
|
+
this.name = name
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
getName() {
|
|
681
|
+
return this.name
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
static create(data) {
|
|
685
|
+
return new User(data.name)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
class Admin extends User {
|
|
690
|
+
constructor(name, role) {
|
|
691
|
+
super(name)
|
|
692
|
+
this.role = role
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
`
|
|
696
|
+
const ext = new JavaScriptExtractor('src/user.js', src)
|
|
697
|
+
const classes = ext.extractClasses()
|
|
698
|
+
expect(classes.length).toBe(2)
|
|
699
|
+
expect(classes[0].name).toBe('User')
|
|
700
|
+
expect(classes[1].name).toBe('Admin')
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
test('extracts class methods', () => {
|
|
704
|
+
const src = `
|
|
705
|
+
class Calculator {
|
|
706
|
+
add(a, b) { return a + b }
|
|
707
|
+
subtract(a, b) { return a - b }
|
|
708
|
+
}
|
|
709
|
+
`
|
|
710
|
+
const ext = new JavaScriptExtractor('src/calc.js', src)
|
|
711
|
+
const classes = ext.extractClasses()
|
|
712
|
+
expect(classes[0].methods.length).toBe(2)
|
|
713
|
+
})
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
describe('Object Patterns', () => {
|
|
717
|
+
test('handles object literal functions', () => {
|
|
718
|
+
const src = `
|
|
719
|
+
function formatDate() { return 'date' }
|
|
720
|
+
function parseJSON() { return 'json' }
|
|
721
|
+
`
|
|
722
|
+
const ext = new JavaScriptExtractor('src/utils.js', src)
|
|
723
|
+
const fns = ext.extractFunctions()
|
|
724
|
+
expect(fns.length).toBe(2)
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
test('handles computed property names', () => {
|
|
728
|
+
const src = `
|
|
729
|
+
const obj = {
|
|
730
|
+
[key]: value,
|
|
731
|
+
['computed' + 'Key']() {}
|
|
732
|
+
}
|
|
733
|
+
`
|
|
734
|
+
const ext = new JavaScriptExtractor('src/obj.js', src)
|
|
735
|
+
expect(() => ext.extractFunctions()).not.toThrow()
|
|
736
|
+
})
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
describe('Arrow Functions', () => {
|
|
740
|
+
test('handles various arrow function patterns', () => {
|
|
741
|
+
const src = `
|
|
742
|
+
const add = (a, b) => a + b
|
|
743
|
+
const greet = name => \`Hello \${name}\`
|
|
744
|
+
const promise = () => new Promise(resolve => resolve())
|
|
745
|
+
const multi = (a, b) => {
|
|
746
|
+
return a + b
|
|
747
|
+
}
|
|
748
|
+
`
|
|
749
|
+
const ext = new JavaScriptExtractor('src/arrow.js', src)
|
|
750
|
+
const fns = ext.extractFunctions()
|
|
751
|
+
expect(fns.length).toBe(4)
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
describe('Callback Patterns', () => {
|
|
756
|
+
test('handles callback functions', () => {
|
|
757
|
+
const src = `
|
|
758
|
+
items.forEach(item => console.log(item))
|
|
759
|
+
const filtered = items.filter(x => x > 0)
|
|
760
|
+
const mapped = items.map(x => x * 2)
|
|
761
|
+
const found = items.find(x => x.id === id)
|
|
762
|
+
`
|
|
763
|
+
const ext = new JavaScriptExtractor('src/callbacks.js', src)
|
|
764
|
+
const fns = ext.extractFunctions()
|
|
765
|
+
expect(fns.length).toBe(0) // callbacks aren't function declarations
|
|
766
|
+
})
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
describe('Error Handling in Functions', () => {
|
|
770
|
+
test('detects try-catch blocks', () => {
|
|
771
|
+
const src = `
|
|
772
|
+
function safeParse(json) {
|
|
773
|
+
try {
|
|
774
|
+
return JSON.parse(json)
|
|
775
|
+
} catch (e) {
|
|
776
|
+
console.error(e)
|
|
777
|
+
return null
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
`
|
|
781
|
+
const ext = new JavaScriptExtractor('src/safe.js', src)
|
|
782
|
+
const fns = ext.extractFunctions()
|
|
783
|
+
expect(fns[0].errorHandling.length).toBeGreaterThan(0)
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
test('detects throw statements', () => {
|
|
787
|
+
const src = `
|
|
788
|
+
function validate(value) {
|
|
789
|
+
if (!value) {
|
|
790
|
+
throw new Error('value required')
|
|
791
|
+
}
|
|
792
|
+
return true
|
|
793
|
+
}
|
|
794
|
+
`
|
|
795
|
+
const ext = new JavaScriptExtractor('src/validate.js', src)
|
|
796
|
+
const fns = ext.extractFunctions()
|
|
797
|
+
expect(fns[0].errorHandling.some(e => e.type === 'throw')).toBe(true)
|
|
798
|
+
})
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
describe('Complex Return Statements', () => {
|
|
802
|
+
test('handles early returns', () => {
|
|
803
|
+
const src = `
|
|
804
|
+
function findUser(id) {
|
|
805
|
+
if (!id) return null
|
|
806
|
+
const user = db.find(id)
|
|
807
|
+
if (!user) return null
|
|
808
|
+
return user
|
|
809
|
+
}
|
|
810
|
+
`
|
|
811
|
+
const ext = new JavaScriptExtractor('src/find.js', src)
|
|
812
|
+
const fns = ext.extractFunctions()
|
|
813
|
+
expect(fns[0].edgeCasesHandled.length).toBeGreaterThan(0)
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
test('handles conditional returns', () => {
|
|
817
|
+
const src = `
|
|
818
|
+
function getStatus(isActive) {
|
|
819
|
+
return isActive ? 'active' : 'inactive'
|
|
820
|
+
}
|
|
821
|
+
`
|
|
822
|
+
const ext = new JavaScriptExtractor('src/status.js', src)
|
|
823
|
+
const fns = ext.extractFunctions()
|
|
824
|
+
expect(fns.length).toBe(1)
|
|
825
|
+
})
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
describe('Async/Await', () => {
|
|
829
|
+
test('handles async arrow functions', () => {
|
|
830
|
+
const src = `
|
|
831
|
+
const fetchData = async (url) => {
|
|
832
|
+
const res = await fetch(url)
|
|
833
|
+
return res.json()
|
|
834
|
+
}
|
|
835
|
+
`
|
|
836
|
+
const ext = new JavaScriptExtractor('src/async.js', src)
|
|
837
|
+
const fns = ext.extractFunctions()
|
|
838
|
+
expect(fns[0].isAsync).toBe(true)
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
test('handles await without async wrapper', () => {
|
|
842
|
+
const src = `
|
|
843
|
+
async function main() {
|
|
844
|
+
await doSomething()
|
|
845
|
+
}
|
|
846
|
+
`
|
|
847
|
+
const ext = new JavaScriptExtractor('src/main.js', src)
|
|
848
|
+
const fns = ext.extractFunctions()
|
|
849
|
+
expect(fns[0].isAsync).toBe(true)
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
test('handles Promise.all', () => {
|
|
853
|
+
const src = `
|
|
854
|
+
async function loadAll(urls) {
|
|
855
|
+
return Promise.all(urls.map(fetch))
|
|
856
|
+
}
|
|
857
|
+
`
|
|
858
|
+
const ext = new JavaScriptExtractor('src/load.js', src)
|
|
859
|
+
const fns = ext.extractFunctions()
|
|
860
|
+
expect(fns[0].isAsync).toBe(true)
|
|
861
|
+
})
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
describe('Generator Functions', () => {
|
|
865
|
+
test('handles generator functions', () => {
|
|
866
|
+
const src = `
|
|
867
|
+
function* numberGenerator() {
|
|
868
|
+
yield 1
|
|
869
|
+
yield 2
|
|
870
|
+
yield 3
|
|
871
|
+
}
|
|
872
|
+
`
|
|
873
|
+
const ext = new JavaScriptExtractor('src/gen.js', src)
|
|
874
|
+
const fns = ext.extractFunctions()
|
|
875
|
+
expect(fns.length).toBe(1)
|
|
876
|
+
})
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
describe('Destructuring', () => {
|
|
880
|
+
test('handles destructured parameters', () => {
|
|
881
|
+
const src = `
|
|
882
|
+
function process({ name, age }, [first, ...rest]) {
|
|
883
|
+
return name + age + first
|
|
884
|
+
}
|
|
885
|
+
`
|
|
886
|
+
const ext = new JavaScriptExtractor('src/dest.js', src)
|
|
887
|
+
const fns = ext.extractFunctions()
|
|
888
|
+
expect(fns[0].params.length).toBe(2)
|
|
889
|
+
})
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
describe('Rest/Spread', () => {
|
|
893
|
+
test('handles rest parameters', () => {
|
|
894
|
+
const src = `
|
|
895
|
+
function sum(...numbers) {
|
|
896
|
+
return numbers.reduce((a, b) => a + b, 0)
|
|
897
|
+
}
|
|
898
|
+
`
|
|
899
|
+
const ext = new JavaScriptExtractor('src/rest.js', src)
|
|
900
|
+
const fns = ext.extractFunctions()
|
|
901
|
+
expect(fns[0].params[0].name).toBe('numbers')
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
test('handles spread in function calls', () => {
|
|
905
|
+
const src = `
|
|
906
|
+
function apply(...args) {
|
|
907
|
+
return fn(...args)
|
|
908
|
+
}
|
|
909
|
+
`
|
|
910
|
+
const ext = new JavaScriptExtractor('src/spread.js', src)
|
|
911
|
+
const fns = ext.extractFunctions()
|
|
912
|
+
expect(fns.length).toBe(1)
|
|
913
|
+
})
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
describe('Template Literals', () => {
|
|
917
|
+
test('handles template literals', () => {
|
|
918
|
+
const src = `
|
|
919
|
+
function greet(name) {
|
|
920
|
+
return \`Hello, \${name}!\`
|
|
921
|
+
}
|
|
922
|
+
`
|
|
923
|
+
const ext = new JavaScriptExtractor('src/tmpl.js', src)
|
|
924
|
+
const fns = ext.extractFunctions()
|
|
925
|
+
expect(fns.length).toBe(1)
|
|
926
|
+
})
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
describe('Regex Patterns', () => {
|
|
930
|
+
test('handles regex in code', () => {
|
|
931
|
+
const src = `
|
|
932
|
+
function validateEmail(email) {
|
|
933
|
+
const re = /^[a-zA-Z0-9@]+.[a-zA-Z0-9@]+$/
|
|
934
|
+
return re.test(email)
|
|
935
|
+
}
|
|
936
|
+
`
|
|
937
|
+
const ext = new JavaScriptExtractor('src/regex.js', src)
|
|
938
|
+
const fns = ext.extractFunctions()
|
|
939
|
+
expect(fns.length).toBe(1)
|
|
940
|
+
})
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
describe('Module Patterns', () => {
|
|
944
|
+
test('handles IIFE', () => {
|
|
945
|
+
const src = `
|
|
946
|
+
(function() {
|
|
947
|
+
const privateVar = 'secret'
|
|
948
|
+
window.init = function() {}
|
|
949
|
+
})()
|
|
950
|
+
|
|
951
|
+
(async () => {
|
|
952
|
+
await load()
|
|
953
|
+
})()
|
|
954
|
+
`
|
|
955
|
+
const ext = new JavaScriptExtractor('src/iife.js', src)
|
|
956
|
+
expect(() => ext.extractFunctions()).not.toThrow()
|
|
957
|
+
})
|
|
958
|
+
|
|
959
|
+
test('handles UMD pattern', () => {
|
|
960
|
+
const src = `
|
|
961
|
+
(function(root, factory) {
|
|
962
|
+
if (typeof module === 'object') {
|
|
963
|
+
module.exports = factory()
|
|
964
|
+
} else {
|
|
965
|
+
root.MyLib = factory()
|
|
966
|
+
}
|
|
967
|
+
}(this, function() {
|
|
968
|
+
return { version: '1.0' }
|
|
969
|
+
}))
|
|
970
|
+
`
|
|
971
|
+
const ext = new JavaScriptExtractor('src/umd.js', src)
|
|
972
|
+
expect(() => ext.extractFunctions()).not.toThrow()
|
|
973
|
+
})
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
describe('Complex Types', () => {
|
|
977
|
+
test('handles JSDoc comments', () => {
|
|
978
|
+
const src = `
|
|
979
|
+
/**
|
|
980
|
+
* Adds two numbers
|
|
981
|
+
* @param {number} a - First number
|
|
982
|
+
* @param {number} b - Second number
|
|
983
|
+
* @returns {number} Sum
|
|
984
|
+
*/
|
|
985
|
+
function add(a, b) {
|
|
986
|
+
return a + b
|
|
987
|
+
}
|
|
988
|
+
`
|
|
989
|
+
const ext = new TypeScriptExtractor('src/add.js', src)
|
|
990
|
+
const fns = ext.extractFunctions()
|
|
991
|
+
expect(fns[0].purpose).toBeDefined()
|
|
992
|
+
})
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
describe('Decorator-like Patterns', () => {
|
|
996
|
+
test('handles higher-order functions', () => {
|
|
997
|
+
const src = `
|
|
998
|
+
function withLogging(fn) {
|
|
999
|
+
return function(...args) {
|
|
1000
|
+
console.log('Calling', fn.name)
|
|
1001
|
+
return fn.apply(this, args)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
@withLogging
|
|
1006
|
+
function decorated() {}
|
|
1007
|
+
`
|
|
1008
|
+
const ext = new JavaScriptExtractor('src/decorator.js', src)
|
|
1009
|
+
const fns = ext.extractFunctions()
|
|
1010
|
+
expect(fns.length).toBe(2)
|
|
1011
|
+
})
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
describe('Web/Node APIs', () => {
|
|
1015
|
+
test('handles fetch API', async () => {
|
|
1016
|
+
const src = `
|
|
1017
|
+
async function fetchData(url) {
|
|
1018
|
+
const response = await fetch(url)
|
|
1019
|
+
const data = await response.json()
|
|
1020
|
+
return data
|
|
1021
|
+
}
|
|
1022
|
+
`
|
|
1023
|
+
const ext = new JavaScriptExtractor('src/fetch.js', src)
|
|
1024
|
+
const fns = ext.extractFunctions()
|
|
1025
|
+
expect(fns[0].isAsync).toBe(true)
|
|
1026
|
+
expect(fns[0].calls.length).toBe(2)
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
test('handles Express routers', () => {
|
|
1030
|
+
const src = `
|
|
1031
|
+
const express = require('express')
|
|
1032
|
+
const router = express.Router()
|
|
1033
|
+
|
|
1034
|
+
router.get('/users', getUsers)
|
|
1035
|
+
router.post('/users', createUser)
|
|
1036
|
+
router.put('/users/:id', updateUser)
|
|
1037
|
+
router.delete('/users/:id', deleteUser)
|
|
1038
|
+
`
|
|
1039
|
+
const ext = new JavaScriptExtractor('src/routes.js', src)
|
|
1040
|
+
const routes = ext.extractRoutes()
|
|
1041
|
+
expect(routes.length).toBe(4)
|
|
1042
|
+
})
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1045
|
+
describe('Chained Methods', () => {
|
|
1046
|
+
test('handles method chaining', () => {
|
|
1047
|
+
const src = `
|
|
1048
|
+
const result = items
|
|
1049
|
+
.filter(x => x.active)
|
|
1050
|
+
.map(x => x.value)
|
|
1051
|
+
.reduce((a, b) => a + b, 0)
|
|
1052
|
+
`
|
|
1053
|
+
const ext = new JavaScriptExtractor('src/chain.js', src)
|
|
1054
|
+
expect(() => ext.extractFunctions()).not.toThrow()
|
|
1055
|
+
})
|
|
1056
|
+
})
|
|
1057
|
+
|
|
1058
|
+
describe('Large Files', () => {
|
|
1059
|
+
test('handles many functions', () => {
|
|
1060
|
+
const fns = Array.from({ length: 500 }, (_, i) =>
|
|
1061
|
+
`function fn${i}() { return ${i} }`
|
|
1062
|
+
).join('\n')
|
|
1063
|
+
|
|
1064
|
+
const ext = new JavaScriptExtractor('src/many.js', fns)
|
|
1065
|
+
const result = ext.extractFunctions()
|
|
1066
|
+
expect(result.length).toBe(500)
|
|
1067
|
+
})
|
|
1068
|
+
})
|
|
1069
|
+
|
|
1070
|
+
describe('Unicode and Special Chars', () => {
|
|
1071
|
+
test('handles unicode in function names', () => {
|
|
1072
|
+
const src = `
|
|
1073
|
+
function 验证() {
|
|
1074
|
+
return true
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const 用户 = { name: 'test' }
|
|
1078
|
+
`
|
|
1079
|
+
const ext = new JavaScriptExtractor('src/unicode.js', src)
|
|
1080
|
+
const fns = ext.extractFunctions()
|
|
1081
|
+
expect(fns.length).toBe(1)
|
|
1082
|
+
})
|
|
1083
|
+
|
|
1084
|
+
test('handles emoji', () => {
|
|
1085
|
+
const src = `
|
|
1086
|
+
function 🎉() {
|
|
1087
|
+
return 'celebration'
|
|
1088
|
+
}
|
|
1089
|
+
`
|
|
1090
|
+
const ext = new JavaScriptExtractor('src/emoji.js', src)
|
|
1091
|
+
const fns = ext.extractFunctions()
|
|
1092
|
+
expect(fns.length).toBe(1)
|
|
1093
|
+
})
|
|
1094
|
+
})
|
|
1095
|
+
})
|