@@ -352,8 +352,7 @@ implement_metric_trait!(
352352 RustCode ,
353353 CppCode ,
354354 PreprocCode ,
355- CcommentCode ,
356- KotlinCode
355+ CcommentCode
357356) ;
358357
359358// Fitzpatrick, Jerry (1997). "Applying the ABC metric to C, C++ and Java". C++ Report.
@@ -546,6 +545,237 @@ impl Abc for JavaCode {
546545 }
547546}
548547
548+ // Inspects the content of Kotlin parenthesized expressions
549+ // and `Not` operators to find unary conditional expressions
550+ fn kotlin_inspect_container ( container_node : & Node , conditions : & mut f64 ) {
551+ use Kotlin :: * ;
552+
553+ let mut node = * container_node;
554+ let mut node_kind = node. kind_id ( ) . into ( ) ;
555+
556+ // Initializes the flag to true if the container is known to contain a boolean value
557+ let mut has_boolean_content = matches ! (
558+ node. parent( ) . unwrap( ) . kind_id( ) . into( ) ,
559+ BinaryExpression | IfExpression | WhileStatement | DoWhileStatement | ForStatement
560+ ) ;
561+
562+ // Looks inside parenthesized expressions and `Not` operators to find what they contain
563+ loop {
564+ // Checks if the node is a parenthesized expression or a `Not` operator
565+ // The child node of index 0 contains the unary expression operator (we look for the `!` operator)
566+ let is_parenthesised_exp = matches ! ( node_kind, ParenthesizedExpression ) ;
567+ let is_not_operator = matches ! ( node_kind, UnaryExpression )
568+ && matches ! ( node. child( 0 ) . unwrap( ) . kind_id( ) . into( ) , BANG ) ;
569+
570+ // Stops the exploration if the node is neither
571+ // a parenthesized expression nor a `Not` operator
572+ if !is_parenthesised_exp && !is_not_operator {
573+ break ;
574+ }
575+
576+ // Sets the flag to true if a `Not` operator is found
577+ // This is used to prove if a variable or a value returned by a method is actually boolean
578+ // e.g. `return (!x);`
579+ if !has_boolean_content && is_not_operator {
580+ has_boolean_content = true ;
581+ }
582+
583+ // Parenthesized expressions and `Not` operators nodes
584+ // always store their expressions in the children nodes of index one
585+ // Reference: tree-sitter-kotlin grammar for parenthesized_expression and prefix_expression
586+ node = node. child ( 1 ) . unwrap ( ) ;
587+ node_kind = node. kind_id ( ) . into ( ) ;
588+
589+ // Stops the exploration when the content is found
590+ if matches ! ( node_kind, CallExpression | Identifier | True | False ) {
591+ if has_boolean_content {
592+ * conditions += 1. ;
593+ }
594+ break ;
595+ }
596+ }
597+ }
598+
599+ // Inspects a list of elements and counts any unary conditional expression found
600+ fn kotlin_count_unary_conditions ( list_node : & Node , conditions : & mut f64 ) {
601+ use Kotlin :: * ;
602+
603+ let list_kind = list_node. kind_id ( ) . into ( ) ;
604+ let mut cursor = list_node. cursor ( ) ;
605+
606+ // Scans the immediate children nodes of the argument node
607+ if cursor. goto_first_child ( ) {
608+ loop {
609+ // Gets the current child node and its kind
610+ let node = cursor. node ( ) ;
611+ let node_kind = node. kind_id ( ) . into ( ) ;
612+
613+ // Checks if the node is a unary condition
614+ if matches ! ( node_kind, CallExpression | Identifier | True | False )
615+ && matches ! ( list_kind, BinaryExpression )
616+ {
617+ * conditions += 1. ;
618+ } else {
619+ // Checks if the node is a unary condition container
620+ kotlin_inspect_container ( & node, conditions) ;
621+ }
622+
623+ // Moves the cursor to the next sibling node of the current node
624+ // Exits the scan if there is no next sibling node
625+ if !cursor. goto_next_sibling ( ) {
626+ break ;
627+ }
628+ }
629+ }
630+ }
631+
632+ impl Abc for KotlinCode {
633+ fn compute ( node : & Node , stats : & mut Stats ) {
634+ use Kotlin :: * ;
635+
636+ match node. kind_id ( ) . into ( ) {
637+ STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | PLUSPLUS | DASHDASH => {
638+ stats. assignments += 1. ;
639+ }
640+ VariableDeclaration | PropertyDeclaration => {
641+ stats. declaration . push ( DeclKind :: Var ) ;
642+ }
643+ Final => {
644+ if let Some ( DeclKind :: Var ) = stats. declaration . last ( ) {
645+ stats. declaration . push ( DeclKind :: Const ) ;
646+ }
647+ }
648+ SEMI => {
649+ if let Some ( DeclKind :: Const | DeclKind :: Var ) = stats. declaration . last ( ) {
650+ stats. declaration . clear ( ) ;
651+ }
652+ }
653+ EQ => {
654+ // Excludes constant declarations
655+ stats
656+ . declaration
657+ . last ( )
658+ . map ( |decl| {
659+ if matches ! ( decl, DeclKind :: Var ) {
660+ stats. assignments += 1. ;
661+ }
662+ } )
663+ . unwrap_or_else ( || {
664+ stats. assignments += 1. ;
665+ } ) ;
666+ }
667+ CallExpression => {
668+ stats. branches += 1. ;
669+ }
670+ GTEQ | LTEQ | EQEQ | BANGEQ | Else | Try | Catch => {
671+ stats. conditions += 1. ;
672+ }
673+ GT | LT => {
674+ // Excludes `<` and `>` used for generic types
675+ if let Some ( parent) = node. parent ( )
676+ && !matches ! ( parent. kind_id( ) . into( ) , TypeArguments )
677+ {
678+ stats. conditions += 1. ;
679+ }
680+ }
681+ // Counts unary conditions in elements separated by `&&` or `||` boolean operators
682+ AMPAMP | PIPEPIPE => {
683+ if let Some ( parent) = node. parent ( ) {
684+ kotlin_count_unary_conditions ( & parent, & mut stats. conditions ) ;
685+ }
686+ }
687+ // Counts unary conditions inside assignments
688+ Assignment => {
689+ // The child node of index 2 contains the right operand of an assignment operation
690+ if let Some ( right_operand) = node. child ( 2 )
691+ && matches ! (
692+ right_operand. kind_id( ) . into( ) ,
693+ ParenthesizedExpression | UnaryExpression
694+ )
695+ {
696+ kotlin_inspect_container ( & right_operand, & mut stats. conditions ) ;
697+ }
698+ }
699+ // Counts unary conditions inside if and while statements
700+ IfExpression | WhileStatement => {
701+ // The child node of index 1 contains the condition
702+ if let Some ( condition) = node. child ( 1 )
703+ && matches ! ( condition. kind_id( ) . into( ) , ParenthesizedExpression )
704+ {
705+ kotlin_inspect_container ( & condition, & mut stats. conditions ) ;
706+ }
707+ }
708+ // Counts unary conditions inside do-while statements
709+ DoWhileStatement => {
710+ // The child node of index 3 contains the condition
711+ if let Some ( condition) = node. child ( 3 )
712+ && matches ! ( condition. kind_id( ) . into( ) , ParenthesizedExpression )
713+ {
714+ kotlin_inspect_container ( & condition, & mut stats. conditions ) ;
715+ }
716+ }
717+ // Counts unary conditions inside for statements
718+ ForStatement => {
719+ // The child node of index 3 contains the `condition` when
720+ // the initialization expression is a variable declaration
721+ // e.g. `for ( int i=0; `condition`; ... ) {}`
722+ if let Some ( condition) = node. child ( 3 ) {
723+ match condition. kind_id ( ) . into ( ) {
724+ SEMI => {
725+ // The child node of index 4 contains the `condition` when
726+ // the initialization expression is not a variable declaration
727+ // e.g. `for ( i=0; `condition`; ... ) {}`
728+ if let Some ( cond) = node. child ( 4 ) {
729+ match cond. kind_id ( ) . into ( ) {
730+ CallExpression | Identifier | True | False | SEMI | RPAREN => {
731+ stats. conditions += 1. ;
732+ }
733+ ParenthesizedExpression | UnaryExpression => {
734+ kotlin_inspect_container ( & cond, & mut stats. conditions ) ;
735+ }
736+ _ => { }
737+ }
738+ }
739+ }
740+ CallExpression | Identifier | True | False => {
741+ stats. conditions += 1. ;
742+ }
743+ ParenthesizedExpression | UnaryExpression => {
744+ kotlin_inspect_container ( & condition, & mut stats. conditions ) ;
745+ }
746+ _ => { }
747+ }
748+ }
749+ }
750+ // Counts unary conditions inside return statements
751+ Return => {
752+ // The child node of index 1 contains the return value
753+ if let Some ( value) = node. child ( 1 )
754+ && matches ! (
755+ value. kind_id( ) . into( ) ,
756+ ParenthesizedExpression | UnaryExpression
757+ )
758+ {
759+ kotlin_inspect_container ( & value, & mut stats. conditions )
760+ }
761+ }
762+ // Counts unary conditions inside implicit return statements in lambda expressions
763+ AnnotatedLambda => {
764+ // The child node of index 2 contains the return value
765+ if let Some ( value) = node. child ( 2 )
766+ && matches ! (
767+ value. kind_id( ) . into( ) ,
768+ ParenthesizedExpression | UnaryExpression
769+ )
770+ {
771+ kotlin_inspect_container ( & value, & mut stats. conditions )
772+ }
773+ }
774+ _ => { }
775+ }
776+ }
777+ }
778+
549779#[ cfg( test) ]
550780mod tests {
551781 use crate :: tools:: check_metrics;
0 commit comments