@@ -65,14 +65,31 @@ function findJavacgJar() {
6565/**
6666 * Scan .java source files to build SimpleClassName → filename.java map.
6767 * Used to resolve file fields in the edge key format "name@file".
68+ *
69+ * Maps the first top-level type per file — inner classes are not indexed.
70+ * Handles common modifiers (public, abstract, final, sealed, non-sealed, strictfp)
71+ * and type keywords (class, interface, enum, record).
6872 */
6973function buildClassFileMap ( fixtureDir ) {
7074 const map = new Map ( ) ;
71- for ( const filename of fs . readdirSync ( fixtureDir ) ) {
72- if ( ! filename . endsWith ( '.java' ) ) continue ;
75+ const javaFiles = fs . readdirSync ( fixtureDir ) . filter ( ( f ) => f . endsWith ( '.java' ) ) ;
76+ for ( const filename of javaFiles ) {
7377 const src = fs . readFileSync ( path . join ( fixtureDir , filename ) , 'utf8' ) ;
74- const m = src . match ( / (?: p u b l i c \s + ) ? (?: a b s t r a c t \s + ) ? (?: c l a s s | i n t e r f a c e ) \s + ( \w + ) / ) ;
75- if ( m ) map . set ( m [ 1 ] , filename ) ;
78+ // Match any combination of access/modifier keywords before the type keyword
79+ const m = src . match (
80+ / (?: (?: p u b l i c | p r o t e c t e d | p r i v a t e | a b s t r a c t | f i n a l | s e a l e d | n o n - s e a l e d | s t r i c t f p ) \s + ) * (?: c l a s s | i n t e r f a c e | e n u m | r e c o r d ) \s + ( \w + ) / ,
81+ ) ;
82+ if ( m ) {
83+ map . set ( m [ 1 ] , filename ) ;
84+ } else {
85+ console . warn ( `[warn] buildClassFileMap: no type declaration found in ${ filename } — edges involving this file will be filtered out` ) ;
86+ }
87+ }
88+ // Validate: every .java file should map to exactly one class name
89+ if ( map . size !== javaFiles . length ) {
90+ console . warn (
91+ `[warn] buildClassFileMap: ${ javaFiles . length } .java files but only ${ map . size } class names resolved — precision/recall may be skewed` ,
92+ ) ;
7693 }
7794 return map ;
7895}
@@ -153,7 +170,8 @@ function runJavacg(javacgJar, fixtureDir) {
153170 */
154171function parseJavacgOutput ( output , classFileMap ) {
155172 // M: caller (T) callee — the space between caller and (T) may vary
156- const lineRe = / ^ M : ( \S + ) \s + \( ( [ C S O I D E ] ) \) ( \S + ) $ / ;
173+ // T values: C=virtual, S=static, O=special (constructors/super), I=interface, D=dynamic (invokedynamic)
174+ const lineRe = / ^ M : ( \S + ) \s + \( ( [ C S O I D ] ) \) ( \S + ) $ / ;
157175 const edges = new Set ( ) ;
158176
159177 for ( const rawLine of output . split ( '\n' ) ) {
0 commit comments