@@ -3080,6 +3080,276 @@ describe('Directory Exclusion', () => {
30803080 } ) ;
30813081} ) ;
30823082
3083+ // =============================================================================
3084+ // Scala
3085+ // =============================================================================
3086+
3087+ describe ( 'Scala Extraction' , ( ) => {
3088+ describe ( 'Language detection' , ( ) => {
3089+ it ( 'should detect Scala files' , ( ) => {
3090+ expect ( detectLanguage ( 'Main.scala' ) ) . toBe ( 'scala' ) ;
3091+ expect ( detectLanguage ( 'script.sc' ) ) . toBe ( 'scala' ) ;
3092+ expect ( detectLanguage ( 'src/UserService.scala' ) ) . toBe ( 'scala' ) ;
3093+ } ) ;
3094+
3095+ it ( 'should report Scala as supported' , ( ) => {
3096+ expect ( isLanguageSupported ( 'scala' ) ) . toBe ( true ) ;
3097+ expect ( getSupportedLanguages ( ) ) . toContain ( 'scala' ) ;
3098+ } ) ;
3099+ } ) ;
3100+
3101+ describe ( 'Class extraction' , ( ) => {
3102+ it ( 'should extract class definitions' , ( ) => {
3103+ const code = `
3104+ class UserService(private val repo: UserRepository) {
3105+ def findUser(id: String): Option[String] = Some(id)
3106+ }
3107+ ` ;
3108+ const result = extractFromSource ( 'UserService.scala' , code ) ;
3109+ const cls = result . nodes . find ( ( n ) => n . kind === 'class' && n . name === 'UserService' ) ;
3110+ expect ( cls ) . toBeDefined ( ) ;
3111+ expect ( cls ?. language ) . toBe ( 'scala' ) ;
3112+ } ) ;
3113+
3114+ it ( 'should extract object definitions as class kind' , ( ) => {
3115+ const code = `
3116+ object DatabaseConfig {
3117+ val url = "jdbc:postgresql://localhost/mydb"
3118+ }
3119+ ` ;
3120+ const result = extractFromSource ( 'Config.scala' , code ) ;
3121+ const obj = result . nodes . find ( ( n ) => n . kind === 'class' && n . name === 'DatabaseConfig' ) ;
3122+ expect ( obj ) . toBeDefined ( ) ;
3123+ } ) ;
3124+
3125+ it ( 'should extract trait definitions as trait kind' , ( ) => {
3126+ const code = `
3127+ trait Repository[A] {
3128+ def findById(id: String): Option[A]
3129+ def save(entity: A): Unit
3130+ }
3131+ ` ;
3132+ const result = extractFromSource ( 'Repository.scala' , code ) ;
3133+ const trait_ = result . nodes . find ( ( n ) => n . kind === 'trait' && n . name === 'Repository' ) ;
3134+ expect ( trait_ ) . toBeDefined ( ) ;
3135+ } ) ;
3136+ } ) ;
3137+
3138+ describe ( 'Method and function extraction' , ( ) => {
3139+ it ( 'should extract method definitions inside a class' , ( ) => {
3140+ const code = `
3141+ class Calculator {
3142+ def add(a: Int, b: Int): Int = a + b
3143+ def divide(a: Double, b: Double): Double = a / b
3144+ }
3145+ ` ;
3146+ const result = extractFromSource ( 'Calculator.scala' , code ) ;
3147+ const methods = result . nodes . filter ( ( n ) => n . kind === 'method' ) ;
3148+ expect ( methods . find ( ( m ) => m . name === 'add' ) ) . toBeDefined ( ) ;
3149+ expect ( methods . find ( ( m ) => m . name === 'divide' ) ) . toBeDefined ( ) ;
3150+ } ) ;
3151+
3152+ it ( 'should extract method signatures' , ( ) => {
3153+ const code = `
3154+ class Greeter {
3155+ def greet(name: String): String = s"Hello, \${name}!"
3156+ }
3157+ ` ;
3158+ const result = extractFromSource ( 'Greeter.scala' , code ) ;
3159+ const method = result . nodes . find ( ( n ) => n . name === 'greet' ) ;
3160+ expect ( method ?. signature ) . toContain ( 'name: String' ) ;
3161+ expect ( method ?. signature ) . toContain ( 'String' ) ;
3162+ } ) ;
3163+
3164+ it ( 'should extract top-level function definitions as functions' , ( ) => {
3165+ const code = `
3166+ def factorial(n: Int): Int = if (n <= 1) 1 else n * factorial(n - 1)
3167+ def greet(name: String): String = s"Hello, \${name}!"
3168+ ` ;
3169+ const result = extractFromSource ( 'utils.scala' , code ) ;
3170+ const fns = result . nodes . filter ( ( n ) => n . kind === 'function' ) ;
3171+ expect ( fns . find ( ( f ) => f . name === 'factorial' ) ) . toBeDefined ( ) ;
3172+ expect ( fns . find ( ( f ) => f . name === 'greet' ) ) . toBeDefined ( ) ;
3173+ } ) ;
3174+ } ) ;
3175+
3176+ describe ( 'Val and var extraction' , ( ) => {
3177+ it ( 'should extract val inside a class as field' , ( ) => {
3178+ const code = `
3179+ class Config {
3180+ val timeout: Int = 30
3181+ val host: String = "localhost"
3182+ }
3183+ ` ;
3184+ const result = extractFromSource ( 'Config.scala' , code ) ;
3185+ const fields = result . nodes . filter ( ( n ) => n . kind === 'field' ) ;
3186+ expect ( fields . find ( ( f ) => f . name === 'timeout' ) ) . toBeDefined ( ) ;
3187+ expect ( fields . find ( ( f ) => f . name === 'host' ) ) . toBeDefined ( ) ;
3188+ } ) ;
3189+
3190+ it ( 'should extract var inside a class as field' , ( ) => {
3191+ const code = `
3192+ class Counter {
3193+ var count: Int = 0
3194+ }
3195+ ` ;
3196+ const result = extractFromSource ( 'Counter.scala' , code ) ;
3197+ const field = result . nodes . find ( ( n ) => n . kind === 'field' && n . name === 'count' ) ;
3198+ expect ( field ) . toBeDefined ( ) ;
3199+ } ) ;
3200+
3201+ it ( 'should extract top-level val as constant' , ( ) => {
3202+ const code = `
3203+ val MaxConnections: Int = 100
3204+ val DefaultTimeout = 30
3205+ ` ;
3206+ const result = extractFromSource ( 'constants.scala' , code ) ;
3207+ const consts = result . nodes . filter ( ( n ) => n . kind === 'constant' ) ;
3208+ expect ( consts . find ( ( c ) => c . name === 'MaxConnections' ) ) . toBeDefined ( ) ;
3209+ } ) ;
3210+
3211+ it ( 'should extract top-level var as variable' , ( ) => {
3212+ const code = `
3213+ var retries: Int = 3
3214+ ` ;
3215+ const result = extractFromSource ( 'state.scala' , code ) ;
3216+ const v = result . nodes . find ( ( n ) => n . kind === 'variable' && n . name === 'retries' ) ;
3217+ expect ( v ) . toBeDefined ( ) ;
3218+ } ) ;
3219+
3220+ it ( 'should include type in val/var signature' , ( ) => {
3221+ const code = `
3222+ class Service {
3223+ val timeout: Int = 30
3224+ }
3225+ ` ;
3226+ const result = extractFromSource ( 'Service.scala' , code ) ;
3227+ const field = result . nodes . find ( ( n ) => n . name === 'timeout' ) ;
3228+ expect ( field ?. signature ) . toContain ( 'timeout' ) ;
3229+ expect ( field ?. signature ) . toContain ( 'Int' ) ;
3230+ } ) ;
3231+ } ) ;
3232+
3233+ describe ( 'Enum extraction' , ( ) => {
3234+ it ( 'should extract enum definitions' , ( ) => {
3235+ const code = `
3236+ enum Color:
3237+ case Red
3238+ case Green
3239+ case Blue
3240+ ` ;
3241+ const result = extractFromSource ( 'Color.scala' , code ) ;
3242+ const enumNode = result . nodes . find ( ( n ) => n . kind === 'enum' && n . name === 'Color' ) ;
3243+ expect ( enumNode ) . toBeDefined ( ) ;
3244+ } ) ;
3245+
3246+ it ( 'should extract enum cases as enum_member' , ( ) => {
3247+ const code = `
3248+ enum Direction:
3249+ case North
3250+ case South
3251+ case East
3252+ case West
3253+ ` ;
3254+ const result = extractFromSource ( 'Direction.scala' , code ) ;
3255+ const members = result . nodes . filter ( ( n ) => n . kind === 'enum_member' ) ;
3256+ expect ( members . find ( ( m ) => m . name === 'North' ) ) . toBeDefined ( ) ;
3257+ expect ( members . find ( ( m ) => m . name === 'South' ) ) . toBeDefined ( ) ;
3258+ expect ( members . length ) . toBeGreaterThanOrEqual ( 4 ) ;
3259+ } ) ;
3260+ } ) ;
3261+
3262+ describe ( 'Type alias extraction' , ( ) => {
3263+ it ( 'should extract type aliases' , ( ) => {
3264+ const code = `
3265+ type UserId = String
3266+ type UserMap = Map[String, String]
3267+ ` ;
3268+ const result = extractFromSource ( 'types.scala' , code ) ;
3269+ const aliases = result . nodes . filter ( ( n ) => n . kind === 'type_alias' ) ;
3270+ expect ( aliases . find ( ( a ) => a . name === 'UserId' ) ) . toBeDefined ( ) ;
3271+ expect ( aliases . find ( ( a ) => a . name === 'UserMap' ) ) . toBeDefined ( ) ;
3272+ } ) ;
3273+ } ) ;
3274+
3275+ describe ( 'Import extraction' , ( ) => {
3276+ it ( 'should extract import declarations' , ( ) => {
3277+ const code = `
3278+ import scala.collection.mutable.ListBuffer
3279+ import scala.concurrent.Future
3280+ ` ;
3281+ const result = extractFromSource ( 'imports.scala' , code ) ;
3282+ const imports = result . nodes . filter ( ( n ) => n . kind === 'import' ) ;
3283+ expect ( imports . length ) . toBeGreaterThanOrEqual ( 2 ) ;
3284+ } ) ;
3285+ } ) ;
3286+
3287+ describe ( 'Visibility modifiers' , ( ) => {
3288+ it ( 'should extract private visibility' , ( ) => {
3289+ const code = `
3290+ class Service {
3291+ private val secret: String = "abc"
3292+ private def helper(): Unit = {}
3293+ }
3294+ ` ;
3295+ const result = extractFromSource ( 'Service.scala' , code ) ;
3296+ const secretField = result . nodes . find ( ( n ) => n . name === 'secret' ) ;
3297+ expect ( secretField ?. visibility ) . toBe ( 'private' ) ;
3298+ const helperMethod = result . nodes . find ( ( n ) => n . name === 'helper' ) ;
3299+ expect ( helperMethod ?. visibility ) . toBe ( 'private' ) ;
3300+ } ) ;
3301+
3302+ it ( 'should extract protected visibility' , ( ) => {
3303+ const code = `
3304+ class Base {
3305+ protected def helperMethod(): Unit = {}
3306+ }
3307+ ` ;
3308+ const result = extractFromSource ( 'Base.scala' , code ) ;
3309+ const method = result . nodes . find ( ( n ) => n . name === 'helperMethod' ) ;
3310+ expect ( method ?. visibility ) . toBe ( 'protected' ) ;
3311+ } ) ;
3312+
3313+ it ( 'should default to public visibility' , ( ) => {
3314+ const code = `
3315+ class Greeter {
3316+ def hello(): Unit = {}
3317+ }
3318+ ` ;
3319+ const result = extractFromSource ( 'Greeter.scala' , code ) ;
3320+ const method = result . nodes . find ( ( n ) => n . name === 'hello' ) ;
3321+ expect ( method ?. visibility ) . toBe ( 'public' ) ;
3322+ } ) ;
3323+ } ) ;
3324+
3325+ describe ( 'Inheritance' , ( ) => {
3326+ it ( 'should extract extends relationships' , ( ) => {
3327+ const code = `
3328+ class AdminUser extends User {
3329+ def adminAction(): Unit = {}
3330+ }
3331+ ` ;
3332+ const result = extractFromSource ( 'AdminUser.scala' , code ) ;
3333+ const extendsRefs = result . unresolvedReferences . filter ( ( r ) => r . referenceKind === 'extends' ) ;
3334+ expect ( extendsRefs . find ( ( r ) => r . referenceName === 'User' ) ) . toBeDefined ( ) ;
3335+ } ) ;
3336+ } ) ;
3337+
3338+ describe ( 'Call extraction' , ( ) => {
3339+ it ( 'should extract function call expressions' , ( ) => {
3340+ const code = `
3341+ def processData(): Unit = {
3342+ val result = computeResult()
3343+ println(result)
3344+ }
3345+ ` ;
3346+ const result = extractFromSource ( 'processor.scala' , code ) ;
3347+ const calls = result . unresolvedReferences . filter ( ( r ) => r . referenceKind === 'calls' ) ;
3348+ expect ( calls . length ) . toBeGreaterThan ( 0 ) ;
3349+ } ) ;
3350+ } ) ;
3351+ } ) ;
3352+
30833353describe ( 'Vue Extraction' , ( ) => {
30843354 it ( 'should detect Vue files' , ( ) => {
30853355 expect ( detectLanguage ( 'App.vue' ) ) . toBe ( 'vue' ) ;
0 commit comments