@@ -100,7 +100,7 @@ class MockViolation implements Violation {
100100 constructor (
101101 private rule : Rule ,
102102 private message : string ,
103- private primaryLocation : CodeLocation
103+ private primaryLocation : CodeLocation | null
104104 ) { }
105105
106106 getRule ( ) : Rule {
@@ -112,10 +112,10 @@ class MockViolation implements Violation {
112112 }
113113
114114 getCodeLocations ( ) : CodeLocation [ ] {
115- return [ this . primaryLocation ] ;
115+ return this . primaryLocation ? [ this . primaryLocation ] : [ ] ;
116116 }
117117
118- getPrimaryLocation ( ) : CodeLocation {
118+ getPrimaryLocation ( ) : CodeLocation | null {
119119 return this . primaryLocation ;
120120 }
121121
@@ -811,4 +811,119 @@ describe('applyBulkSuppressions', () => {
811811 expect ( result . suppressedCount ) . toBe ( 0 ) ;
812812 } ) ;
813813 } ) ;
814+
815+ describe ( 'Null/undefined primary location handling' , ( ) => {
816+ it ( 'should not suppress violations with null primary location' , ( ) => {
817+ const violations = [
818+ new MockViolation (
819+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
820+ 'Unused method' ,
821+ null // Null primary location
822+ ) ,
823+ new MockViolation (
824+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
825+ 'Unused method' ,
826+ new MockCodeLocation ( path . join ( workspaceRoot , 'src' , 'file1.apex' ) , 10 , 1 , 10 , 20 )
827+ )
828+ ] ;
829+
830+ const bulkConfig = {
831+ 'src/' : [
832+ {
833+ rule_selector : 'pmd' ,
834+ max_suppressed_violations : null
835+ }
836+ ]
837+ } ;
838+
839+ const quotas : BulkSuppressionQuotas = new Map ( ) ;
840+ const result = applyBulkSuppressions ( violations , bulkConfig , quotas , workspaceRoot ) ;
841+
842+ // First violation should not be suppressed (null primary location)
843+ // Second violation should be suppressed (valid location)
844+ expect ( result . unsuppressedViolations ) . toHaveLength ( 1 ) ;
845+ expect ( result . suppressedCount ) . toBe ( 1 ) ;
846+ expect ( result . unsuppressedViolations [ 0 ] . getPrimaryLocation ( ) ) . toBeNull ( ) ;
847+ } ) ;
848+
849+ it ( 'should not suppress violations with undefined file path' , ( ) => {
850+ const violations = [
851+ new MockViolation (
852+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
853+ 'Unused method' ,
854+ new MockCodeLocation ( undefined , 10 , 1 , 10 , 20 ) // No file path
855+ ) ,
856+ new MockViolation (
857+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
858+ 'Unused method' ,
859+ new MockCodeLocation ( path . join ( workspaceRoot , 'src' , 'file1.apex' ) , 10 , 1 , 10 , 20 )
860+ )
861+ ] ;
862+
863+ const bulkConfig = {
864+ 'src/' : [
865+ {
866+ rule_selector : 'pmd' ,
867+ max_suppressed_violations : null
868+ }
869+ ]
870+ } ;
871+
872+ const quotas : BulkSuppressionQuotas = new Map ( ) ;
873+ const result = applyBulkSuppressions ( violations , bulkConfig , quotas , workspaceRoot ) ;
874+
875+ // First violation should not be suppressed (no file path)
876+ // Second violation should be suppressed (valid file path)
877+ expect ( result . unsuppressedViolations ) . toHaveLength ( 1 ) ;
878+ expect ( result . suppressedCount ) . toBe ( 1 ) ;
879+ expect ( result . unsuppressedViolations [ 0 ] . getPrimaryLocation ( ) ?. getFile ( ) ) . toBeUndefined ( ) ;
880+ } ) ;
881+
882+ it ( 'should handle sorting with null primary locations' , ( ) => {
883+ const violations = [
884+ new MockViolation (
885+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
886+ 'Unused method 3' ,
887+ new MockCodeLocation ( path . join ( workspaceRoot , 'src' , 'file2.apex' ) , 5 , 1 , 5 , 20 )
888+ ) ,
889+ new MockViolation (
890+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
891+ 'Unused method 1' ,
892+ null // Null primary location
893+ ) ,
894+ new MockViolation (
895+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
896+ 'Unused method 2' ,
897+ new MockCodeLocation ( path . join ( workspaceRoot , 'src' , 'file1.apex' ) , 10 , 1 , 10 , 20 )
898+ ) ,
899+ new MockViolation (
900+ new MockRule ( 'pmd' , 'UnusedMethod' ) ,
901+ 'Unused method 4' ,
902+ new MockCodeLocation ( undefined , 10 , 1 , 10 , 20 ) // No file path
903+ )
904+ ] ;
905+
906+ const bulkConfig = {
907+ 'src/' : [
908+ {
909+ rule_selector : 'pmd' ,
910+ max_suppressed_violations : null
911+ }
912+ ]
913+ } ;
914+
915+ const quotas : BulkSuppressionQuotas = new Map ( ) ;
916+ const result = applyBulkSuppressions ( violations , bulkConfig , quotas , workspaceRoot ) ;
917+
918+ // Violations with null location or undefined file should not be suppressed
919+ // Violations with valid locations should be suppressed
920+ expect ( result . unsuppressedViolations ) . toHaveLength ( 2 ) ;
921+ expect ( result . suppressedCount ) . toBe ( 2 ) ;
922+
923+ // Verify the unsuppressed violations are the ones with null/undefined locations
924+ const unsuppressedMessages = result . unsuppressedViolations . map ( v => v . getMessage ( ) ) ;
925+ expect ( unsuppressedMessages ) . toContain ( 'Unused method 1' ) ; // Null primary location
926+ expect ( unsuppressedMessages ) . toContain ( 'Unused method 4' ) ; // Undefined file
927+ } ) ;
928+ } ) ;
814929} ) ;
0 commit comments