From 7b3ad3a3da020d81f927ce850d26cbe8455148ce Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 15:32:20 -0800
Subject: [PATCH 01/53] Add c-sharp (with class query)

---
 Cargo.lock                        | 11 +++++++++++
 Cargo.toml                        |  1 +
 qlty-analysis/Cargo.toml          |  1 +
 qlty-analysis/src/lang.rs         |  4 +++-
 qlty-analysis/src/lang/c_sharp.rs | 13 +++++++++++++
 5 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 qlty-analysis/src/lang/c_sharp.rs

diff --git a/Cargo.lock b/Cargo.lock
index 805a6900f..2051c2fc1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2402,6 +2402,7 @@ dependencies = [
  "time",
  "tracing",
  "tree-sitter",
+ "tree-sitter-c-sharp",
  "tree-sitter-go",
  "tree-sitter-java",
  "tree-sitter-javascript",
@@ -3700,6 +3701,16 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "tree-sitter-c-sharp"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8997ad04502208449025114e434c9024a33a74e700513c702a9d2cac6522a771"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-go"
 version = "0.21.2"
diff --git a/Cargo.toml b/Cargo.toml
index 955b104ce..f7cf984d2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -121,6 +121,7 @@ tracing-appender = "0.2.3"
 tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
 tracing-test = "0.2.5"
 tree-sitter = "0.22.6"
+tree-sitter-c-sharp = "0.21.3"
 tree-sitter-go = "0.21.2"
 tree-sitter-java = "0.21.0"
 tree-sitter-javascript = "0.21.4"
diff --git a/qlty-analysis/Cargo.toml b/qlty-analysis/Cargo.toml
index 3ee80ee95..dd3394145 100644
--- a/qlty-analysis/Cargo.toml
+++ b/qlty-analysis/Cargo.toml
@@ -37,6 +37,7 @@ tempfile.workspace = true
 time.workspace = true
 tracing.workspace = true
 tree-sitter.workspace = true
+tree-sitter-c-sharp.workspace = true
 tree-sitter-go.workspace = true
 tree-sitter-java.workspace = true
 tree-sitter-javascript.workspace = true
diff --git a/qlty-analysis/src/lang.rs b/qlty-analysis/src/lang.rs
index 6c8286a09..9a323ce10 100644
--- a/qlty-analysis/src/lang.rs
+++ b/qlty-analysis/src/lang.rs
@@ -3,6 +3,7 @@ use core::fmt;
 use std::sync::Arc;
 use tree_sitter::{Node, Parser, Query};
 
+mod c_sharp;
 mod go;
 mod java;
 mod javascript;
@@ -16,7 +17,7 @@ mod typescript;
 mod typescript_common;
 
 pub use {
-    go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*, tsx::*,
+    c_sharp::*, go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*, tsx::*,
     typescript::*,
 };
 
@@ -30,6 +31,7 @@ use lazy_static::lazy_static;
 lazy_static! {
     pub static ref ALL_LANGS: Vec<Box<dyn Language + Sync>> = {
         vec![
+            Box::<c_sharp::CSharp>::default(),
             Box::<php::Php>::default(),
             Box::<kotlin::Kotlin>::default(),
             Box::<go::Go>::default(),
diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
new file mode 100644
index 000000000..0e4392987
--- /dev/null
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -0,0 +1,13 @@
+use crate::code::File;
+use crate::code::{child_source, node_source};
+use crate::lang::Language;
+use tree_sitter::Node;
+
+const CLASS_QUERY: &str = r#"
+[
+  (class_declaration 
+    name: (identifier) @name)
+  (interface_declaration 
+    name: (identifier) @name)
+] @definition.class
+"#;

From 06d945af73f8505f26179c496706a31005df6856 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 15:32:28 -0800
Subject: [PATCH 02/53] qlty fmt

---
 qlty-analysis/src/lang.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qlty-analysis/src/lang.rs b/qlty-analysis/src/lang.rs
index 9a323ce10..f884762ea 100644
--- a/qlty-analysis/src/lang.rs
+++ b/qlty-analysis/src/lang.rs
@@ -17,8 +17,8 @@ mod typescript;
 mod typescript_common;
 
 pub use {
-    c_sharp::*, go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*, tsx::*,
-    typescript::*,
+    c_sharp::*, go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*,
+    tsx::*, typescript::*,
 };
 
 #[allow(clippy::borrowed_box)]

From ed6f9003f1b27ca56446ebd54aa9ed6543afa440 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 15:46:55 -0800
Subject: [PATCH 03/53] Add more (copying java's implementation for now)

---
 qlty-analysis/src/lang/c_sharp.rs | 231 +++++++++++++++++++++++++++++-
 1 file changed, 230 insertions(+), 1 deletion(-)

diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
index 0e4392987..f4135769e 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -1,5 +1,5 @@
 use crate::code::File;
-use crate::code::{child_source, node_source};
+use crate::code::node_source;
 use crate::lang::Language;
 use tree_sitter::Node;
 
@@ -11,3 +11,232 @@ const CLASS_QUERY: &str = r#"
     name: (identifier) @name)
 ] @definition.class
 "#;
+
+const FUNCTION_DECLARATION_QUERY: &str = r#"
+[
+    (method_declaration
+        name: (identifier) @name
+        parameters: (_) @parameters)
+    (constructor_declaration
+        name: (identifier) @name
+        parameters: (_) @parameters)
+] @definition.function
+"#;
+
+const FIELD_QUERY: &str = r#"
+(field_declaration
+    declarator: (variable_declarator
+        name: (identifier) @name)) @field
+"#;
+
+pub struct CSharp {
+  pub class_query: tree_sitter::Query,
+  pub function_declaration_query: tree_sitter::Query,
+  pub field_query: tree_sitter::Query,
+}
+
+impl CSharp {
+  pub const SELF: &'static str = "this";
+  pub const BINARY: &'static str = "binary_expression";
+  pub const BLOCK: &'static str = "block";
+  pub const BREAK: &'static str = "break_statement";
+  pub const CATCH: &'static str = "catch_clause";
+  pub const CASE: &'static str = "switch_block_statement_group";
+  pub const LINE_COMMENT: &'static str = "line_comment";
+  pub const BLOCK_COMMENT: &'static str = "block_comment";
+  pub const CONTINUE: &'static str = "continue_statement";
+  pub const DO: &'static str = "do_statement";
+  pub const FIELD_ACCESS: &'static str = "field_access";
+  pub const FIELD_DECLARATION: &'static str = "field_declaration";
+  pub const FOR_IN: &'static str = "enhanced_for_statement";
+  pub const FOR: &'static str = "for_statement";
+  pub const METHOD_DECLARATION: &'static str = "method_declaration";
+  pub const METHOD_INVOCATION: &'static str = "method_invocation";
+  pub const IDENTIFIER: &'static str = "identifier";
+  pub const IF: &'static str = "if_statement";
+  pub const LAMBDA: &'static str = "lambda_expression";
+  pub const PROGRAM: &'static str = "program";
+  pub const RETURN: &'static str = "return_statement";
+  pub const STRING: &'static str = "string_literal";
+  pub const SWITCH: &'static str = "switch_expression";
+  pub const TEMPLATE_STRING: &'static str = "template_expression";
+  pub const TERNARY: &'static str = "ternary_expression";
+  pub const TRY: &'static str = "try_statement";
+  pub const TRY_WITH_RESOURCES: &'static str = "try_with_resources_statement";
+  pub const WHILE: &'static str = "while_statement";
+
+  pub const AND: &'static str = "&&";
+  pub const OR: &'static str = "||";
+}
+
+impl Default for CSharp {
+  fn default() -> Self {
+      let language = tree_sitter_c_sharp::language();
+
+      Self {
+          class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
+          field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
+          function_declaration_query: tree_sitter::Query::new(
+              &language,
+              FUNCTION_DECLARATION_QUERY,
+          )
+          .unwrap(),
+      }
+  }
+}
+
+impl Language for CSharp {
+  fn name(&self) -> &str {
+      "c#"
+  }
+
+  fn self_keyword(&self) -> Option<&str> {
+      Some(Self::SELF)
+  }
+
+  fn class_query(&self) -> &tree_sitter::Query {
+      &self.class_query
+  }
+
+  fn function_declaration_query(&self) -> &tree_sitter::Query {
+      &self.function_declaration_query
+  }
+
+  fn field_query(&self) -> &tree_sitter::Query {
+      &self.field_query
+  }
+
+  fn if_nodes(&self) -> Vec<&str> {
+      vec![Self::IF]
+  }
+
+  fn block_nodes(&self) -> Vec<&str> {
+      vec![Self::BLOCK]
+  }
+
+  fn conditional_assignment_nodes(&self) -> Vec<&str> {
+      vec![]
+  }
+
+  fn invisible_container_nodes(&self) -> Vec<&str> {
+      vec![Self::PROGRAM]
+  }
+
+  fn switch_nodes(&self) -> Vec<&str> {
+      vec![Self::SWITCH]
+  }
+
+  fn case_nodes(&self) -> Vec<&str> {
+      vec![Self::CASE]
+  }
+
+  fn ternary_nodes(&self) -> Vec<&str> {
+      vec![Self::TERNARY]
+  }
+
+  fn loop_nodes(&self) -> Vec<&str> {
+      vec![Self::FOR, Self::FOR_IN, Self::WHILE, Self::DO]
+  }
+
+  fn except_nodes(&self) -> Vec<&str> {
+      vec![Self::CATCH]
+  }
+
+  fn try_expression_nodes(&self) -> Vec<&str> {
+      vec![Self::TRY, Self::TRY_WITH_RESOURCES]
+  }
+
+  fn jump_nodes(&self) -> Vec<&str> {
+      vec![Self::BREAK, Self::CONTINUE]
+  }
+
+  fn return_nodes(&self) -> Vec<&str> {
+      vec![Self::RETURN]
+  }
+
+  fn binary_nodes(&self) -> Vec<&str> {
+      vec![Self::BINARY]
+  }
+
+  fn boolean_operator_nodes(&self) -> Vec<&str> {
+      vec![Self::AND, Self::OR]
+  }
+
+  fn field_nodes(&self) -> Vec<&str> {
+      vec![Self::FIELD_DECLARATION]
+  }
+
+  fn call_nodes(&self) -> Vec<&str> {
+      vec![Self::METHOD_INVOCATION]
+  }
+
+  fn function_nodes(&self) -> Vec<&str> {
+      vec![Self::METHOD_DECLARATION]
+  }
+
+  fn closure_nodes(&self) -> Vec<&str> {
+      vec![Self::LAMBDA]
+  }
+
+  fn comment_nodes(&self) -> Vec<&str> {
+      vec![Self::LINE_COMMENT, Self::BLOCK_COMMENT]
+  }
+
+  fn string_nodes(&self) -> Vec<&str> {
+      vec![Self::STRING, Self::TEMPLATE_STRING]
+  }
+
+  fn is_jump_label(&self, node: &Node) -> bool {
+      node.kind() == Self::IDENTIFIER
+  }
+
+  fn has_labeled_jumps(&self) -> bool {
+      true
+  }
+
+  fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
+      match node.kind() {
+          Self::METHOD_INVOCATION => {
+              let (receiver, object) = self.field_identifiers(source_file, node);
+
+              (Some(receiver), object)
+          }
+          _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
+      }
+  }
+
+  fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
+      let object_node = node.child_by_field_name("object");
+      let property_node = node
+          .child_by_field_name("name")
+          .or_else(|| node.child_by_field_name("field"));
+
+      match (&object_node, &property_node) {
+          (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
+              let object_source =
+                  get_node_source_or_default(obj.child_by_field_name("field"), source_file);
+              let property_source = get_node_source_or_default(Some(*prop), source_file);
+              (object_source, property_source)
+          }
+          (Some(obj), Some(prop)) => (
+              get_node_source_or_default(Some(*obj), source_file),
+              get_node_source_or_default(Some(*prop), source_file),
+          ),
+          (None, Some(prop)) => (
+              Self::SELF.to_owned(),
+              get_node_source_or_default(Some(*prop), source_file),
+          ),
+          _ => ("<UNKNOWN>".to_string(), "<UNKNOWN>".to_string()),
+      }
+  }
+
+  fn tree_sitter_language(&self) -> tree_sitter::Language {
+      tree_sitter_c_sharp::language()
+  }
+}
+
+fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String {
+  node.as_ref()
+      .map(|n| node_source(n, source_file))
+      .unwrap_or("<UNKNOWN>".to_string())
+}

From ba275b85d7fa6461567f4b610befd7575b0530c1 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 15:46:58 -0800
Subject: [PATCH 04/53] qlty fmt

---
 qlty-analysis/src/lang/c_sharp.rs | 392 +++++++++++++++---------------
 1 file changed, 196 insertions(+), 196 deletions(-)

diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
index f4135769e..1c2296c5b 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -1,5 +1,5 @@
-use crate::code::File;
 use crate::code::node_source;
+use crate::code::File;
 use crate::lang::Language;
 use tree_sitter::Node;
 
@@ -30,213 +30,213 @@ const FIELD_QUERY: &str = r#"
 "#;
 
 pub struct CSharp {
-  pub class_query: tree_sitter::Query,
-  pub function_declaration_query: tree_sitter::Query,
-  pub field_query: tree_sitter::Query,
+    pub class_query: tree_sitter::Query,
+    pub function_declaration_query: tree_sitter::Query,
+    pub field_query: tree_sitter::Query,
 }
 
 impl CSharp {
-  pub const SELF: &'static str = "this";
-  pub const BINARY: &'static str = "binary_expression";
-  pub const BLOCK: &'static str = "block";
-  pub const BREAK: &'static str = "break_statement";
-  pub const CATCH: &'static str = "catch_clause";
-  pub const CASE: &'static str = "switch_block_statement_group";
-  pub const LINE_COMMENT: &'static str = "line_comment";
-  pub const BLOCK_COMMENT: &'static str = "block_comment";
-  pub const CONTINUE: &'static str = "continue_statement";
-  pub const DO: &'static str = "do_statement";
-  pub const FIELD_ACCESS: &'static str = "field_access";
-  pub const FIELD_DECLARATION: &'static str = "field_declaration";
-  pub const FOR_IN: &'static str = "enhanced_for_statement";
-  pub const FOR: &'static str = "for_statement";
-  pub const METHOD_DECLARATION: &'static str = "method_declaration";
-  pub const METHOD_INVOCATION: &'static str = "method_invocation";
-  pub const IDENTIFIER: &'static str = "identifier";
-  pub const IF: &'static str = "if_statement";
-  pub const LAMBDA: &'static str = "lambda_expression";
-  pub const PROGRAM: &'static str = "program";
-  pub const RETURN: &'static str = "return_statement";
-  pub const STRING: &'static str = "string_literal";
-  pub const SWITCH: &'static str = "switch_expression";
-  pub const TEMPLATE_STRING: &'static str = "template_expression";
-  pub const TERNARY: &'static str = "ternary_expression";
-  pub const TRY: &'static str = "try_statement";
-  pub const TRY_WITH_RESOURCES: &'static str = "try_with_resources_statement";
-  pub const WHILE: &'static str = "while_statement";
-
-  pub const AND: &'static str = "&&";
-  pub const OR: &'static str = "||";
+    pub const SELF: &'static str = "this";
+    pub const BINARY: &'static str = "binary_expression";
+    pub const BLOCK: &'static str = "block";
+    pub const BREAK: &'static str = "break_statement";
+    pub const CATCH: &'static str = "catch_clause";
+    pub const CASE: &'static str = "switch_block_statement_group";
+    pub const LINE_COMMENT: &'static str = "line_comment";
+    pub const BLOCK_COMMENT: &'static str = "block_comment";
+    pub const CONTINUE: &'static str = "continue_statement";
+    pub const DO: &'static str = "do_statement";
+    pub const FIELD_ACCESS: &'static str = "field_access";
+    pub const FIELD_DECLARATION: &'static str = "field_declaration";
+    pub const FOR_IN: &'static str = "enhanced_for_statement";
+    pub const FOR: &'static str = "for_statement";
+    pub const METHOD_DECLARATION: &'static str = "method_declaration";
+    pub const METHOD_INVOCATION: &'static str = "method_invocation";
+    pub const IDENTIFIER: &'static str = "identifier";
+    pub const IF: &'static str = "if_statement";
+    pub const LAMBDA: &'static str = "lambda_expression";
+    pub const PROGRAM: &'static str = "program";
+    pub const RETURN: &'static str = "return_statement";
+    pub const STRING: &'static str = "string_literal";
+    pub const SWITCH: &'static str = "switch_expression";
+    pub const TEMPLATE_STRING: &'static str = "template_expression";
+    pub const TERNARY: &'static str = "ternary_expression";
+    pub const TRY: &'static str = "try_statement";
+    pub const TRY_WITH_RESOURCES: &'static str = "try_with_resources_statement";
+    pub const WHILE: &'static str = "while_statement";
+
+    pub const AND: &'static str = "&&";
+    pub const OR: &'static str = "||";
 }
 
 impl Default for CSharp {
-  fn default() -> Self {
-      let language = tree_sitter_c_sharp::language();
-
-      Self {
-          class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
-          field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
-          function_declaration_query: tree_sitter::Query::new(
-              &language,
-              FUNCTION_DECLARATION_QUERY,
-          )
-          .unwrap(),
-      }
-  }
+    fn default() -> Self {
+        let language = tree_sitter_c_sharp::language();
+
+        Self {
+            class_query: tree_sitter::Query::new(&language, CLASS_QUERY).unwrap(),
+            field_query: tree_sitter::Query::new(&language, FIELD_QUERY).unwrap(),
+            function_declaration_query: tree_sitter::Query::new(
+                &language,
+                FUNCTION_DECLARATION_QUERY,
+            )
+            .unwrap(),
+        }
+    }
 }
 
 impl Language for CSharp {
-  fn name(&self) -> &str {
-      "c#"
-  }
+    fn name(&self) -> &str {
+        "c#"
+    }
 
-  fn self_keyword(&self) -> Option<&str> {
-      Some(Self::SELF)
-  }
+    fn self_keyword(&self) -> Option<&str> {
+        Some(Self::SELF)
+    }
 
-  fn class_query(&self) -> &tree_sitter::Query {
-      &self.class_query
-  }
-
-  fn function_declaration_query(&self) -> &tree_sitter::Query {
-      &self.function_declaration_query
-  }
-
-  fn field_query(&self) -> &tree_sitter::Query {
-      &self.field_query
-  }
-
-  fn if_nodes(&self) -> Vec<&str> {
-      vec![Self::IF]
-  }
-
-  fn block_nodes(&self) -> Vec<&str> {
-      vec![Self::BLOCK]
-  }
-
-  fn conditional_assignment_nodes(&self) -> Vec<&str> {
-      vec![]
-  }
-
-  fn invisible_container_nodes(&self) -> Vec<&str> {
-      vec![Self::PROGRAM]
-  }
-
-  fn switch_nodes(&self) -> Vec<&str> {
-      vec![Self::SWITCH]
-  }
-
-  fn case_nodes(&self) -> Vec<&str> {
-      vec![Self::CASE]
-  }
-
-  fn ternary_nodes(&self) -> Vec<&str> {
-      vec![Self::TERNARY]
-  }
-
-  fn loop_nodes(&self) -> Vec<&str> {
-      vec![Self::FOR, Self::FOR_IN, Self::WHILE, Self::DO]
-  }
-
-  fn except_nodes(&self) -> Vec<&str> {
-      vec![Self::CATCH]
-  }
-
-  fn try_expression_nodes(&self) -> Vec<&str> {
-      vec![Self::TRY, Self::TRY_WITH_RESOURCES]
-  }
-
-  fn jump_nodes(&self) -> Vec<&str> {
-      vec![Self::BREAK, Self::CONTINUE]
-  }
-
-  fn return_nodes(&self) -> Vec<&str> {
-      vec![Self::RETURN]
-  }
-
-  fn binary_nodes(&self) -> Vec<&str> {
-      vec![Self::BINARY]
-  }
-
-  fn boolean_operator_nodes(&self) -> Vec<&str> {
-      vec![Self::AND, Self::OR]
-  }
-
-  fn field_nodes(&self) -> Vec<&str> {
-      vec![Self::FIELD_DECLARATION]
-  }
-
-  fn call_nodes(&self) -> Vec<&str> {
-      vec![Self::METHOD_INVOCATION]
-  }
-
-  fn function_nodes(&self) -> Vec<&str> {
-      vec![Self::METHOD_DECLARATION]
-  }
-
-  fn closure_nodes(&self) -> Vec<&str> {
-      vec![Self::LAMBDA]
-  }
-
-  fn comment_nodes(&self) -> Vec<&str> {
-      vec![Self::LINE_COMMENT, Self::BLOCK_COMMENT]
-  }
-
-  fn string_nodes(&self) -> Vec<&str> {
-      vec![Self::STRING, Self::TEMPLATE_STRING]
-  }
-
-  fn is_jump_label(&self, node: &Node) -> bool {
-      node.kind() == Self::IDENTIFIER
-  }
-
-  fn has_labeled_jumps(&self) -> bool {
-      true
-  }
-
-  fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
-      match node.kind() {
-          Self::METHOD_INVOCATION => {
-              let (receiver, object) = self.field_identifiers(source_file, node);
-
-              (Some(receiver), object)
-          }
-          _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
-      }
-  }
-
-  fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
-      let object_node = node.child_by_field_name("object");
-      let property_node = node
-          .child_by_field_name("name")
-          .or_else(|| node.child_by_field_name("field"));
-
-      match (&object_node, &property_node) {
-          (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
-              let object_source =
-                  get_node_source_or_default(obj.child_by_field_name("field"), source_file);
-              let property_source = get_node_source_or_default(Some(*prop), source_file);
-              (object_source, property_source)
-          }
-          (Some(obj), Some(prop)) => (
-              get_node_source_or_default(Some(*obj), source_file),
-              get_node_source_or_default(Some(*prop), source_file),
-          ),
-          (None, Some(prop)) => (
-              Self::SELF.to_owned(),
-              get_node_source_or_default(Some(*prop), source_file),
-          ),
-          _ => ("<UNKNOWN>".to_string(), "<UNKNOWN>".to_string()),
-      }
-  }
-
-  fn tree_sitter_language(&self) -> tree_sitter::Language {
-      tree_sitter_c_sharp::language()
-  }
+    fn class_query(&self) -> &tree_sitter::Query {
+        &self.class_query
+    }
+
+    fn function_declaration_query(&self) -> &tree_sitter::Query {
+        &self.function_declaration_query
+    }
+
+    fn field_query(&self) -> &tree_sitter::Query {
+        &self.field_query
+    }
+
+    fn if_nodes(&self) -> Vec<&str> {
+        vec![Self::IF]
+    }
+
+    fn block_nodes(&self) -> Vec<&str> {
+        vec![Self::BLOCK]
+    }
+
+    fn conditional_assignment_nodes(&self) -> Vec<&str> {
+        vec![]
+    }
+
+    fn invisible_container_nodes(&self) -> Vec<&str> {
+        vec![Self::PROGRAM]
+    }
+
+    fn switch_nodes(&self) -> Vec<&str> {
+        vec![Self::SWITCH]
+    }
+
+    fn case_nodes(&self) -> Vec<&str> {
+        vec![Self::CASE]
+    }
+
+    fn ternary_nodes(&self) -> Vec<&str> {
+        vec![Self::TERNARY]
+    }
+
+    fn loop_nodes(&self) -> Vec<&str> {
+        vec![Self::FOR, Self::FOR_IN, Self::WHILE, Self::DO]
+    }
+
+    fn except_nodes(&self) -> Vec<&str> {
+        vec![Self::CATCH]
+    }
+
+    fn try_expression_nodes(&self) -> Vec<&str> {
+        vec![Self::TRY, Self::TRY_WITH_RESOURCES]
+    }
+
+    fn jump_nodes(&self) -> Vec<&str> {
+        vec![Self::BREAK, Self::CONTINUE]
+    }
+
+    fn return_nodes(&self) -> Vec<&str> {
+        vec![Self::RETURN]
+    }
+
+    fn binary_nodes(&self) -> Vec<&str> {
+        vec![Self::BINARY]
+    }
+
+    fn boolean_operator_nodes(&self) -> Vec<&str> {
+        vec![Self::AND, Self::OR]
+    }
+
+    fn field_nodes(&self) -> Vec<&str> {
+        vec![Self::FIELD_DECLARATION]
+    }
+
+    fn call_nodes(&self) -> Vec<&str> {
+        vec![Self::METHOD_INVOCATION]
+    }
+
+    fn function_nodes(&self) -> Vec<&str> {
+        vec![Self::METHOD_DECLARATION]
+    }
+
+    fn closure_nodes(&self) -> Vec<&str> {
+        vec![Self::LAMBDA]
+    }
+
+    fn comment_nodes(&self) -> Vec<&str> {
+        vec![Self::LINE_COMMENT, Self::BLOCK_COMMENT]
+    }
+
+    fn string_nodes(&self) -> Vec<&str> {
+        vec![Self::STRING, Self::TEMPLATE_STRING]
+    }
+
+    fn is_jump_label(&self, node: &Node) -> bool {
+        node.kind() == Self::IDENTIFIER
+    }
+
+    fn has_labeled_jumps(&self) -> bool {
+        true
+    }
+
+    fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
+        match node.kind() {
+            Self::METHOD_INVOCATION => {
+                let (receiver, object) = self.field_identifiers(source_file, node);
+
+                (Some(receiver), object)
+            }
+            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
+        }
+    }
+
+    fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
+        let object_node = node.child_by_field_name("object");
+        let property_node = node
+            .child_by_field_name("name")
+            .or_else(|| node.child_by_field_name("field"));
+
+        match (&object_node, &property_node) {
+            (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
+                let object_source =
+                    get_node_source_or_default(obj.child_by_field_name("field"), source_file);
+                let property_source = get_node_source_or_default(Some(*prop), source_file);
+                (object_source, property_source)
+            }
+            (Some(obj), Some(prop)) => (
+                get_node_source_or_default(Some(*obj), source_file),
+                get_node_source_or_default(Some(*prop), source_file),
+            ),
+            (None, Some(prop)) => (
+                Self::SELF.to_owned(),
+                get_node_source_or_default(Some(*prop), source_file),
+            ),
+            _ => ("<UNKNOWN>".to_string(), "<UNKNOWN>".to_string()),
+        }
+    }
+
+    fn tree_sitter_language(&self) -> tree_sitter::Language {
+        tree_sitter_c_sharp::language()
+    }
 }
 
 fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String {
-  node.as_ref()
-      .map(|n| node_source(n, source_file))
-      .unwrap_or("<UNKNOWN>".to_string())
+    node.as_ref()
+        .map(|n| node_source(n, source_file))
+        .unwrap_or("<UNKNOWN>".to_string())
 }

From c5486efb6ce79d103761e083fe6cc1987ba817bf Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 16:19:27 -0800
Subject: [PATCH 05/53] Update field query

---
 qlty-analysis/src/lang/c_sharp.rs | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
index 1c2296c5b..c20b09ad2 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -20,13 +20,18 @@ const FUNCTION_DECLARATION_QUERY: &str = r#"
     (constructor_declaration
         name: (identifier) @name
         parameters: (_) @parameters)
+    (local_function_statement
+        name: (identifier) @name
+        parameters: (_) @parameters)
 ] @definition.function
 "#;
 
 const FIELD_QUERY: &str = r#"
-(field_declaration
-    declarator: (variable_declarator
-        name: (identifier) @name)) @field
+  (field_declaration 
+    (variable_declaration 
+      (variable_declarator name: (identifier) @name)
+    )
+  ) @field
 "#;
 
 pub struct CSharp {

From 942f9237f5958a88aadc5a070b5e9f7111d0af7e Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 16:39:53 -0800
Subject: [PATCH 06/53] Add c sharp file extension

---
 qlty-config/default.toml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/qlty-config/default.toml b/qlty-config/default.toml
index f11e96c68..ba680e496 100644
--- a/qlty-config/default.toml
+++ b/qlty-config/default.toml
@@ -31,6 +31,9 @@ exclude_patterns = [
 [file_types.ALL]
 globs = ["*.*"]
 
+[file_types.c_sharp]
+globs = ["*.cs"]
+
 [file_types.css]
 globs = ["*.css"]
 

From 887c0621dbe83268c058183cb67ce6130334d35c Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 17:28:44 -0800
Subject: [PATCH 07/53] More C-Sharp updates

---
 qlty-analysis/src/lang/c_sharp.rs | 27 +++++++++++----------------
 1 file changed, 11 insertions(+), 16 deletions(-)

diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
index c20b09ad2..c5f12c19b 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -41,33 +41,28 @@ pub struct CSharp {
 }
 
 impl CSharp {
-    pub const SELF: &'static str = "this";
     pub const BINARY: &'static str = "binary_expression";
     pub const BLOCK: &'static str = "block";
     pub const BREAK: &'static str = "break_statement";
     pub const CATCH: &'static str = "catch_clause";
-    pub const CASE: &'static str = "switch_block_statement_group";
-    pub const LINE_COMMENT: &'static str = "line_comment";
-    pub const BLOCK_COMMENT: &'static str = "block_comment";
+    pub const CASE: &'static str = "switch_section";
+    pub const COMMENT: &'static str = "comment";
     pub const CONTINUE: &'static str = "continue_statement";
     pub const DO: &'static str = "do_statement";
-    pub const FIELD_ACCESS: &'static str = "field_access";
     pub const FIELD_DECLARATION: &'static str = "field_declaration";
-    pub const FOR_IN: &'static str = "enhanced_for_statement";
+    pub const FIELD_ACCESS: &'static str = "member_access_expression";
     pub const FOR: &'static str = "for_statement";
     pub const METHOD_DECLARATION: &'static str = "method_declaration";
-    pub const METHOD_INVOCATION: &'static str = "method_invocation";
+    pub const METHOD_INVOCATION: &'static str = "invocation_expression";
     pub const IDENTIFIER: &'static str = "identifier";
     pub const IF: &'static str = "if_statement";
     pub const LAMBDA: &'static str = "lambda_expression";
-    pub const PROGRAM: &'static str = "program";
     pub const RETURN: &'static str = "return_statement";
+    pub const SELF: &'static str = "this";
     pub const STRING: &'static str = "string_literal";
     pub const SWITCH: &'static str = "switch_expression";
-    pub const TEMPLATE_STRING: &'static str = "template_expression";
-    pub const TERNARY: &'static str = "ternary_expression";
+    pub const TERNARY: &'static str = "conditional_expression";
     pub const TRY: &'static str = "try_statement";
-    pub const TRY_WITH_RESOURCES: &'static str = "try_with_resources_statement";
     pub const WHILE: &'static str = "while_statement";
 
     pub const AND: &'static str = "&&";
@@ -124,7 +119,7 @@ impl Language for CSharp {
     }
 
     fn invisible_container_nodes(&self) -> Vec<&str> {
-        vec![Self::PROGRAM]
+        vec![]
     }
 
     fn switch_nodes(&self) -> Vec<&str> {
@@ -140,7 +135,7 @@ impl Language for CSharp {
     }
 
     fn loop_nodes(&self) -> Vec<&str> {
-        vec![Self::FOR, Self::FOR_IN, Self::WHILE, Self::DO]
+        vec![Self::FOR, Self::WHILE, Self::DO]
     }
 
     fn except_nodes(&self) -> Vec<&str> {
@@ -148,7 +143,7 @@ impl Language for CSharp {
     }
 
     fn try_expression_nodes(&self) -> Vec<&str> {
-        vec![Self::TRY, Self::TRY_WITH_RESOURCES]
+        vec![Self::TRY]
     }
 
     fn jump_nodes(&self) -> Vec<&str> {
@@ -184,11 +179,11 @@ impl Language for CSharp {
     }
 
     fn comment_nodes(&self) -> Vec<&str> {
-        vec![Self::LINE_COMMENT, Self::BLOCK_COMMENT]
+        vec![Self::COMMENT]
     }
 
     fn string_nodes(&self) -> Vec<&str> {
-        vec![Self::STRING, Self::TEMPLATE_STRING]
+        vec![Self::STRING]
     }
 
     fn is_jump_label(&self, node: &Node) -> bool {

From 323cd9265b170634ac3fbac1a6b696a67de478a7 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Thu, 2 Jan 2025 23:37:18 -0800
Subject: [PATCH 08/53] Change language name

---
 qlty-analysis/src/lang/c_sharp.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/c_sharp.rs
index c5f12c19b..de12f3a14 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/c_sharp.rs
@@ -47,6 +47,7 @@ impl CSharp {
     pub const CATCH: &'static str = "catch_clause";
     pub const CASE: &'static str = "switch_section";
     pub const COMMENT: &'static str = "comment";
+    pub const COMPILATION_UNIT: &'static str = "complication_unit";
     pub const CONTINUE: &'static str = "continue_statement";
     pub const DO: &'static str = "do_statement";
     pub const FIELD_DECLARATION: &'static str = "field_declaration";
@@ -87,7 +88,7 @@ impl Default for CSharp {
 
 impl Language for CSharp {
     fn name(&self) -> &str {
-        "c#"
+        "c_sharp"
     }
 
     fn self_keyword(&self) -> Option<&str> {
@@ -119,7 +120,7 @@ impl Language for CSharp {
     }
 
     fn invisible_container_nodes(&self) -> Vec<&str> {
-        vec![]
+        vec![Self::COMPILATION_UNIT]
     }
 
     fn switch_nodes(&self) -> Vec<&str> {

From 500bcfe65e2ab0adcba79b1afbae981598e170df Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Fri, 3 Jan 2025 14:26:33 -0800
Subject: [PATCH 09/53] Renames / integration tests

---
 qlty-analysis/src/lang.rs                     |  6 +-
 .../src/lang/{c_sharp.rs => csharp.rs}        |  2 +-
 qlty-cli/tests/lang.rs                        |  5 ++
 .../tests/lang/csharp/basic.in/.gitignore     |  4 ++
 .../lang/csharp/basic.in/.qlty/qlty.toml      |  1 +
 .../lang/csharp/basic.in/BooleanLogic.cs      | 28 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 69 +++++++++++++++++++
 qlty-cli/tests/lang/csharp/basic.toml         |  3 +
 qlty-config/default.toml                      |  5 +-
 qlty-types/src/lib.rs                         |  1 +
 qlty-types/src/protos/qlty.analysis.v1.rs     |  3 +
 .../src/protos/qlty.analysis.v1.serde.rs      |  3 +
 12 files changed, 125 insertions(+), 5 deletions(-)
 rename qlty-analysis/src/lang/{c_sharp.rs => csharp.rs} (99%)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/.gitignore
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/.qlty/qlty.toml
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/BooleanLogic.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.stdout
 create mode 100644 qlty-cli/tests/lang/csharp/basic.toml

diff --git a/qlty-analysis/src/lang.rs b/qlty-analysis/src/lang.rs
index f884762ea..1c4278ca9 100644
--- a/qlty-analysis/src/lang.rs
+++ b/qlty-analysis/src/lang.rs
@@ -3,7 +3,7 @@ use core::fmt;
 use std::sync::Arc;
 use tree_sitter::{Node, Parser, Query};
 
-mod c_sharp;
+mod csharp;
 mod go;
 mod java;
 mod javascript;
@@ -17,7 +17,7 @@ mod typescript;
 mod typescript_common;
 
 pub use {
-    c_sharp::*, go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*,
+    csharp::*, go::*, java::*, javascript::*, kotlin::*, php::*, python::*, ruby::*, rust::*,
     tsx::*, typescript::*,
 };
 
@@ -31,7 +31,7 @@ use lazy_static::lazy_static;
 lazy_static! {
     pub static ref ALL_LANGS: Vec<Box<dyn Language + Sync>> = {
         vec![
-            Box::<c_sharp::CSharp>::default(),
+            Box::<csharp::CSharp>::default(),
             Box::<php::Php>::default(),
             Box::<kotlin::Kotlin>::default(),
             Box::<go::Go>::default(),
diff --git a/qlty-analysis/src/lang/c_sharp.rs b/qlty-analysis/src/lang/csharp.rs
similarity index 99%
rename from qlty-analysis/src/lang/c_sharp.rs
rename to qlty-analysis/src/lang/csharp.rs
index de12f3a14..a258f0926 100644
--- a/qlty-analysis/src/lang/c_sharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -88,7 +88,7 @@ impl Default for CSharp {
 
 impl Language for CSharp {
     fn name(&self) -> &str {
-        "c_sharp"
+        "csharp"
     }
 
     fn self_keyword(&self) -> Option<&str> {
diff --git a/qlty-cli/tests/lang.rs b/qlty-cli/tests/lang.rs
index 76fdeab21..8b6249777 100644
--- a/qlty-cli/tests/lang.rs
+++ b/qlty-cli/tests/lang.rs
@@ -49,3 +49,8 @@ fn python_tests() {
 fn go_tests() {
     setup_and_run_test_cases("tests/lang/go/**/*.toml");
 }
+
+#[test]
+fn c_sharp_tests() {
+    setup_and_run_test_cases("tests/lang/csharp/**/*.toml");
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.in/.gitignore b/qlty-cli/tests/lang/csharp/basic.in/.gitignore
new file mode 100644
index 000000000..f5c3477bb
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/.gitignore
@@ -0,0 +1,4 @@
+.qlty/results
+.qlty/logs
+.qlty/out
+.qlty/sources
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/.qlty/qlty.toml b/qlty-cli/tests/lang/csharp/basic.in/.qlty/qlty.toml
new file mode 100644
index 000000000..4db0969f4
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/.qlty/qlty.toml
@@ -0,0 +1 @@
+config_version = "0"
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/BooleanLogic.cs b/qlty-cli/tests/lang/csharp/basic.in/BooleanLogic.cs
new file mode 100644
index 000000000..7c563a846
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/BooleanLogic.cs
@@ -0,0 +1,28 @@
+public class BooleanLogic
+{
+    private int foo;
+    private int bar;
+
+    private void F0()
+    {
+        var x = foo - bar + 1;
+    }
+}
+
+public class BooleanLogic1
+{
+    private bool foo;
+    private bool bar;
+    private bool baz;
+    private bool qux;
+    private bool zoo;
+
+    private void F1()
+    {
+        if (foo && bar && baz && qux && zoo)
+        {
+            return;
+        }
+    }
+}
+
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
new file mode 100644
index 000000000..18e89bac6
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -0,0 +1,69 @@
+{
+  "metadata": {
+    "buildId": "[..]",
+    "result": "ANALYSIS_RESULT_SUCCESS",
+    "filesAnalyzed": 1,
+    "startTime": "[..]",
+    "finishTime": "[..]",
+    "commitMessage": "initial/n",
+    "committedAt": "2024-01-01T00:00:00+00:00",
+    "committerEmail": "test@codeclimate.com",
+    "committerName": "TEST",
+    "authorEmail": "test@codeclimate.com",
+    "authorName": "TEST",
+    "authoredAt": "2024-01-01T00:00:00+00:00"
+  },
+  "messages": [],
+  "invocations": [],
+  "issues": [
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "boolean-logic",
+      "message": "Complex binary expression",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "foo && bar",
+      "snippetWithContext": "public class BooleanLogic1/n{/n    private bool foo;/n    private bool bar;/n    private bool baz;/n    private bool qux;/n    private bool zoo;/n/n    private void F1()/n    {/n        if (foo && bar && baz && qux && zoo)/n        {/n            return;/n        }/n    }/n}/n",
+      "effortMinutes": 10,
+      "value": 4,
+      "location": {
+        "path": "BooleanLogic.cs",
+        "range": {
+          "startLine": 22,
+          "startColumn": 13,
+          "endLine": 22,
+          "endColumn": 23,
+          "startByte": 319,
+          "endByte": 329
+        }
+      },
+      "mode": "MODE_BLOCK"
+    }
+  ],
+  "stats": [
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "BooleanLogic.cs",
+      "fullyQualifiedName": "BooleanLogic.cs",
+      "path": "BooleanLogic.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 2,
+      "functions": 2,
+      "fields": 5,
+      "lines": 28,
+      "codeLines": 22,
+      "commentLines": 0,
+      "blankLines": 6,
+      "complexity": 2,
+      "cyclomatic": 8,
+      "lcom4": 0
+    }
+  ]
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.toml b/qlty-cli/tests/lang/csharp/basic.toml
new file mode 100644
index 000000000..f8004557d
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.toml
@@ -0,0 +1,3 @@
+bin.name = "qlty"
+args = ["build", "--no-plugins", "--print"]
+status.code = 0
diff --git a/qlty-config/default.toml b/qlty-config/default.toml
index ba680e496..7811a2909 100644
--- a/qlty-config/default.toml
+++ b/qlty-config/default.toml
@@ -31,7 +31,7 @@ exclude_patterns = [
 [file_types.ALL]
 globs = ["*.*"]
 
-[file_types.c_sharp]
+[file_types.csharp]
 globs = ["*.cs"]
 
 [file_types.css]
@@ -435,6 +435,9 @@ smells.duplication.filter_patterns = ["(import_statement _)"]
 [language.tsx]
 globs = ["*.tsx", "*.mtsx", "*.ctsx"]
 
+[language.csharp]
+globs = ["*.cs"]
+
 [smells.boolean_logic]
 threshold = 4
 
diff --git a/qlty-types/src/lib.rs b/qlty-types/src/lib.rs
index 9c7409300..678e69f76 100644
--- a/qlty-types/src/lib.rs
+++ b/qlty-types/src/lib.rs
@@ -312,6 +312,7 @@ pub fn language_enum_from_name(name: &str) -> analysis::v1::Language {
         "rust" => analysis::v1::Language::Rust,
         "tsx" => analysis::v1::Language::Tsx,
         "typescript" => analysis::v1::Language::Typescript,
+        "csharp" => analysis::v1::Language::CSharp,
         _ => panic!("Unrecognized language name: {}", name),
     }
 }
diff --git a/qlty-types/src/protos/qlty.analysis.v1.rs b/qlty-types/src/protos/qlty.analysis.v1.rs
index ad3aaf8db..6e67b4fbc 100644
--- a/qlty-types/src/protos/qlty.analysis.v1.rs
+++ b/qlty-types/src/protos/qlty.analysis.v1.rs
@@ -677,6 +677,7 @@ pub enum Language {
     Rust = 10,
     Kotlin = 11,
     Php = 12,
+    CSharp = 13,
 }
 impl Language {
     /// String value of the enum field names used in the ProtoBuf definition.
@@ -698,6 +699,7 @@ impl Language {
             Self::Rust => "LANGUAGE_RUST",
             Self::Kotlin => "LANGUAGE_KOTLIN",
             Self::Php => "LANGUAGE_PHP",
+            Self::CSharp => "LANGUAGE_C_SHARP",
         }
     }
     /// Creates an enum from field names used in the ProtoBuf definition.
@@ -716,6 +718,7 @@ impl Language {
             "LANGUAGE_RUST" => Some(Self::Rust),
             "LANGUAGE_KOTLIN" => Some(Self::Kotlin),
             "LANGUAGE_PHP" => Some(Self::Php),
+            "LANGUAGE_C_SHARP" => Some(Self::CSharp),
             _ => None,
         }
     }
diff --git a/qlty-types/src/protos/qlty.analysis.v1.serde.rs b/qlty-types/src/protos/qlty.analysis.v1.serde.rs
index 03977a2a2..f8c1766f7 100644
--- a/qlty-types/src/protos/qlty.analysis.v1.serde.rs
+++ b/qlty-types/src/protos/qlty.analysis.v1.serde.rs
@@ -1794,6 +1794,7 @@ impl serde::Serialize for Language {
             Self::Rust => "LANGUAGE_RUST",
             Self::Kotlin => "LANGUAGE_KOTLIN",
             Self::Php => "LANGUAGE_PHP",
+            Self::CSharp => "LANGUAGE_C_SHARP",
         };
         serializer.serialize_str(variant)
     }
@@ -1818,6 +1819,7 @@ impl<'de> serde::Deserialize<'de> for Language {
             "LANGUAGE_RUST",
             "LANGUAGE_KOTLIN",
             "LANGUAGE_PHP",
+            "LANGUAGE_C_SHARP",
         ];
 
         struct GeneratedVisitor;
@@ -1871,6 +1873,7 @@ impl<'de> serde::Deserialize<'de> for Language {
                     "LANGUAGE_RUST" => Ok(Language::Rust),
                     "LANGUAGE_KOTLIN" => Ok(Language::Kotlin),
                     "LANGUAGE_PHP" => Ok(Language::Php),
+                    "LANGUAGE_C_SHARP" => Ok(Language::CSharp),
                     _ => Err(serde::de::Error::unknown_variant(value, FIELDS)),
                 }
             }

From 6d4cb13681e755e83c3e47e38f90e01e1adc8d53 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Fri, 3 Jan 2025 14:57:06 -0800
Subject: [PATCH 10/53] File and function complexity test

---
 .../lang/csharp/basic.in/FileComplexity.cs    |  41 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 115 +++++++++++++++++-
 2 files changed, 153 insertions(+), 3 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/FileComplexity.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/FileComplexity.cs b/qlty-cli/tests/lang/csharp/basic.in/FileComplexity.cs
new file mode 100644
index 000000000..91fb68e8d
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/FileComplexity.cs
@@ -0,0 +1,41 @@
+using System;
+
+public class FileComplexity
+{
+    public static void Main(string[] args)
+    {
+        int foo = 42;
+        
+        if (foo > 0)
+        {
+            if (foo > 10)
+            {
+                if (foo < 20)
+                {
+                    if (foo % 2 == 0)
+                    {
+                        if (foo % 3 == 0)
+                        {
+                            if (foo % 5 == 0)
+                            {
+                                if (foo % 7 == 0)
+                                {
+                                    if (foo % 11 == 0)
+                                    {
+                                        if (foo % 13 == 0)
+                                        {
+                                            if (foo % 17 == 0)
+                                            {
+                                                Console.WriteLine("Nested!");
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 18e89bac6..ba3d5ebaa 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 1,
+    "filesAnalyzed": 2,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -42,12 +42,101 @@
         }
       },
       "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
+      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "file-complexity",
+      "message": "High total complexity (count = 55)",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "using System;/n/npublic class FileComplexity/n{/n    public static void Main(string[] args)/n    {/n        int foo = 42;/n        /n        if (foo > 0)/n        {/n            if (foo > 10)/n            {/n                if (foo < 20)/n                {/n                    if (foo % 2 == 0)/n                    {/n                        if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }/n                    }/n                }/n            }/n        }/n    }/n}/n",
+      "snippetWithContext": "using System;/n/npublic class FileComplexity/n{/n    public static void Main(string[] args)/n    {/n        int foo = 42;/n        /n        if (foo > 0)/n        {/n            if (foo > 10)/n            {/n                if (foo < 20)/n                {/n                    if (foo % 2 == 0)/n                    {/n                        if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }/n                    }/n                }/n            }/n        }/n    }/n}",
+      "effortMinutes": 150,
+      "value": 55,
+      "valueDelta": 5,
+      "location": {
+        "path": "FileComplexity.cs",
+        "range": {
+          "startLine": 1,
+          "startColumn": 1,
+          "endLine": 42,
+          "endColumn": 1,
+          "startByte": 0,
+          "endByte": 1201
+        }
+      },
+      "partialFingerprints": {
+        "file.path": "FileComplexity.cs"
+      },
+      "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
+      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "function-complexity",
+      "message": "Function with high complexity (count = 55): Main",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "public static void Main(string[] args)/n    {/n        int foo = 42;/n        /n        if (foo > 0)/n        {/n            if (foo > 10)/n            {/n                if (foo < 20)/n                {/n                    if (foo % 2 == 0)/n                    {/n                        if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }/n                    }/n                }/n            }/n        }/n    }",
+      "snippetWithContext": "using System;/n/npublic class FileComplexity/n{/n    public static void Main(string[] args)/n    {/n        int foo = 42;/n        /n        if (foo > 0)/n        {/n            if (foo > 10)/n            {/n                if (foo < 20)/n                {/n                    if (foo % 2 == 0)/n                    {/n                        if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }/n                    }/n                }/n            }/n        }/n    }/n}",
+      "effortMinutes": 195,
+      "value": 55,
+      "valueDelta": 37,
+      "location": {
+        "path": "FileComplexity.cs",
+        "range": {
+          "startLine": 5,
+          "startColumn": 5,
+          "endLine": 40,
+          "endColumn": 6,
+          "startByte": 49,
+          "endByte": 1198
+        }
+      },
+      "partialFingerprints": {
+        "function.name": "Main"
+      },
+      "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
+      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "nested-control-flow",
+      "message": "Deeply nested control flow (level = 5)",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }",
+      "snippetWithContext": "        int foo = 42;/n        /n        if (foo > 0)/n        {/n            if (foo > 10)/n            {/n                if (foo < 20)/n                {/n                    if (foo % 2 == 0)/n                    {/n                        if (foo % 3 == 0)/n                        {/n                            if (foo % 5 == 0)/n                            {/n                                if (foo % 7 == 0)/n                                {/n                                    if (foo % 11 == 0)/n                                    {/n                                        if (foo % 13 == 0)/n                                        {/n                                            if (foo % 17 == 0)/n                                            {/n                                                Console.WriteLine(/"Nested!/");/n                                            }/n                                        }/n                                    }/n                                }/n                            }/n                        }/n                    }/n                }/n            }/n        }/n    }/n}",
+      "effortMinutes": 15,
+      "value": 5,
+      "location": {
+        "path": "FileComplexity.cs",
+        "range": {
+          "startLine": 17,
+          "startColumn": 25,
+          "endLine": 35,
+          "endColumn": 26,
+          "startByte": 328,
+          "endByte": 1128
+        }
+      },
+      "mode": "MODE_BLOCK"
     }
   ],
   "stats": [
     {
-      "buildId": "[..]",
-      "analyzedAt": "[..]",
+      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
+      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
       "name": "BooleanLogic.cs",
       "fullyQualifiedName": "BooleanLogic.cs",
       "path": "BooleanLogic.cs",
@@ -64,6 +153,26 @@
       "complexity": 2,
       "cyclomatic": 8,
       "lcom4": 0
+    },
+    {
+      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
+      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "name": "FileComplexity.cs",
+      "fullyQualifiedName": "FileComplexity.cs",
+      "path": "FileComplexity.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 41,
+      "codeLines": 29,
+      "commentLines": 0,
+      "blankLines": 12,
+      "complexity": 55,
+      "cyclomatic": 28,
+      "lcom4": 0
     }
   ]
 }

From b23edcc3434737e1acdcf36a1cf8d68d8d0ae9dd Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Fri, 3 Jan 2025 15:05:52 -0800
Subject: [PATCH 11/53] Function complexity

---
 .../csharp/basic.in/FunctionComplexity.cs     | 30 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 69 ++++++++++++++++---
 2 files changed, 88 insertions(+), 11 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/FunctionComplexity.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/FunctionComplexity.cs b/qlty-cli/tests/lang/csharp/basic.in/FunctionComplexity.cs
new file mode 100644
index 000000000..e6d00b498
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/FunctionComplexity.cs
@@ -0,0 +1,30 @@
+using System;
+
+public class FunctionComplexity
+{
+    public void Simple()
+    {
+    }
+
+    public void Complex()
+    {
+        int bar = 42;
+
+        if (bar > 0)
+        {
+            if (bar > 10)
+            {
+                if (bar < 20)
+                {
+                    if (bar % 2 == 0)
+                    {
+                        if (bar % 3 == 0)
+                        {
+                            Console.WriteLine("Nested!");
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index ba3d5ebaa..0d9565c5b 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 2,
+    "filesAnalyzed": 3,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -44,8 +44,8 @@
       "mode": "MODE_BLOCK"
     },
     {
-      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
-      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "tool": "qlty",
       "driver": "structure",
       "ruleKey": "file-complexity",
@@ -75,8 +75,8 @@
       "mode": "MODE_BLOCK"
     },
     {
-      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
-      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "tool": "qlty",
       "driver": "structure",
       "ruleKey": "function-complexity",
@@ -106,8 +106,8 @@
       "mode": "MODE_BLOCK"
     },
     {
-      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
-      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "tool": "qlty",
       "driver": "structure",
       "ruleKey": "nested-control-flow",
@@ -131,12 +131,39 @@
         }
       },
       "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "nested-control-flow",
+      "message": "Deeply nested control flow (level = 5)",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "if (bar % 3 == 0)/n                        {/n                            Console.WriteLine(/"Nested!/");/n                        }",
+      "snippetWithContext": "        int bar = 42;/n/n        if (bar > 0)/n        {/n            if (bar > 10)/n            {/n                if (bar < 20)/n                {/n                    if (bar % 2 == 0)/n                    {/n                        if (bar % 3 == 0)/n                        {/n                            Console.WriteLine(/"Nested!/");/n                        }/n                    }/n                }/n            }/n        }/n    }/n}",
+      "effortMinutes": 15,
+      "value": 5,
+      "location": {
+        "path": "FunctionComplexity.cs",
+        "range": {
+          "startLine": 21,
+          "startColumn": 25,
+          "endLine": 24,
+          "endColumn": 26,
+          "startByte": 345,
+          "endByte": 472
+        }
+      },
+      "mode": "MODE_BLOCK"
     }
   ],
   "stats": [
     {
-      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
-      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "BooleanLogic.cs",
       "fullyQualifiedName": "BooleanLogic.cs",
       "path": "BooleanLogic.cs",
@@ -155,8 +182,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "b02520fe-4947-4fad-9d84-5728dfba3399",
-      "analyzedAt": "2025-01-03T22:56:44.556091+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "FileComplexity.cs",
       "fullyQualifiedName": "FileComplexity.cs",
       "path": "FileComplexity.cs",
@@ -173,6 +200,26 @@
       "complexity": 55,
       "cyclomatic": 28,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "FunctionComplexity.cs",
+      "fullyQualifiedName": "FunctionComplexity.cs",
+      "path": "FunctionComplexity.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 2,
+      "fields": 0,
+      "lines": 30,
+      "codeLines": 21,
+      "commentLines": 0,
+      "blankLines": 9,
+      "complexity": 15,
+      "cyclomatic": 13,
+      "lcom4": 0
     }
   ]
 }

From 0858ecf71714fd722c18a4b4bfabf5f88c4bd4f3 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Fri, 3 Jan 2025 16:07:07 -0800
Subject: [PATCH 12/53] Add duplication test

---
 .../tests/lang/csharp/basic.in/Identical.cs   |  56 ++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 104 +++++++++++++++++-
 2 files changed, 159 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/Identical.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/Identical.cs b/qlty-cli/tests/lang/csharp/basic.in/Identical.cs
new file mode 100644
index 000000000..98f919e83
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/Identical.cs
@@ -0,0 +1,56 @@
+using System;
+
+public class Identical
+{
+    public static double[] f0(double[] numbers)
+    {
+        double sum = 0;
+        foreach (double num in numbers)
+        {
+            sum += num;
+        }
+        double mean = sum / numbers.Length;
+
+        double[] sortedNumbers = (double[])numbers.Clone();
+        Array.Sort(sortedNumbers);
+
+        double median;
+        int length = sortedNumbers.Length;
+        if (length % 2 == 0)
+        {
+            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;
+        }
+        else
+        {
+            median = sortedNumbers[length / 2];
+        }
+
+        return new double[] { mean, median };
+    }
+
+    public static double[] f1(double[] numbers)
+    {
+        double sum = 0;
+        foreach (double num in numbers)
+        {
+            sum += num;
+        }
+        double mean = sum / numbers.Length;
+
+        double[] sortedNumbers = (double[])numbers.Clone();
+        Array.Sort(sortedNumbers);
+
+        double median;
+        int length = sortedNumbers.Length;
+        if (length % 2 == 0)
+        {
+            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;
+        }
+        else
+        {
+            median = sortedNumbers[length / 2];
+        }
+
+        return new double[] { mean, median };
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 0d9565c5b..5f0efd67f 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 3,
+    "filesAnalyzed": 4,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -16,6 +16,88 @@
   "messages": [],
   "invocations": [],
   "issues": [
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "duplication",
+      "ruleKey": "similar-code",
+      "message": "Found 25 lines of similar code in 2 locations (mass = 125)",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_DUPLICATION",
+      "snippet": "public static double[] f0(double[] numbers)/n    {/n        double sum = 0;/n        foreach (double num in numbers)/n        {/n            sum += num;/n        }/n        double mean = sum / numbers.Length;/n/n        double[] sortedNumbers = (double[])numbers.Clone();/n        Array.Sort(sortedNumbers);/n/n        double median;/n        int length = sortedNumbers.Length;/n        if (length % 2 == 0)/n        {/n            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;/n        }/n        else/n        {/n            median = sortedNumbers[length / 2];/n        }/n/n        return new double[] { mean, median };/n    }",
+      "snippetWithContext": "using System;/n/npublic class Identical/n{/n    public static double[] f0(double[] numbers)/n    {/n        double sum = 0;/n        foreach (double num in numbers)/n        {/n            sum += num;/n        }/n        double mean = sum / numbers.Length;/n/n        double[] sortedNumbers = (double[])numbers.Clone();/n        Array.Sort(sortedNumbers);/n/n        double median;/n        int length = sortedNumbers.Length;/n        if (length % 2 == 0)/n        {/n            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;/n        }/n        else/n        {/n            median = sortedNumbers[length / 2];/n        }/n/n        return new double[] { mean, median };/n    }/n/n    public static double[] f1(double[] numbers)/n    {/n        double sum = 0;/n        foreach (double num in numbers)/n        {/n            sum += num;/n        }/n        double mean = sum / numbers.Length;/n/n        double[] sortedNumbers = (double[])numbers.Clone();",
+      "effortMinutes": 60,
+      "value": 25,
+      "valueDelta": 10,
+      "location": {
+        "path": "Identical.cs",
+        "range": {
+          "startLine": 5,
+          "endLine": 29,
+          "startByte": 44,
+          "endByte": 679
+        }
+      },
+      "otherLocations": [
+        {
+          "path": "Identical.cs",
+          [..]
+          [..]
+          [..]
+          [..]
+        }
+      ],
+      "properties": {
+        [..]
+        [..]
+        [..]
+        [..]
+      },
+      "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "duplication",
+      "ruleKey": "similar-code",
+      "message": "Found 25 lines of similar code in 2 locations (mass = 125)",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_DUPLICATION",
+      "snippet": "public static double[] f1(double[] numbers)/n    {/n        double sum = 0;/n        foreach (double num in numbers)/n        {/n            sum += num;/n        }/n        double mean = sum / numbers.Length;/n/n        double[] sortedNumbers = (double[])numbers.Clone();/n        Array.Sort(sortedNumbers);/n/n        double median;/n        int length = sortedNumbers.Length;/n        if (length % 2 == 0)/n        {/n            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;/n        }/n        else/n        {/n            median = sortedNumbers[length / 2];/n        }/n/n        return new double[] { mean, median };/n    }",
+      "snippetWithContext": "        }/n        else/n        {/n            median = sortedNumbers[length / 2];/n        }/n/n        return new double[] { mean, median };/n    }/n/n    public static double[] f1(double[] numbers)/n    {/n        double sum = 0;/n        foreach (double num in numbers)/n        {/n            sum += num;/n        }/n        double mean = sum / numbers.Length;/n/n        double[] sortedNumbers = (double[])numbers.Clone();/n        Array.Sort(sortedNumbers);/n/n        double median;/n        int length = sortedNumbers.Length;/n        if (length % 2 == 0)/n        {/n            median = (sortedNumbers[length / 2 - 1] + sortedNumbers[length / 2]) / 2.0;/n        }/n        else/n        {/n            median = sortedNumbers[length / 2];/n        }/n/n        return new double[] { mean, median };/n    }/n}",
+      "effortMinutes": 60,
+      "value": 25,
+      "valueDelta": 10,
+      "location": {
+        "path": "Identical.cs",
+        "range": {
+          "startLine": 31,
+          "endLine": 55,
+          "startByte": 685,
+          "endByte": 1320
+        }
+      },
+      "otherLocations": [
+        {
+          "path": "Identical.cs",
+          [..]
+          [..]
+          [..]
+          [..]
+        }
+      ],
+      "properties": {
+        [..]
+        [..]
+        [..]
+        [..]
+      },
+      "mode": "MODE_BLOCK"
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -220,6 +302,26 @@
       "complexity": 15,
       "cyclomatic": 13,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Identical.cs",
+      "fullyQualifiedName": "Identical.cs",
+      "path": "Identical.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 2,
+      "fields": 0,
+      "lines": 56,
+      "codeLines": 37,
+      "commentLines": 0,
+      "blankLines": 19,
+      "complexity": 4,
+      "cyclomatic": 21,
+      "lcom4": 0
     }
   ]
 }

From c5038dc70ab53dd66c18fe18a8717cddbdcdc31d Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Fri, 3 Jan 2025 22:12:37 -0800
Subject: [PATCH 13/53] Add Lines.cs

---
 qlty-cli/tests/lang/csharp/basic.in/Lines.cs | 57 ++++++++++++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout      | 22 +++++++-
 2 files changed, 78 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/Lines.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/Lines.cs b/qlty-cli/tests/lang/csharp/basic.in/Lines.cs
new file mode 100644
index 000000000..f85669ac9
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/Lines.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+
+public class Main
+{
+    public static void F1()
+    {
+        try
+        {
+            string tempFilePath = Path.Combine(
+                Path.GetTempPath(),
+                $"ruby{Guid.NewGuid()}.kt"
+            );
+            
+            using (var writer = new StreamWriter(tempFilePath))
+            {
+                writer.Write("foo(...args)");
+            }
+
+            object tree = ParseFile(tempFilePath);
+
+            Bar();
+        }
+        catch (IOException e)
+        {
+            Console.Error.WriteLine(e);
+        }
+    }
+
+    public static object ParseFile(string filePath)
+    {
+        return new object();
+    }
+
+    // Foo
+    public static void F2()
+    {
+        Bar(); // does not count as comment line
+    }
+
+    // multi-line comment
+    /*
+     * line1
+     * line2
+     * line4
+     */
+
+    public static void F3()
+    {
+        Bar();
+    }
+
+    public static void Bar()
+    {
+        Console.WriteLine("bar() called");
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 5f0efd67f..29a78f024 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 4,
+    "filesAnalyzed": 5,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -322,6 +322,26 @@
       "complexity": 4,
       "cyclomatic": 21,
       "lcom4": 0
+    },
+    {
+      "buildId": "1e0563bf-7a4b-4bac-ad95-31e63a74a305",
+      "analyzedAt": "2025-01-04T06:11:47.862391+00:00",
+      "name": "Lines.cs",
+      "fullyQualifiedName": "Lines.cs",
+      "path": "Lines.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 5,
+      "fields": 0,
+      "lines": 57,
+      "codeLines": 32,
+      "commentLines": 7,
+      "blankLines": 18,
+      "complexity": 1,
+      "cyclomatic": 3,
+      "lcom4": 0
     }
   ]
 }

From b7f0d519ffea66bf67c6840cea5a1de24a2ffcfa Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 13:26:12 -0800
Subject: [PATCH 14/53] Ignore buildid and analyzed at

---
 qlty-cli/tests/lang/csharp/basic.stdout | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 29a78f024..e90fc7155 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -324,8 +324,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "1e0563bf-7a4b-4bac-ad95-31e63a74a305",
-      "analyzedAt": "2025-01-04T06:11:47.862391+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "Lines.cs",
       "fullyQualifiedName": "Lines.cs",
       "path": "Lines.cs",

From 6acf02c9af5b42ac0e9ea40b26719beaac462d98 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 13:45:55 -0800
Subject: [PATCH 15/53] Nested control aka too many return statements

---
 .../lang/csharp/basic.in/NestedControl.cs     | 62 +++++++++++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 50 ++++++++++++++-
 2 files changed, 111 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/NestedControl.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/NestedControl.cs b/qlty-cli/tests/lang/csharp/basic.in/NestedControl.cs
new file mode 100644
index 000000000..bbef1cc38
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/NestedControl.cs
@@ -0,0 +1,62 @@
+using System;
+
+public class Main
+{
+    public static void NotNested(string foo, string bar)
+    {
+        if ((foo == "cat" && bar == "dog") || (foo == "dog" && bar == "cat"))
+        {
+            Console.WriteLine("Got a cat and a dog!");
+        }
+        else
+        {
+            Console.WriteLine("Got nothing");
+        }
+    }
+
+    public static void F0(bool bar, bool baz, bool qux, bool quux)
+    {
+        if (bar)
+        {
+            if (baz)
+            {
+                if (qux)
+                {
+                    if (quux)
+                    {
+                        Console.WriteLine("Not deeply nested enough!");
+                    }
+                }
+            }
+        }
+    }
+
+    public static string F2(int foo)
+    {
+        switch (foo)
+        {
+            case 1:
+                return "bar1";
+            case 2:
+                return "bar2";
+            case 3:
+                return "bar3";
+            case 4:
+                return "bar4";
+            case 5:
+                return "bar5";
+            case 6:
+                return "bar6";
+            case 7:
+                return "bar7";
+            case 8:
+                return "bar8";
+            case 9:
+                return "bar9";
+            case 10:
+                return "bar10";
+            default:
+                throw new ArgumentException("Invalid foo value");
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index e90fc7155..8e865ef35 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 5,
+    "filesAnalyzed": 6,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -240,6 +240,34 @@
         }
       },
       "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "return-statements",
+      "message": "Function with many returns (count = 10): F2",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "public static string F2(int foo)/n    {/n        switch (foo)/n        {/n            case 1:/n                return /"bar1/";/n            case 2:/n                return /"bar2/";/n            case 3:/n                return /"bar3/";/n            case 4:/n                return /"bar4/";/n            case 5:/n                return /"bar5/";/n            case 6:/n                return /"bar6/";/n            case 7:/n                return /"bar7/";/n            case 8:/n                return /"bar8/";/n            case 9:/n                return /"bar9/";/n            case 10:/n                return /"bar10/";/n            default:/n                throw new ArgumentException(/"Invalid foo value/");/n        }/n    }",
+      "snippetWithContext": "                {/n                    if (quux)/n                    {/n                        Console.WriteLine(/"Not deeply nested enough!/");/n                    }/n                }/n            }/n        }/n    }/n/n    public static string F2(int foo)/n    {/n        switch (foo)/n        {/n            case 1:/n                return /"bar1/";/n            case 2:/n                return /"bar2/";/n            case 3:/n                return /"bar3/";/n            case 4:/n                return /"bar4/";/n            case 5:/n                return /"bar5/";/n            case 6:/n                return /"bar6/";/n            case 7:/n                return /"bar7/";/n            case 8:/n                return /"bar8/";/n            case 9:/n                return /"bar9/";/n            case 10:/n                return /"bar10/";/n            default:/n                throw new ArgumentException(/"Invalid foo value/");/n        }/n    }/n}",
+      "effortMinutes": 35,
+      "value": 10,
+      "valueDelta": 4,
+      "location": {
+        "path": "NestedControl.cs",
+        "range": {
+          "startLine": 34,
+          "startColumn": 5,
+          "endLine": 61,
+          "endColumn": 6,
+          "startByte": 714,
+          "endByte": 1398
+        }
+      },
+      "mode": "MODE_BLOCK"
     }
   ],
   "stats": [
@@ -342,6 +370,26 @@
       "complexity": 1,
       "cyclomatic": 3,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "NestedControl.cs",
+      "fullyQualifiedName": "NestedControl.cs",
+      "path": "NestedControl.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 3,
+      "fields": 0,
+      "lines": 62,
+      "codeLines": 48,
+      "commentLines": 0,
+      "blankLines": 14,
+      "complexity": 15,
+      "cyclomatic": 24,
+      "lcom4": 0
     }
   ]
 }

From ffbc80cf00bff9a7dab1b80a1dc5b0c6cc77aee7 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 13:54:18 -0800
Subject: [PATCH 16/53] Too many parameters integration test

---
 .../tests/lang/csharp/basic.in/Parameters.cs  | 26 ++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 49 ++++++++++++++++++-
 2 files changed, 74 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/Parameters.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/Parameters.cs b/qlty-cli/tests/lang/csharp/basic.in/Parameters.cs
new file mode 100644
index 000000000..49b7dc0e9
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/Parameters.cs
@@ -0,0 +1,26 @@
+using System;
+
+public class Parameters
+{
+    public static void F0()
+    {
+    }
+
+    public static void F1(object dog, object cat)
+    {
+    }
+
+    public static void F2(object a, object b, object c, object d, object e, object f)
+    {
+    }
+
+    public static void F3()
+    {
+        object foo = Bar(1, 2, 3, 4);
+    }
+
+    public static object Bar(int a, int b, int c, int d)
+    {
+        return new object();
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 8e865ef35..e6b12f392 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 6,
+    "filesAnalyzed": 7,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -187,6 +187,33 @@
       },
       "mode": "MODE_BLOCK"
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "function-parameters",
+      "message": "Function with many parameters (count = 6): F2",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "(object a, object b, object c, object d, object e, object f)",
+      "snippetWithContext": "public class Parameters/n{/n    public static void F0()/n    {/n    }/n/n    public static void F1(object dog, object cat)/n    {/n    }/n/n    public static void F2(object a, object b, object c, object d, object e, object f)/n    {/n    }/n/n    public static void F3()/n    {/n        object foo = Bar(1, 2, 3, 4);/n    }/n/n    public static object Bar(int a, int b, int c, int d)/n    {",
+      "effortMinutes": 15,
+      "value": 6,
+      "location": {
+        "path": "Parameters.cs",
+        "range": {
+          "startLine": 13,
+          "startColumn": 26,
+          "endLine": 13,
+          "endColumn": 86,
+          "startByte": 170,
+          "endByte": 230
+        }
+      },
+      "mode": "MODE_BLOCK"
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -390,6 +417,26 @@
       "complexity": 15,
       "cyclomatic": 24,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Parameters.cs",
+      "fullyQualifiedName": "Parameters.cs",
+      "path": "Parameters.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 5,
+      "fields": 0,
+      "lines": 26,
+      "codeLines": 17,
+      "commentLines": 0,
+      "blankLines": 9,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
     }
   ]
 }

From a9630c360cd5c3673b513e9cc6caca1f7a082667 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 13:56:48 -0800
Subject: [PATCH 17/53] Returns file for C#

---
 .../tests/lang/csharp/basic.in/Returns.cs     | 61 +++++++++++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 22 ++++++-
 2 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/Returns.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/Returns.cs b/qlty-cli/tests/lang/csharp/basic.in/Returns.cs
new file mode 100644
index 000000000..d47a26ab2
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/Returns.cs
@@ -0,0 +1,61 @@
+using System;
+
+public class Returns
+{
+    public void F0()
+    {
+    }
+
+    public void F1()
+    {
+        return;
+    }
+
+    public void F2()
+    {
+        if (true)
+        {
+            return;
+        }
+        else
+        {
+            return;
+        }
+    }
+
+    public void F3()
+    {
+        if (true)
+        {
+            return;
+        }
+        else if (true)
+        {
+            return;
+        }
+        else
+        {
+            return;
+        }
+    }
+
+    public void F4()
+    {
+        if (true)
+        {
+            return;
+        }
+        else if (true)
+        {
+            return;
+        }
+        else if (true)
+        {
+            return;
+        }
+        else
+        {
+            return;
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index e6b12f392..02f00c37c 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 7,
+    "filesAnalyzed": 8,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -437,6 +437,26 @@
       "complexity": 0,
       "cyclomatic": 1,
       "lcom4": 0
+    },
+    {
+      "buildId": "96e26162-d478-43be-8b24-4e17ad685ee2",
+      "analyzedAt": "2025-01-06T21:56:34.969635+00:00",
+      "name": "Returns.cs",
+      "fullyQualifiedName": "Returns.cs",
+      "path": "Returns.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 5,
+      "fields": 0,
+      "lines": 61,
+      "codeLines": 34,
+      "commentLines": 0,
+      "blankLines": 27,
+      "complexity": 9,
+      "cyclomatic": 7,
+      "lcom4": 0
     }
   ]
 }

From 1b4a79474a0a94f43decf7a0d61cbb75ee3fe4a6 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 13:57:24 -0800
Subject: [PATCH 18/53] --

---
 qlty-cli/tests/lang/csharp/basic.stdout | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 02f00c37c..8eae9d907 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -439,8 +439,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "96e26162-d478-43be-8b24-4e17ad685ee2",
-      "analyzedAt": "2025-01-06T21:56:34.969635+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "Returns.cs",
       "fullyQualifiedName": "Returns.cs",
       "path": "Returns.cs",

From ad652ed88747f363c7623b2a3a6f2128f3e44dc4 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 14:57:52 -0800
Subject: [PATCH 19/53] Conditional assignment

---
 .../cognitive/CountConditionalAssignment.cs   | 16 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 41 ++++++++++++++++++-
 2 files changed, 56 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cognitive/CountConditionalAssignment.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountConditionalAssignment.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountConditionalAssignment.cs
new file mode 100644
index 000000000..9fab85923
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountConditionalAssignment.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Cognitive
+{
+    class CountConditionalAssignment
+    {
+        public static void Main(string[] args)
+        {
+            int bar = 0;
+            bar = bar != 0 ? bar : 10;
+
+            int foo = 0;
+            foo = foo != 0 && foo != 10 ? foo : 10;
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 8eae9d907..b69724c79 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 8,
+    "filesAnalyzed": 9,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -457,6 +457,45 @@
       "complexity": 9,
       "cyclomatic": 7,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CountConditionalAssignment.cs",
+      "fullyQualifiedName": "cognitive/CountConditionalAssignment.cs",
+      "path": "cognitive/CountConditionalAssignment.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 16,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 3,
+      "cyclomatic": 7,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "cognitive",
+      "fullyQualifiedName": "cognitive",
+      "path": "cognitive",
+      "kind": "COMPONENT_TYPE_DIRECTORY",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 16,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 3,
+      "cyclomatic": 7,
+      "lcom4": 0
     }
   ]
 }

From b95cd5923d94ddd2d32f52c650801c10b067b7b6 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 15:01:00 -0800
Subject: [PATCH 20/53] Count function with ternary

---
 .../cognitive/CountFunctionWithTernary.cs     |  8 +++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 36 ++++++++++++++-----
 2 files changed, 36 insertions(+), 8 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cognitive/CountFunctionWithTernary.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountFunctionWithTernary.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountFunctionWithTernary.cs
new file mode 100644
index 000000000..fefb933d7
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountFunctionWithTernary.cs
@@ -0,0 +1,8 @@
+namespace cognitive 
+{
+  class CountFunctionWithTernary {
+    public static int Main(int num) {
+      return num > 0 ? num : num * -1;
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index b69724c79..fa7f4d20e 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 9,
+    "filesAnalyzed": 10,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -481,20 +481,40 @@
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
+      "name": "CountFunctionWithTernary.cs",
+      "fullyQualifiedName": "cognitive/CountFunctionWithTernary.cs",
+      "path": "cognitive/CountFunctionWithTernary.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 8,
+      "codeLines": 8,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 1,
+      "cyclomatic": 4,
+      "lcom4": 0
+    },
+    {
+      "buildId": "5558c31d-837f-4304-b7b5-c311dc30d5b1",
+      "analyzedAt": "2025-01-06T23:00:37.510021+00:00",
       "name": "cognitive",
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 1,
-      "classes": 1,
-      "functions": 1,
+      "files": 2,
+      "classes": 2,
+      "functions": 2,
       "fields": 0,
-      "lines": 16,
-      "codeLines": 14,
+      "lines": 24,
+      "codeLines": 22,
       "commentLines": 0,
       "blankLines": 2,
-      "complexity": 3,
-      "cyclomatic": 7,
+      "complexity": 4,
+      "cyclomatic": 11,
       "lcom4": 0
     }
   ]

From 2551060239e14551f44e0327d8716d5a5429c8b5 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 15:01:28 -0800
Subject: [PATCH 21/53] Count function with ternary

---
 qlty-cli/tests/lang/csharp/basic.stdout | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index fa7f4d20e..6c9f0a520 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -499,8 +499,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "5558c31d-837f-4304-b7b5-c311dc30d5b1",
-      "analyzedAt": "2025-01-06T23:00:37.510021+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "cognitive",
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",

From 1c29a36abe4ff8aa7f0c6dd41e696942bb91cb4f Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 15:15:14 -0800
Subject: [PATCH 22/53] Count non sequential logical operators test

---
 .../CountNonSequentialLogicalOperators.cs     |  8 +++
 qlty-cli/tests/lang/csharp/basic.stdout       | 63 ++++++++++++++++---
 2 files changed, 63 insertions(+), 8 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs
new file mode 100644
index 000000000..0dcced0b2
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs
@@ -0,0 +1,8 @@
+namespace cognitive
+{
+  class CountNonSequentialLogicalOperators {
+    public static boolean Main(String[] args) {
+      return true || false && true && false || true;
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 6c9f0a520..1c583d5be 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 10,
+    "filesAnalyzed": 11,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -125,6 +125,33 @@
       },
       "mode": "MODE_BLOCK"
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "boolean-logic",
+      "message": "Complex binary expression",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "false && true",
+      "snippetWithContext": "namespace cognitive/n{/n  class CountNonSequentialLogicalOperators {/n    public static boolean Main(String[] args) {/n      return true || false && true && false || true;/n    }/n  }/n}",
+      "effortMinutes": 10,
+      "value": 4,
+      "location": {
+        "path": "cognitive/CountNonSequentialLogicalOperators.cs",
+        "range": {
+          "startLine": 5,
+          "startColumn": 22,
+          "endLine": 5,
+          "endColumn": 35,
+          "startByte": 136,
+          "endByte": 149
+        }
+      },
+      "mode": "MODE_BLOCK"
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -498,6 +525,26 @@
       "cyclomatic": 4,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CountNonSequentialLogicalOperators.cs",
+      "fullyQualifiedName": "cognitive/CountNonSequentialLogicalOperators.cs",
+      "path": "cognitive/CountNonSequentialLogicalOperators.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 8,
+      "codeLines": 8,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 3,
+      "cyclomatic": 5,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -505,16 +552,16 @@
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 2,
-      "classes": 2,
-      "functions": 2,
+      "files": 3,
+      "classes": 3,
+      "functions": 3,
       "fields": 0,
-      "lines": 24,
-      "codeLines": 22,
+      "lines": 32,
+      "codeLines": 30,
       "commentLines": 0,
       "blankLines": 2,
-      "complexity": 4,
-      "cyclomatic": 11,
+      "complexity": 7,
+      "cyclomatic": 16,
       "lcom4": 0
     }
   ]

From 24d71ffa6342f8e9fcb3c0e345c0f0d17c450c32 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 20:48:08 -0800
Subject: [PATCH 23/53] Multiple conditionals

---
 .../CountNonSequentialLogicalOperators.cs     |  2 +-
 .../cognitive/MultipleConditionals.cs         | 20 ++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 67 ++++++++++++++++---
 .../lang/csharp/pending/CountRecursion.cs     |  8 +++
 4 files changed, 86 insertions(+), 11 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cognitive/MultipleConditionals.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/CountRecursion.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs
index 0dcced0b2..598bbb30e 100644
--- a/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs
+++ b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountNonSequentialLogicalOperators.cs
@@ -1,7 +1,7 @@
 namespace cognitive
 {
   class CountNonSequentialLogicalOperators {
-    public static boolean Main(String[] args) {
+    public static boolean Main(string[] args) {
       return true || false && true && false || true;
     }
   }
diff --git a/qlty-cli/tests/lang/csharp/basic.in/cognitive/MultipleConditionals.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/MultipleConditionals.cs
new file mode 100644
index 000000000..f6e0e9fc8
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cognitive/MultipleConditionals.cs
@@ -0,0 +1,20 @@
+namespace cognitive
+{
+  class MultipleConditionals {
+    public static string Main(int foo) {
+      if (foo >= 80 && foo <= 100) {
+        return "Most complex!";
+      } else if (foo >= 60 && foo <= 79) {
+        return "Very complex";
+      } else if (foo >= 40 && foo <= 59) {
+        return "Somewhat complex";
+      } else if (foo >= 20 && foo <= 39) {
+        return "Not complex";
+      } else if (foo >= 0 && foo <= 19) {
+        return "Least complex!";
+      } else {
+        return null;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 1c583d5be..85ec25c73 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 11,
+    "filesAnalyzed": 12,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -136,7 +136,7 @@
       "language": "LANGUAGE_C_SHARP",
       "category": "CATEGORY_STRUCTURE",
       "snippet": "false && true",
-      "snippetWithContext": "namespace cognitive/n{/n  class CountNonSequentialLogicalOperators {/n    public static boolean Main(String[] args) {/n      return true || false && true && false || true;/n    }/n  }/n}",
+      "snippetWithContext": "namespace cognitive/n{/n  class CountNonSequentialLogicalOperators {/n    public static boolean Main(string[] args) {/n      return true || false && true && false || true;/n    }/n  }/n}",
       "effortMinutes": 10,
       "value": 4,
       "location": {
@@ -322,6 +322,33 @@
         }
       },
       "mode": "MODE_BLOCK"
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "tool": "qlty",
+      "driver": "structure",
+      "ruleKey": "return-statements",
+      "message": "Function with many returns (count = 6): Main",
+      "level": "LEVEL_MEDIUM",
+      "language": "LANGUAGE_C_SHARP",
+      "category": "CATEGORY_STRUCTURE",
+      "snippet": "public static string Main(int foo) {/n      if (foo >= 80 && foo <= 100) {/n        return /"Most complex!/";/n      } else if (foo >= 60 && foo <= 79) {/n        return /"Very complex/";/n      } else if (foo >= 40 && foo <= 59) {/n        return /"Somewhat complex/";/n      } else if (foo >= 20 && foo <= 39) {/n        return /"Not complex/";/n      } else if (foo >= 0 && foo <= 19) {/n        return /"Least complex!/";/n      } else {/n        return null;/n      }/n    }",
+      "snippetWithContext": "namespace cognitive/n{/n  class MultipleConditionals {/n    public static string Main(int foo) {/n      if (foo >= 80 && foo <= 100) {/n        return /"Most complex!/";/n      } else if (foo >= 60 && foo <= 79) {/n        return /"Very complex/";/n      } else if (foo >= 40 && foo <= 59) {/n        return /"Somewhat complex/";/n      } else if (foo >= 20 && foo <= 39) {/n        return /"Not complex/";/n      } else if (foo >= 0 && foo <= 19) {/n        return /"Least complex!/";/n      } else {/n        return null;/n      }/n    }/n  }/n}",
+      "effortMinutes": 15,
+      "value": 6,
+      "location": {
+        "path": "cognitive/MultipleConditionals.cs",
+        "range": {
+          "startLine": 4,
+          "startColumn": 5,
+          "endLine": 18,
+          "endColumn": 6,
+          "startByte": 57,
+          "endByte": 512
+        }
+      },
+      "mode": "MODE_BLOCK"
     }
   ],
   "stats": [
@@ -545,6 +572,26 @@
       "cyclomatic": 5,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "MultipleConditionals.cs",
+      "fullyQualifiedName": "cognitive/MultipleConditionals.cs",
+      "path": "cognitive/MultipleConditionals.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 20,
+      "codeLines": 19,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 7,
+      "cyclomatic": 21,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -552,16 +599,16 @@
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 3,
-      "classes": 3,
-      "functions": 3,
+      "files": 4,
+      "classes": 4,
+      "functions": 4,
       "fields": 0,
-      "lines": 32,
-      "codeLines": 30,
+      "lines": 52,
+      "codeLines": 49,
       "commentLines": 0,
-      "blankLines": 2,
-      "complexity": 7,
-      "cyclomatic": 16,
+      "blankLines": 3,
+      "complexity": 14,
+      "cyclomatic": 37,
       "lcom4": 0
     }
   ]
diff --git a/qlty-cli/tests/lang/csharp/pending/CountRecursion.cs b/qlty-cli/tests/lang/csharp/pending/CountRecursion.cs
new file mode 100644
index 000000000..aecd1e25c
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/CountRecursion.cs
@@ -0,0 +1,8 @@
+namespace cognitive
+{
+  class CountRecursion {
+    public static void Main() {
+      Main();
+    }
+  }
+}
\ No newline at end of file

From 7a6b0c8ec3a3bd03da6a209c6a7b76768852f7e3 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:23:28 -0800
Subject: [PATCH 24/53] Add Cyclo basic

---
 .../csharp/basic.in/cyclomatic/CycloBasic.cs  |  7 ++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 41 ++++++++++++++++++-
 2 files changed, 47 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloBasic.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloBasic.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloBasic.cs
new file mode 100644
index 000000000..d54d0a340
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloBasic.cs
@@ -0,0 +1,7 @@
+namespace Cyclomatic {
+  class CycloBasic {
+    public static void Main(string args[]) {
+      int x = 1;
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 85ec25c73..50949f667 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 12,
+    "filesAnalyzed": 13,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -592,6 +592,26 @@
       "cyclomatic": 21,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CycloBasic.cs",
+      "fullyQualifiedName": "cyclomatic/CycloBasic.cs",
+      "path": "cyclomatic/CycloBasic.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 7,
+      "codeLines": 7,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -610,6 +630,25 @@
       "complexity": 14,
       "cyclomatic": 37,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "cyclomatic",
+      "fullyQualifiedName": "cyclomatic",
+      "path": "cyclomatic",
+      "kind": "COMPONENT_TYPE_DIRECTORY",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 7,
+      "codeLines": 7,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
     }
   ]
 }

From 88da24e550375b808d4cadac877757de4b41af79 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:31:23 -0800
Subject: [PATCH 25/53] CylcoIf

---
 .../csharp/basic.in/cyclomatic/CycloIf.cs     | 11 ++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 36 ++++++++++++++-----
 2 files changed, 39 insertions(+), 8 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIf.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIf.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIf.cs
new file mode 100644
index 000000000..3583dea7f
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIf.cs
@@ -0,0 +1,11 @@
+namespace Cyclomatic
+{
+  class CycloIf {
+    public static string Main(string args[]) {
+      int x = 1;
+      if (x > 0) {
+        int y = 1;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 50949f667..6e2f6c34f 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 13,
+    "filesAnalyzed": 14,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -612,6 +612,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CycloIf.cs",
+      "fullyQualifiedName": "cyclomatic/CycloIf.cs",
+      "path": "cyclomatic/CycloIf.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 11,
+      "codeLines": 11,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 1,
+      "cyclomatic": 3,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -638,16 +658,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 1,
-      "classes": 1,
-      "functions": 1,
+      "files": 2,
+      "classes": 2,
+      "functions": 2,
       "fields": 0,
-      "lines": 7,
-      "codeLines": 7,
+      "lines": 18,
+      "codeLines": 18,
       "commentLines": 0,
       "blankLines": 0,
-      "complexity": 0,
-      "cyclomatic": 1,
+      "complexity": 1,
+      "cyclomatic": 4,
       "lcom4": 0
     }
   ]

From dc88e6887997426754fda86f98bd6db416e0b5b1 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:46:57 -0800
Subject: [PATCH 26/53] cycloifelse

---
 .../csharp/basic.in/cyclomatic/CycloIfElse.cs | 13 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 2 files changed, 42 insertions(+), 9 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElse.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElse.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElse.cs
new file mode 100644
index 000000000..5bf619083
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElse.cs
@@ -0,0 +1,13 @@
+namespace Cyclomatic
+{
+  class CycloIfElse {
+    public static void Main(string[] args) {
+      int x = 1;
+      if (x > 0) {
+        int y = 1;
+      } else {
+        int y = 2;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 6e2f6c34f..023bc5640 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 14,
+    "filesAnalyzed": 15,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -632,6 +632,26 @@
       "cyclomatic": 3,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CycloIfElse.cs",
+      "fullyQualifiedName": "cyclomatic/CycloIfElse.cs",
+      "path": "cyclomatic/CycloIfElse.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 13,
+      "codeLines": 12,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 2,
+      "cyclomatic": 3,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -658,16 +678,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 2,
-      "classes": 2,
-      "functions": 2,
+      "files": 3,
+      "classes": 3,
+      "functions": 3,
       "fields": 0,
-      "lines": 18,
-      "codeLines": 18,
+      "lines": 31,
+      "codeLines": 30,
       "commentLines": 0,
-      "blankLines": 0,
-      "complexity": 1,
-      "cyclomatic": 4,
+      "blankLines": 1,
+      "complexity": 3,
+      "cyclomatic": 7,
       "lcom4": 0
     }
   ]

From 386bf32c54ec8d936e23d40ec7fd94cde9b7a313 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:59:49 -0800
Subject: [PATCH 27/53] IfElseIf

---
 .../basic.in/cyclomatic/CycloIfElseIf.cs      | 13 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 36 ++++++++++++++-----
 2 files changed, 41 insertions(+), 8 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIf.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIf.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIf.cs
new file mode 100644
index 000000000..19210ea95
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIf.cs
@@ -0,0 +1,13 @@
+namespace Cyclomatic
+{
+  class CycloIfElseIf {
+    public static void Main(string[] args) {
+      int x = 1;
+      if (x > 0) {
+        int y = 1;
+      } else if (x < 0) {
+        int y = 2;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 023bc5640..32a112c1a 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 15,
+    "filesAnalyzed": 16,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -652,6 +652,26 @@
       "cyclomatic": 3,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CycloIfElseIf.cs",
+      "fullyQualifiedName": "cyclomatic/CycloIfElseIf.cs",
+      "path": "cyclomatic/CycloIfElseIf.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 13,
+      "codeLines": 13,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 2,
+      "cyclomatic": 5,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -678,16 +698,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 3,
-      "classes": 3,
-      "functions": 3,
+      "files": 4,
+      "classes": 4,
+      "functions": 4,
       "fields": 0,
-      "lines": 31,
-      "codeLines": 30,
+      "lines": 44,
+      "codeLines": 43,
       "commentLines": 0,
       "blankLines": 1,
-      "complexity": 3,
-      "cyclomatic": 7,
+      "complexity": 5,
+      "cyclomatic": 12,
       "lcom4": 0
     }
   ]

From 12179db2717d05c43688c1b1352e0dc57cd5580c Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:01:54 -0800
Subject: [PATCH 28/53] IfElseIfElse

---
 .../basic.in/cyclomatic/CycloIfElseIfElse.cs  | 15 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 2 files changed, 44 insertions(+), 9 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIfElse.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIfElse.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIfElse.cs
new file mode 100644
index 000000000..d4423889d
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/CycloIfElseIfElse.cs
@@ -0,0 +1,15 @@
+namespace Cyclomatic
+{
+  class CycloIfElseIfElse {
+    public static void Main(string[] args) {
+      int x = 1;
+      if (x > 0) {
+        int y = 1;
+      } else if (x < 0) {
+        int y = 2;
+      } else {
+        int y = 3;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 32a112c1a..60fdf5969 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 16,
+    "filesAnalyzed": 17,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -672,6 +672,26 @@
       "cyclomatic": 5,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CycloIfElseIfElse.cs",
+      "fullyQualifiedName": "cyclomatic/CycloIfElseIfElse.cs",
+      "path": "cyclomatic/CycloIfElseIfElse.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 15,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 3,
+      "cyclomatic": 5,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -698,16 +718,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 4,
-      "classes": 4,
-      "functions": 4,
+      "files": 5,
+      "classes": 5,
+      "functions": 5,
       "fields": 0,
-      "lines": 44,
-      "codeLines": 43,
+      "lines": 59,
+      "codeLines": 57,
       "commentLines": 0,
-      "blankLines": 1,
-      "complexity": 5,
-      "cyclomatic": 12,
+      "blankLines": 2,
+      "complexity": 8,
+      "cyclomatic": 17,
       "lcom4": 0
     }
   ]

From b857c4518b9793e5af07c15216ddd3a2b55be64b Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:03:50 -0800
Subject: [PATCH 29/53] Empty function

---
 .../basic.in/cyclomatic/EmptyFunction.cs      |  6 +++
 qlty-cli/tests/lang/csharp/basic.stdout       | 40 ++++++++++++++-----
 2 files changed, 36 insertions(+), 10 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyFunction.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyFunction.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyFunction.cs
new file mode 100644
index 000000000..b8742b52b
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyFunction.cs
@@ -0,0 +1,6 @@
+namespace Cyclomatic
+{
+  class EmptyFunction {
+    public static string Main(string args[]) {
+    }
+  }
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 60fdf5969..30ef54d16 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 17,
+    "filesAnalyzed": 18,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -692,6 +692,26 @@
       "cyclomatic": 5,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "EmptyFunction.cs",
+      "fullyQualifiedName": "cyclomatic/EmptyFunction.cs",
+      "path": "cyclomatic/EmptyFunction.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 6,
+      "codeLines": 5,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -712,22 +732,22 @@
       "lcom4": 0
     },
     {
-      "buildId": "[..]",
-      "analyzedAt": "[..]",
+      "buildId": "15dc4eb3-4676-4ad5-a517-c42f24bf719b",
+      "analyzedAt": "2025-01-07T06:03:17.463615+00:00",
       "name": "cyclomatic",
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 5,
-      "classes": 5,
-      "functions": 5,
+      "files": 6,
+      "classes": 6,
+      "functions": 6,
       "fields": 0,
-      "lines": 59,
-      "codeLines": 57,
+      "lines": 65,
+      "codeLines": 62,
       "commentLines": 0,
-      "blankLines": 2,
+      "blankLines": 3,
       "complexity": 8,
-      "cyclomatic": 17,
+      "cyclomatic": 18,
       "lcom4": 0
     }
   ]

From 536c508df3c55eb93cfa359b23b9214108a8d184 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:06:27 -0800
Subject: [PATCH 30/53] Empty If

---
 .../csharp/basic.in/cyclomatic/EmptyIf.cs     |  9 +++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 40 ++++++++++++++-----
 2 files changed, 39 insertions(+), 10 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyIf.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyIf.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyIf.cs
new file mode 100644
index 000000000..38fb54c23
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/EmptyIf.cs
@@ -0,0 +1,9 @@
+namespace Cyclomatic
+{
+  class EmptyIf {
+    public static string Main(bool arg) {
+      if (arg) {
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 30ef54d16..027fb0256 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 18,
+    "filesAnalyzed": 19,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -712,6 +712,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "EmptyIf.cs",
+      "fullyQualifiedName": "cyclomatic/EmptyIf.cs",
+      "path": "cyclomatic/EmptyIf.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 9,
+      "codeLines": 9,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 1,
+      "cyclomatic": 2,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -732,22 +752,22 @@
       "lcom4": 0
     },
     {
-      "buildId": "15dc4eb3-4676-4ad5-a517-c42f24bf719b",
-      "analyzedAt": "2025-01-07T06:03:17.463615+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "cyclomatic",
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 6,
-      "classes": 6,
-      "functions": 6,
+      "files": 7,
+      "classes": 7,
+      "functions": 7,
       "fields": 0,
-      "lines": 65,
-      "codeLines": 62,
+      "lines": 74,
+      "codeLines": 71,
       "commentLines": 0,
       "blankLines": 3,
-      "complexity": 8,
-      "cyclomatic": 18,
+      "complexity": 9,
+      "cyclomatic": 20,
       "lcom4": 0
     }
   ]

From 9e437c16f60db48e902bd1749bdb4fa2dd386c8c Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:08:44 -0800
Subject: [PATCH 31/53] IfWithBool

---
 .../csharp/basic.in/cyclomatic/IfWithBool.cs  |  9 +++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 36 ++++++++++++++-----
 2 files changed, 37 insertions(+), 8 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IfWithBool.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IfWithBool.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IfWithBool.cs
new file mode 100644
index 000000000..04f434d8d
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IfWithBool.cs
@@ -0,0 +1,9 @@
+namespace Cyclomatic
+{
+  class IfWithBool {
+    public static string Main(bool[] arg) {
+      if (args[0] && args[1]) {
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 027fb0256..861d7216d 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 19,
+    "filesAnalyzed": 20,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -732,6 +732,26 @@
       "cyclomatic": 2,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "IfWithBool.cs",
+      "fullyQualifiedName": "cyclomatic/IfWithBool.cs",
+      "path": "cyclomatic/IfWithBool.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 9,
+      "codeLines": 9,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 2,
+      "cyclomatic": 3,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -758,16 +778,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 7,
-      "classes": 7,
-      "functions": 7,
+      "files": 8,
+      "classes": 8,
+      "functions": 8,
       "fields": 0,
-      "lines": 74,
-      "codeLines": 71,
+      "lines": 83,
+      "codeLines": 80,
       "commentLines": 0,
       "blankLines": 3,
-      "complexity": 9,
-      "cyclomatic": 20,
+      "complexity": 11,
+      "cyclomatic": 23,
       "lcom4": 0
     }
   ]

From 5dd91df82cd51a462e0008f0600106b3956b9ced Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:15:53 -0800
Subject: [PATCH 32/53] IterativeForOf / Iterative Map

---
 qlty-analysis/src/lang/csharp.rs              |  3 +-
 .../basic.in/cyclomatic/IterativeForOf.cs     | 12 ++++
 .../basic.in/cyclomatic/IterativeMap.cs       | 17 +++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 62 +++++++++++++++----
 4 files changed, 82 insertions(+), 12 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeForOf.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMap.cs

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index a258f0926..ee52d4339 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -53,6 +53,7 @@ impl CSharp {
     pub const FIELD_DECLARATION: &'static str = "field_declaration";
     pub const FIELD_ACCESS: &'static str = "member_access_expression";
     pub const FOR: &'static str = "for_statement";
+    pub const FOREACH: &'static str = "foreach_statement";
     pub const METHOD_DECLARATION: &'static str = "method_declaration";
     pub const METHOD_INVOCATION: &'static str = "invocation_expression";
     pub const IDENTIFIER: &'static str = "identifier";
@@ -136,7 +137,7 @@ impl Language for CSharp {
     }
 
     fn loop_nodes(&self) -> Vec<&str> {
-        vec![Self::FOR, Self::WHILE, Self::DO]
+        vec![Self::FOR, Self::FOREACH, Self::WHILE, Self::DO]
     }
 
     fn except_nodes(&self) -> Vec<&str> {
diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeForOf.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeForOf.cs
new file mode 100644
index 000000000..c3d4bbe46
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeForOf.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Cyclomatic
+{
+  class IterativeForOf {
+    public static void Main(string[] args) {
+      foreach (var animal in new string[] { "dog", "cat", "bear" }) {
+        Console.WriteLine(animal);
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMap.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMap.cs
new file mode 100644
index 000000000..34c525cb2
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMap.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Cyclomatic
+{
+  class IterativeMap {
+    public static void Main(string[] args) {
+      List<string> animals = new List<string> { "dog", "cat", "bear" };
+
+      animals.Select(animal => {
+        Console.WriteLine(animal);
+        return animal;
+      }).ToList();
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 861d7216d..27acb4eda 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 20,
+    "filesAnalyzed": 22,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -428,8 +428,8 @@
       "codeLines": 37,
       "commentLines": 0,
       "blankLines": 19,
-      "complexity": 4,
-      "cyclomatic": 21,
+      "complexity": 6,
+      "cyclomatic": 23,
       "lcom4": 0
     },
     {
@@ -752,6 +752,46 @@
       "cyclomatic": 3,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "IterativeForOf.cs",
+      "fullyQualifiedName": "cyclomatic/IterativeForOf.cs",
+      "path": "cyclomatic/IterativeForOf.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 12,
+      "codeLines": 11,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 1,
+      "cyclomatic": 2,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "IterativeMap.cs",
+      "fullyQualifiedName": "cyclomatic/IterativeMap.cs",
+      "path": "cyclomatic/IterativeMap.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 17,
+      "codeLines": 15,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -778,16 +818,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 8,
-      "classes": 8,
-      "functions": 8,
+      "files": 10,
+      "classes": 10,
+      "functions": 10,
       "fields": 0,
-      "lines": 83,
-      "codeLines": 80,
+      "lines": 112,
+      "codeLines": 106,
       "commentLines": 0,
-      "blankLines": 3,
-      "complexity": 11,
-      "cyclomatic": 23,
+      "blankLines": 6,
+      "complexity": 12,
+      "cyclomatic": 26,
       "lcom4": 0
     }
   ]

From 59b15aca7358bc1dd2821095936dbda081b11009 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:23:57 -0800
Subject: [PATCH 33/53] IterativeMethods

---
 .../IterativeMethodsWithFilterAndInclude.cs   | 20 ++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 2 files changed, 49 insertions(+), 9 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMethodsWithFilterAndInclude.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMethodsWithFilterAndInclude.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMethodsWithFilterAndInclude.cs
new file mode 100644
index 000000000..2a6a1e6c5
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/IterativeMethodsWithFilterAndInclude.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Cyclomatic
+{
+  class IterativeMethodsWithFilterAndInclude {
+    public static void Main(string[] args) {
+      List<string> animals = new List<string> { "dog", "cat", "bear", "tiger" };
+
+      animals.Where(animal => animal.Length > 3)
+             .ToList()
+             .ForEach(animal => Console.WriteLine(animal));
+
+      if (animals.Contains("cat")) {
+        Console.WriteLine("Found a cat!");
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 27acb4eda..9e9362e47 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 22,
+    "filesAnalyzed": 23,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -792,6 +792,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "IterativeMethodsWithFilterAndInclude.cs",
+      "fullyQualifiedName": "cyclomatic/IterativeMethodsWithFilterAndInclude.cs",
+      "path": "cyclomatic/IterativeMethodsWithFilterAndInclude.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 20,
+      "codeLines": 17,
+      "commentLines": 0,
+      "blankLines": 3,
+      "complexity": 1,
+      "cyclomatic": 3,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -818,16 +838,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 10,
-      "classes": 10,
-      "functions": 10,
+      "files": 11,
+      "classes": 11,
+      "functions": 11,
       "fields": 0,
-      "lines": 112,
-      "codeLines": 106,
+      "lines": 132,
+      "codeLines": 123,
       "commentLines": 0,
-      "blankLines": 6,
-      "complexity": 12,
-      "cyclomatic": 26,
+      "blankLines": 9,
+      "complexity": 13,
+      "cyclomatic": 29,
       "lcom4": 0
     }
   ]

From 1efc874bbf03aa39e8eb90b94f214315b77947b1 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Mon, 6 Jan 2025 22:26:09 -0800
Subject: [PATCH 34/53] While loop

---
 .../csharp/basic.in/cyclomatic/WhileLoop.cs   | 15 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 40 ++++++++++++++-----
 2 files changed, 45 insertions(+), 10 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/cyclomatic/WhileLoop.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/WhileLoop.cs b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/WhileLoop.cs
new file mode 100644
index 000000000..0ba6808e1
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/cyclomatic/WhileLoop.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace CyclomaticComplexity {
+  public class WhileLoop {
+    public static void Main(string[] args) {
+      Foo();
+    }
+
+    public static void Foo() {
+      while (true) {
+        // Infinite loop
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 9e9362e47..50638d015 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 23,
+    "filesAnalyzed": 24,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -812,6 +812,26 @@
       "cyclomatic": 3,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "WhileLoop.cs",
+      "fullyQualifiedName": "cyclomatic/WhileLoop.cs",
+      "path": "cyclomatic/WhileLoop.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 2,
+      "fields": 0,
+      "lines": 15,
+      "codeLines": 12,
+      "commentLines": 1,
+      "blankLines": 2,
+      "complexity": 1,
+      "cyclomatic": 2,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -838,16 +858,16 @@
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 11,
-      "classes": 11,
-      "functions": 11,
+      "files": 12,
+      "classes": 12,
+      "functions": 13,
       "fields": 0,
-      "lines": 132,
-      "codeLines": 123,
-      "commentLines": 0,
-      "blankLines": 9,
-      "complexity": 13,
-      "cyclomatic": 29,
+      "lines": 147,
+      "codeLines": 135,
+      "commentLines": 1,
+      "blankLines": 11,
+      "complexity": 14,
+      "cyclomatic": 31,
       "lcom4": 0
     }
   ]

From 4847ab9f874ee3a1f8032fc3bb1ce8315b809110 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:13:46 -0800
Subject: [PATCH 35/53] Unit tests for call_identifiers

---
 qlty-analysis/src/lang/csharp.rs | 92 +++++++++++++++++++++++++-------
 1 file changed, 72 insertions(+), 20 deletions(-)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index ee52d4339..3c77a3e1b 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -199,34 +199,32 @@ impl Language for CSharp {
     fn call_identifiers(&self, source_file: &File, node: &Node) -> (Option<String>, String) {
         match node.kind() {
             Self::METHOD_INVOCATION => {
-                let (receiver, object) = self.field_identifiers(source_file, node);
-
-                (Some(receiver), object)
+                let function_node = node.child_by_field_name("function");
+                match function_node {
+                    Some(f) => {
+                        if f.kind() == Self::FIELD_ACCESS {
+                            let (obj, property) = self.field_identifiers(source_file, &f);
+                            (Some(obj), property)
+                        } else {
+                            (Some(Self::SELF.to_owned()), get_node_source_or_default(Some(f), source_file))
+                        }
+                    },
+                    None => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string())
+                }
             }
-            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
+            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string())
         }
     }
 
     fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
-        let object_node = node.child_by_field_name("object");
+        let object_node = node.child_by_field_name("expression");
         let property_node = node
-            .child_by_field_name("name")
-            .or_else(|| node.child_by_field_name("field"));
+            .child_by_field_name("name");
 
         match (&object_node, &property_node) {
-            (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
-                let object_source =
-                    get_node_source_or_default(obj.child_by_field_name("field"), source_file);
-                let property_source = get_node_source_or_default(Some(*prop), source_file);
-                (object_source, property_source)
-            }
-            (Some(obj), Some(prop)) => (
-                get_node_source_or_default(Some(*obj), source_file),
-                get_node_source_or_default(Some(*prop), source_file),
-            ),
-            (None, Some(prop)) => (
-                Self::SELF.to_owned(),
-                get_node_source_or_default(Some(*prop), source_file),
+            (Some(o), Some(p)) => (
+                get_node_source_or_default(Some(*o), source_file),
+                get_node_source_or_default(Some(*p), source_file),
             ),
             _ => ("<UNKNOWN>".to_string(), "<UNKNOWN>".to_string()),
         }
@@ -242,3 +240,57 @@ fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String
         .map(|n| node_source(n, source_file))
         .unwrap_or("<UNKNOWN>".to_string())
 }
+
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use tree_sitter::Tree;
+
+    #[test]
+    fn call_identifier() {
+        let source_file = File::from_string("csharp", "foo()");
+        let tree = source_file.parse();
+        let call = root_node(&tree);
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.call_identifiers(&source_file, &call),
+            (Some("this".to_string()), "foo".to_string())
+        );
+    }
+
+    #[test]
+    fn call_member() {
+        let source_file = File::from_string("csharp", "foo.bar()");
+        let tree = source_file.parse();
+        let call = root_node(&tree);
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.call_identifiers(&source_file, &call),
+            (Some("foo".into()), "bar".into())
+        );
+    }
+
+    #[test]
+    fn call_with_custom_context() {
+        let source_file = File::from_string("csharp", "foo.bar(context)");
+        let tree = source_file.parse();
+        let root = root_node(&tree);
+        let call = root.child(0).unwrap();
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.call_identifiers(&source_file, &call),
+            (Some("foo".into()), "bar".into())
+        );
+    }
+
+    // navigates down from "(compilation_unit (global statement ...))"
+    fn root_node(tree: &Tree) -> Node {
+        let root_node = tree.root_node();
+        let expression = root_node.named_child(0).unwrap();
+        expression.named_child(0).unwrap()
+    }
+}
\ No newline at end of file

From 7fd3f1bb1fb39aa7731538539639944a9bbe91db Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:29:25 -0800
Subject: [PATCH 36/53] All call_identifier unit tests

---
 qlty-analysis/src/lang/csharp.rs | 20 ++++++++++++++++++++
 qlty-analysis/src/lang/java.rs   |  1 +
 2 files changed, 21 insertions(+)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index 3c77a3e1b..bacf46a87 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -222,6 +222,12 @@ impl Language for CSharp {
             .child_by_field_name("name");
 
         match (&object_node, &property_node) {
+            (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
+                let object_source =
+                    get_node_source_or_default(obj.child_by_field_name("name"), source_file);
+                let property_source = get_node_source_or_default(Some(*prop), source_file);
+                (object_source, property_source)
+            }
             (Some(o), Some(p)) => (
                 get_node_source_or_default(Some(*o), source_file),
                 get_node_source_or_default(Some(*p), source_file),
@@ -287,6 +293,20 @@ mod test {
         );
     }
 
+    #[test]
+    fn method_call_on_nested_object() {
+        let source_file = File::from_string("csharp", "obj.nestedObj.foo()");
+        let tree = source_file.parse();
+        let root = root_node(&tree);
+        let call = root.child(0).unwrap();
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.call_identifiers(&source_file, &call),
+            (Some("nestedObj".into()), "foo".into())
+        );
+    }
+
     // navigates down from "(compilation_unit (global statement ...))"
     fn root_node(tree: &Tree) -> Node {
         let root_node = tree.root_node();
diff --git a/qlty-analysis/src/lang/java.rs b/qlty-analysis/src/lang/java.rs
index 8ff9aa1db..ec4860850 100644
--- a/qlty-analysis/src/lang/java.rs
+++ b/qlty-analysis/src/lang/java.rs
@@ -366,6 +366,7 @@ mod test {
         let source_file = File::from_string("java", "obj.nestedObj.foo();");
         let tree = source_file.parse();
         let call = call_node(&tree);
+        eprintln!("{:?}", call.to_sexp());
         let language = Java::default();
 
         assert_eq!(

From a7168b3414dc7fb36880d91df27195ee9485a880 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:29:31 -0800
Subject: [PATCH 37/53] qlty fmt

---
 qlty-analysis/src/lang/csharp.rs | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index bacf46a87..3f867ac2e 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -206,20 +206,22 @@ impl Language for CSharp {
                             let (obj, property) = self.field_identifiers(source_file, &f);
                             (Some(obj), property)
                         } else {
-                            (Some(Self::SELF.to_owned()), get_node_source_or_default(Some(f), source_file))
+                            (
+                                Some(Self::SELF.to_owned()),
+                                get_node_source_or_default(Some(f), source_file),
+                            )
                         }
-                    },
-                    None => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string())
+                    }
+                    None => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
                 }
             }
-            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string())
+            _ => (Some("<UNKNOWN>".to_string()), "<UNKNOWN>".to_string()),
         }
     }
 
     fn field_identifiers(&self, source_file: &File, node: &Node) -> (String, String) {
         let object_node = node.child_by_field_name("expression");
-        let property_node = node
-            .child_by_field_name("name");
+        let property_node = node.child_by_field_name("name");
 
         match (&object_node, &property_node) {
             (Some(obj), Some(prop)) if obj.kind() == Self::FIELD_ACCESS => {
@@ -247,7 +249,6 @@ fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String
         .unwrap_or("<UNKNOWN>".to_string())
 }
 
-
 #[cfg(test)]
 mod test {
     use super::*;
@@ -313,4 +314,4 @@ mod test {
         let expression = root_node.named_child(0).unwrap();
         expression.named_child(0).unwrap()
     }
-}
\ No newline at end of file
+}

From a4d7b4ed609ebce0aae11fae6587e28b1a00b63d Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 16:00:34 -0800
Subject: [PATCH 38/53] Field identifier tests

---
 qlty-analysis/src/lang/csharp.rs | 54 ++++++++++++++++++++++++++++++++
 qlty-analysis/src/lang/java.rs   |  1 -
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index 3f867ac2e..75a82e249 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -254,6 +254,47 @@ mod test {
     use super::*;
     use tree_sitter::Tree;
 
+    #[test]
+    fn field_identifier_read() {
+        let source_file = File::from_string("csharp", "this.foo");
+        let tree = source_file.parse();
+        let root_node = root_node(&tree);
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.field_identifiers(&source_file, &root_node),
+            ("this".to_string(), "foo".to_string())
+        );
+    }
+
+    #[test]
+    fn field_identifier_write() {
+        let source_file = File::from_string("csharp", "this.foo = 1");
+        let tree = source_file.parse();
+        let root_node = root_node(&tree);
+        let assignment = root_node.named_child(0).unwrap();
+        let field = assignment.named_child(0).unwrap();
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.field_identifiers(&source_file, &field),
+            ("this".to_string(), "foo".to_string())
+        );
+    }
+
+    #[test]
+    fn field_identifier_collaborator() {
+        let source_file = File::from_string("csharp", "other.foo");
+        let tree = source_file.parse();
+        let root_node = root_node(&tree);
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.field_identifiers(&source_file, &root_node),
+            ("other".to_string(), "foo".to_string())
+        );
+    }
+
     #[test]
     fn call_identifier() {
         let source_file = File::from_string("csharp", "foo()");
@@ -308,6 +349,19 @@ mod test {
         );
     }
 
+    #[test]
+    fn nested_field_access() {
+        let source_file = File::from_string("csharp", "obj.nestedObj.oneMoreObj");
+        let tree = source_file.parse();
+        let root = root_node(&tree);
+        let language = CSharp::default();
+
+        assert_eq!(
+            language.field_identifiers(&source_file, &root),
+            ("nestedObj".to_string(), "oneMoreObj".to_string())
+        );
+    }
+
     // navigates down from "(compilation_unit (global statement ...))"
     fn root_node(tree: &Tree) -> Node {
         let root_node = tree.root_node();
diff --git a/qlty-analysis/src/lang/java.rs b/qlty-analysis/src/lang/java.rs
index ec4860850..8ff9aa1db 100644
--- a/qlty-analysis/src/lang/java.rs
+++ b/qlty-analysis/src/lang/java.rs
@@ -366,7 +366,6 @@ mod test {
         let source_file = File::from_string("java", "obj.nestedObj.foo();");
         let tree = source_file.parse();
         let call = call_node(&tree);
-        eprintln!("{:?}", call.to_sexp());
         let language = Java::default();
 
         assert_eq!(

From bf2d5757266b6c6fd44dcbdd56d98e0b7f9d6635 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 16:15:15 -0800
Subject: [PATCH 39/53] Recursion

---
 .../cognitive}/CountRecursion.cs              |  0
 qlty-cli/tests/lang/csharp/basic.stdout       | 44 ++++++++++++++-----
 2 files changed, 32 insertions(+), 12 deletions(-)
 rename qlty-cli/tests/lang/csharp/{pending => basic.in/cognitive}/CountRecursion.cs (100%)

diff --git a/qlty-cli/tests/lang/csharp/pending/CountRecursion.cs b/qlty-cli/tests/lang/csharp/basic.in/cognitive/CountRecursion.cs
similarity index 100%
rename from qlty-cli/tests/lang/csharp/pending/CountRecursion.cs
rename to qlty-cli/tests/lang/csharp/basic.in/cognitive/CountRecursion.cs
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 50638d015..74ef346b3 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 24,
+    "filesAnalyzed": 25,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -450,7 +450,7 @@
       "blankLines": 18,
       "complexity": 1,
       "cyclomatic": 3,
-      "lcom4": 0
+      "lcom4": 1
     },
     {
       "buildId": "[..]",
@@ -490,7 +490,7 @@
       "blankLines": 9,
       "complexity": 0,
       "cyclomatic": 1,
-      "lcom4": 0
+      "lcom4": 1
     },
     {
       "buildId": "[..]",
@@ -572,6 +572,26 @@
       "cyclomatic": 5,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "CountRecursion.cs",
+      "fullyQualifiedName": "cognitive/CountRecursion.cs",
+      "path": "cognitive/CountRecursion.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 8,
+      "codeLines": 8,
+      "commentLines": 0,
+      "blankLines": 0,
+      "complexity": 1,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -830,7 +850,7 @@
       "blankLines": 2,
       "complexity": 1,
       "cyclomatic": 2,
-      "lcom4": 0
+      "lcom4": 1
     },
     {
       "buildId": "[..]",
@@ -839,16 +859,16 @@
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 4,
-      "classes": 4,
-      "functions": 4,
+      "files": 5,
+      "classes": 5,
+      "functions": 5,
       "fields": 0,
-      "lines": 52,
-      "codeLines": 49,
+      "lines": 60,
+      "codeLines": 57,
       "commentLines": 0,
       "blankLines": 3,
-      "complexity": 14,
-      "cyclomatic": 37,
+      "complexity": 15,
+      "cyclomatic": 38,
       "lcom4": 0
     },
     {
@@ -868,7 +888,7 @@
       "blankLines": 11,
       "complexity": 14,
       "cyclomatic": 31,
-      "lcom4": 0
+      "lcom4": 1
     }
   ]
 }

From d10ecb9f3c55dac5c8f40076e90f63decc0bd372 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 17:11:11 -0800
Subject: [PATCH 40/53] Fields in a class declaration

---
 .../basic.in/fields/ClassDeclaration.cs       | 31 ++++++++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 41 ++++++++++++++++++-
 2 files changed, 71 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/ClassDeclaration.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/ClassDeclaration.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/ClassDeclaration.cs
new file mode 100644
index 000000000..e2662a3ac
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/ClassDeclaration.cs
@@ -0,0 +1,31 @@
+namespace Fields
+{
+    class Foo
+    {
+        public string bar;
+        public string baz;
+
+        // Constructor
+        public Foo()
+        {
+            this.bar = "";
+            this.baz = "";
+        }
+    }
+
+    public class ClassDeclaration
+    {
+        public static void Main(string[] args)
+        {
+            System.Console.WriteLine(DoSomething());
+        }
+
+        public static string DoSomething()
+        {
+            Foo foo = new Foo();
+            foo.bar = "Hello";
+            foo.baz = "World";
+            return foo.bar + foo.baz;
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 74ef346b3..3880070e8 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 25,
+    "filesAnalyzed": 26,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -852,6 +852,26 @@
       "cyclomatic": 2,
       "lcom4": 1
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "ClassDeclaration.cs",
+      "fullyQualifiedName": "fields/ClassDeclaration.cs",
+      "path": "fields/ClassDeclaration.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 2,
+      "functions": 3,
+      "fields": 2,
+      "lines": 31,
+      "codeLines": 24,
+      "commentLines": 1,
+      "blankLines": 6,
+      "complexity": 0,
+      "cyclomatic": 2,
+      "lcom4": 1
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -889,6 +909,25 @@
       "complexity": 14,
       "cyclomatic": 31,
       "lcom4": 1
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "fields",
+      "fullyQualifiedName": "fields",
+      "path": "fields",
+      "kind": "COMPONENT_TYPE_DIRECTORY",
+      "files": 1,
+      "classes": 2,
+      "functions": 3,
+      "fields": 2,
+      "lines": 31,
+      "codeLines": 24,
+      "commentLines": 1,
+      "blankLines": 6,
+      "complexity": 0,
+      "cyclomatic": 2,
+      "lcom4": 1
     }
   ]
 }

From 5d5baffb958e79a683b167548146d425f66155bb Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 17:25:51 -0800
Subject: [PATCH 41/53] Multiple test

---
 .../lang/csharp/basic.in/fields/Multiple.cs   | 15 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 40 ++++++++++++++-----
 2 files changed, 45 insertions(+), 10 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/Multiple.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/Multiple.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/Multiple.cs
new file mode 100644
index 000000000..c091d69fa
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/Multiple.cs
@@ -0,0 +1,15 @@
+namespace Fields
+{
+    public class Multiple
+    {
+        public string bar;
+        public string baz;
+
+        // Constructor
+        public Multiple()
+        {
+            this.bar = "";
+            this.baz = "";
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 3880070e8..d53a9b83f 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 26,
+    "filesAnalyzed": 27,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -872,6 +872,26 @@
       "cyclomatic": 2,
       "lcom4": 1
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Multiple.cs",
+      "fullyQualifiedName": "fields/Multiple.cs",
+      "path": "fields/Multiple.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 2,
+      "lines": 15,
+      "codeLines": 12,
+      "commentLines": 1,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -917,16 +937,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 1,
-      "classes": 2,
-      "functions": 3,
-      "fields": 2,
-      "lines": 31,
-      "codeLines": 24,
-      "commentLines": 1,
-      "blankLines": 6,
+      "files": 2,
+      "classes": 3,
+      "functions": 4,
+      "fields": 4,
+      "lines": 46,
+      "codeLines": 36,
+      "commentLines": 2,
+      "blankLines": 8,
       "complexity": 0,
-      "cyclomatic": 2,
+      "cyclomatic": 3,
       "lcom4": 1
     }
   ]

From b527ec8807c816390224b2f7a7bd58b9a1f3a929 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 17:37:43 -0800
Subject: [PATCH 42/53] Other

---
 .../lang/csharp/basic.in/fields/Other.cs      | 15 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 2 files changed, 44 insertions(+), 9 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/Other.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/Other.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/Other.cs
new file mode 100644
index 000000000..322b7bf99
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/Other.cs
@@ -0,0 +1,15 @@
+namespace Fields
+{
+    public class Other
+    {
+        public int foo;
+        public int bar;
+
+        public static void Main(string[] args)
+        {
+            Other other = new Other();
+            other.foo = 1;
+            int barValue = other.bar;
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index d53a9b83f..20755052a 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 27,
+    "filesAnalyzed": 28,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -892,6 +892,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Other.cs",
+      "fullyQualifiedName": "fields/Other.cs",
+      "path": "fields/Other.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 2,
+      "lines": 15,
+      "codeLines": 13,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -937,16 +957,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 2,
-      "classes": 3,
-      "functions": 4,
-      "fields": 4,
-      "lines": 46,
-      "codeLines": 36,
+      "files": 3,
+      "classes": 4,
+      "functions": 5,
+      "fields": 6,
+      "lines": 61,
+      "codeLines": 49,
       "commentLines": 2,
-      "blankLines": 8,
+      "blankLines": 10,
       "complexity": 0,
-      "cyclomatic": 3,
+      "cyclomatic": 4,
       "lcom4": 1
     }
   ]

From 97c90f2a2a2b6ac91b45206e0067e7e62e2f10b2 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 17:51:47 -0800
Subject: [PATCH 43/53] Read

---
 .../tests/lang/csharp/basic.in/fields/Read.cs | 12 ++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 2 files changed, 41 insertions(+), 9 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/Read.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/Read.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/Read.cs
new file mode 100644
index 000000000..b15e97bb8
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/Read.cs
@@ -0,0 +1,12 @@
+namespace Fields
+{
+    public class Read
+    {
+        string foo = "foo";
+
+        public void Main(string[] args)
+        {
+            System.Console.WriteLine(this.foo);
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 20755052a..67ce512c3 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 28,
+    "filesAnalyzed": 29,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -912,6 +912,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Read.cs",
+      "fullyQualifiedName": "fields/Read.cs",
+      "path": "fields/Read.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 1,
+      "lines": 12,
+      "codeLines": 10,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -957,16 +977,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 3,
-      "classes": 4,
-      "functions": 5,
-      "fields": 6,
-      "lines": 61,
-      "codeLines": 49,
+      "files": 4,
+      "classes": 5,
+      "functions": 6,
+      "fields": 7,
+      "lines": 73,
+      "codeLines": 59,
       "commentLines": 2,
-      "blankLines": 10,
+      "blankLines": 12,
       "complexity": 0,
-      "cyclomatic": 4,
+      "cyclomatic": 5,
       "lcom4": 1
     }
   ]

From 2e1098674ca8e51f85fe62663a3057b76346b8e7 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 19:17:52 -0800
Subject: [PATCH 44/53] Private Field Declaration

---
 .../fields/PrivateFieldDeclaration.cs         | 17 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 40 ++++++++++++++-----
 .../csharp/pending/GetterSetterDeclaration.cs | 19 +++++++++
 .../csharp/pending/PublicFieldDeclaration.cs  |  9 +++++
 .../csharp/pending/StaticFieldDeclaration.cs  |  9 +++++
 qlty-cli/tests/lang/csharp/pending/Unique.cs  | 10 +++++
 qlty-cli/tests/lang/csharp/pending/Write.cs   | 11 +++++
 7 files changed, 105 insertions(+), 10 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/PrivateFieldDeclaration.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/Unique.cs
 create mode 100644 qlty-cli/tests/lang/csharp/pending/Write.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/PrivateFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/PrivateFieldDeclaration.cs
new file mode 100644
index 000000000..d65ae2295
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/PrivateFieldDeclaration.cs
@@ -0,0 +1,17 @@
+namespace Fields
+{
+    public class PrivateFieldDeclaration
+    {
+        private string privateField = "privateValue";
+
+        public string GetPrivateField()
+        {
+            return this.privateField;
+        }
+
+        public static void Main(string[] args)
+        {
+            // Entry point
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 67ce512c3..58af46df6 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 29,
+    "filesAnalyzed": 30,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -912,6 +912,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "PrivateFieldDeclaration.cs",
+      "fullyQualifiedName": "fields/PrivateFieldDeclaration.cs",
+      "path": "fields/PrivateFieldDeclaration.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 2,
+      "fields": 1,
+      "lines": 17,
+      "codeLines": 12,
+      "commentLines": 1,
+      "blankLines": 4,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -977,16 +997,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 4,
-      "classes": 5,
-      "functions": 6,
-      "fields": 7,
-      "lines": 73,
-      "codeLines": 59,
-      "commentLines": 2,
-      "blankLines": 12,
+      "files": 5,
+      "classes": 6,
+      "functions": 8,
+      "fields": 8,
+      "lines": 90,
+      "codeLines": 71,
+      "commentLines": 3,
+      "blankLines": 16,
       "complexity": 0,
-      "cyclomatic": 5,
+      "cyclomatic": 6,
       "lcom4": 1
     }
   ]
diff --git a/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
new file mode 100644
index 000000000..6db56d8e2
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
@@ -0,0 +1,19 @@
+package fields;
+
+public class GetterSetterDeclaration {
+  private String field;
+
+  public String getFieldName() {
+    return this.field;
+  }
+
+  public void setFieldName(String value) {
+    this.field = value;
+  }
+
+  public static void main(String[] args) {
+    GetterSetterDeclaration obj = new GetterSetterDeclaration();
+    obj.setFieldName("Hello");
+    System.out.println(obj.getFieldName());
+  }
+}
diff --git a/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
new file mode 100644
index 000000000..7d3fc0df5
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
@@ -0,0 +1,9 @@
+package fields;
+
+public class PublicFieldDeclaration {
+  String fieldName = "someValue";
+
+  public static void main(String[] args) {
+
+  }
+}
diff --git a/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs
new file mode 100644
index 000000000..cf90b7616
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs
@@ -0,0 +1,9 @@
+package fields;
+
+public class StaticFieldDeclaration {
+  public static String staticField = "staticValue";
+
+  public static void main(String[] args) {
+    System.out.println(StaticFieldDeclaration.staticField);
+  }
+}
diff --git a/qlty-cli/tests/lang/csharp/pending/Unique.cs b/qlty-cli/tests/lang/csharp/pending/Unique.cs
new file mode 100644
index 000000000..f9c142bcb
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/Unique.cs
@@ -0,0 +1,10 @@
+package fields;
+
+public class Unique {
+  public int foo;
+
+  public void main(String[] args) {
+    this.foo = 1;
+    System.out.println(this.foo);
+  }
+}
diff --git a/qlty-cli/tests/lang/csharp/pending/Write.cs b/qlty-cli/tests/lang/csharp/pending/Write.cs
new file mode 100644
index 000000000..ccc96723b
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/pending/Write.cs
@@ -0,0 +1,11 @@
+package fields;
+
+public class Write {
+  public int foo;
+  public int bar;
+
+  public void main(String[] args) {
+    this.foo = 1;
+    this.bar = 2;
+  }
+}

From 55fbeffe01360e844a214e4ebb65d71bfb25a5b8 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 19:21:28 -0800
Subject: [PATCH 45/53] Unique

---
 .../lang/csharp/basic.in/fields/Unique.cs     | 13 +++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 qlty-cli/tests/lang/csharp/pending/Unique.cs  | 10 -----
 3 files changed, 42 insertions(+), 19 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/Unique.cs
 delete mode 100644 qlty-cli/tests/lang/csharp/pending/Unique.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/Unique.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/Unique.cs
new file mode 100644
index 000000000..909a05be0
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/Unique.cs
@@ -0,0 +1,13 @@
+namespace Fields
+{
+    public class Unique
+    {
+        public int foo;
+
+        public void Main(string[] args)
+        {
+            this.foo = 1;
+            System.Console.WriteLine(this.foo);
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 58af46df6..edac3445e 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 30,
+    "filesAnalyzed": 31,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -952,6 +952,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "Unique.cs",
+      "fullyQualifiedName": "fields/Unique.cs",
+      "path": "fields/Unique.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 1,
+      "lines": 13,
+      "codeLines": 11,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -997,16 +1017,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 5,
-      "classes": 6,
-      "functions": 8,
-      "fields": 8,
-      "lines": 90,
-      "codeLines": 71,
+      "files": 6,
+      "classes": 7,
+      "functions": 9,
+      "fields": 9,
+      "lines": 103,
+      "codeLines": 82,
       "commentLines": 3,
-      "blankLines": 16,
+      "blankLines": 18,
       "complexity": 0,
-      "cyclomatic": 6,
+      "cyclomatic": 7,
       "lcom4": 1
     }
   ]
diff --git a/qlty-cli/tests/lang/csharp/pending/Unique.cs b/qlty-cli/tests/lang/csharp/pending/Unique.cs
deleted file mode 100644
index f9c142bcb..000000000
--- a/qlty-cli/tests/lang/csharp/pending/Unique.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-package fields;
-
-public class Unique {
-  public int foo;
-
-  public void main(String[] args) {
-    this.foo = 1;
-    System.out.println(this.foo);
-  }
-}

From 510b487580d2ab577d0ac6916e70f219741d0e2d Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 19:33:15 -0800
Subject: [PATCH 46/53] 3 more field tests

---
 .../basic.in/fields/PublicFieldDeclaration.cs | 12 +++
 .../basic.in/fields/StaticFieldDeclaration.cs | 12 +++
 .../{pending => basic.in/fields}/Write.cs     |  0
 qlty-cli/tests/lang/csharp/basic.stdout       | 92 +++++++++++++++----
 .../csharp/pending/PublicFieldDeclaration.cs  |  9 --
 .../csharp/pending/StaticFieldDeclaration.cs  |  9 --
 6 files changed, 100 insertions(+), 34 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/PublicFieldDeclaration.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/fields/StaticFieldDeclaration.cs
 rename qlty-cli/tests/lang/csharp/{pending => basic.in/fields}/Write.cs (100%)
 delete mode 100644 qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
 delete mode 100644 qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/PublicFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/PublicFieldDeclaration.cs
new file mode 100644
index 000000000..af6cb5d28
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/PublicFieldDeclaration.cs
@@ -0,0 +1,12 @@
+namespace Fields
+{
+    public class PublicFieldDeclaration
+    {
+        public string fieldName = "someValue";
+
+        public static void Main(string[] args)
+        {
+            // Entry point
+        }
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.in/fields/StaticFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/StaticFieldDeclaration.cs
new file mode 100644
index 000000000..1e31b5800
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/StaticFieldDeclaration.cs
@@ -0,0 +1,12 @@
+namespace Fields
+{
+    public class StaticFieldDeclaration
+    {
+        public static string staticField = "staticValue";
+
+        public static void Main(string[] args)
+        {
+            System.Console.WriteLine(StaticFieldDeclaration.staticField);
+        }
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/pending/Write.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/Write.cs
similarity index 100%
rename from qlty-cli/tests/lang/csharp/pending/Write.cs
rename to qlty-cli/tests/lang/csharp/basic.in/fields/Write.cs
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index edac3445e..47ca907e6 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 31,
+    "filesAnalyzed": 34,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -932,6 +932,26 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "PublicFieldDeclaration.cs",
+      "fullyQualifiedName": "fields/PublicFieldDeclaration.cs",
+      "path": "fields/PublicFieldDeclaration.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 1,
+      "lines": 12,
+      "codeLines": 10,
+      "commentLines": 1,
+      "blankLines": 1,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -955,6 +975,26 @@
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
+      "name": "StaticFieldDeclaration.cs",
+      "fullyQualifiedName": "fields/StaticFieldDeclaration.cs",
+      "path": "fields/StaticFieldDeclaration.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 1,
+      "lines": 12,
+      "codeLines": 10,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
+    {
+      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
+      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
       "name": "Unique.cs",
       "fullyQualifiedName": "fields/Unique.cs",
       "path": "fields/Unique.cs",
@@ -973,8 +1013,28 @@
       "lcom4": 0
     },
     {
-      "buildId": "[..]",
-      "analyzedAt": "[..]",
+      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
+      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "name": "Write.cs",
+      "fullyQualifiedName": "fields/Write.cs",
+      "path": "fields/Write.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 2,
+      "lines": 11,
+      "codeLines": 10,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
+    {
+      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
+      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
       "name": "cognitive",
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
@@ -992,8 +1052,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "[..]",
-      "analyzedAt": "[..]",
+      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
+      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
       "name": "cyclomatic",
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
@@ -1011,22 +1071,22 @@
       "lcom4": 1
     },
     {
-      "buildId": "[..]",
-      "analyzedAt": "[..]",
+      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
+      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
       "name": "fields",
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 6,
-      "classes": 7,
-      "functions": 9,
-      "fields": 9,
-      "lines": 103,
-      "codeLines": 82,
-      "commentLines": 3,
-      "blankLines": 18,
+      "files": 9,
+      "classes": 10,
+      "functions": 12,
+      "fields": 13,
+      "lines": 138,
+      "codeLines": 112,
+      "commentLines": 4,
+      "blankLines": 22,
       "complexity": 0,
-      "cyclomatic": 7,
+      "cyclomatic": 10,
       "lcom4": 1
     }
   ]
diff --git a/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
deleted file mode 100644
index 7d3fc0df5..000000000
--- a/qlty-cli/tests/lang/csharp/pending/PublicFieldDeclaration.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-package fields;
-
-public class PublicFieldDeclaration {
-  String fieldName = "someValue";
-
-  public static void main(String[] args) {
-
-  }
-}
diff --git a/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs
deleted file mode 100644
index cf90b7616..000000000
--- a/qlty-cli/tests/lang/csharp/pending/StaticFieldDeclaration.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-package fields;
-
-public class StaticFieldDeclaration {
-  public static String staticField = "staticValue";
-
-  public static void main(String[] args) {
-    System.out.println(StaticFieldDeclaration.staticField);
-  }
-}

From 2bfb5755bff417430c0e432f340fdbfc33f6acbf Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 19:33:32 -0800
Subject: [PATCH 47/53] -

---
 qlty-cli/tests/lang/csharp/basic.stdout | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 47ca907e6..7486a6926 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -1013,8 +1013,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
-      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "Write.cs",
       "fullyQualifiedName": "fields/Write.cs",
       "path": "fields/Write.cs",
@@ -1033,8 +1033,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
-      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "cognitive",
       "fullyQualifiedName": "cognitive",
       "path": "cognitive",
@@ -1052,8 +1052,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
-      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "cyclomatic",
       "fullyQualifiedName": "cyclomatic",
       "path": "cyclomatic",
@@ -1071,8 +1071,8 @@
       "lcom4": 1
     },
     {
-      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
-      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "fields",
       "fullyQualifiedName": "fields",
       "path": "fields",

From 4ddff3fa5c11108eb387203e8ca00d97bbaa197a Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 19:50:46 -0800
Subject: [PATCH 48/53] Add Methods tests

---
 .../basic.in/functions/MethodsWithParams.cs   |  18 +++
 .../functions/MethodsWithoutParams.cs         |  22 ++++
 .../functions/SingletonMethodsWithParams.cs   |  13 +++
 .../SingletonMethodsWithoutParams.cs          |   7 ++
 qlty-cli/tests/lang/csharp/basic.stdout       | 105 +++++++++++++++++-
 5 files changed, 162 insertions(+), 3 deletions(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithParams.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithoutParams.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithParams.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithoutParams.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithParams.cs b/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithParams.cs
new file mode 100644
index 000000000..75d4e81ce
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithParams.cs
@@ -0,0 +1,18 @@
+public class MethodsWithParams
+{
+    private string bar;
+    private string baz;
+
+    public MethodsWithParams()
+    {
+        this.bar = "";
+        this.baz = "";
+    }
+
+    public string DoSomething(string baz, string bar)
+    {
+        this.bar = bar;
+        this.baz = baz;
+        return this.bar + this.baz;
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithoutParams.cs b/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithoutParams.cs
new file mode 100644
index 000000000..2a2602bf9
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/functions/MethodsWithoutParams.cs
@@ -0,0 +1,22 @@
+class MethodsWithOutParams
+{
+    private double width;
+    private double height;
+
+    public MethodsWithOutParams()
+    {
+    }
+
+    public double Area()
+    {
+        return this.width * this.height;
+    }
+
+    public void Foo()
+    {
+    }
+
+    public void Bar()
+    {
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithParams.cs b/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithParams.cs
new file mode 100644
index 000000000..108eab998
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithParams.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+public class SingletonMethodsWithParams
+{
+    public static List<string> Bar(object dog, object cat)
+    {
+        return new List<object> { dog, cat }
+            .Select(animal => animal.ToString())
+            .ToList();
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithoutParams.cs b/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithoutParams.cs
new file mode 100644
index 000000000..fcd4c14cc
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/functions/SingletonMethodsWithoutParams.cs
@@ -0,0 +1,7 @@
+public class SingletonMethodsWithoutParams
+{
+    public static string Bar()
+    {
+        return "bar";
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 7486a6926..230ef3090 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 34,
+    "filesAnalyzed": 38,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -993,8 +993,8 @@
       "lcom4": 0
     },
     {
-      "buildId": "7d9708e2-e7ab-4b9c-8347-d2bef72927b0",
-      "analyzedAt": "2025-01-08T03:31:31.248192+00:00",
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
       "name": "Unique.cs",
       "fullyQualifiedName": "fields/Unique.cs",
       "path": "fields/Unique.cs",
@@ -1032,6 +1032,86 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "MethodsWithParams.cs",
+      "fullyQualifiedName": "functions/MethodsWithParams.cs",
+      "path": "functions/MethodsWithParams.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 2,
+      "fields": 2,
+      "lines": 18,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 4,
+      "complexity": 0,
+      "cyclomatic": 2,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "MethodsWithoutParams.cs",
+      "fullyQualifiedName": "functions/MethodsWithoutParams.cs",
+      "path": "functions/MethodsWithoutParams.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 4,
+      "fields": 2,
+      "lines": 22,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 8,
+      "complexity": 0,
+      "cyclomatic": 2,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "SingletonMethodsWithParams.cs",
+      "fullyQualifiedName": "functions/SingletonMethodsWithParams.cs",
+      "path": "functions/SingletonMethodsWithParams.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 13,
+      "codeLines": 11,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "SingletonMethodsWithoutParams.cs",
+      "fullyQualifiedName": "functions/SingletonMethodsWithoutParams.cs",
+      "path": "functions/SingletonMethodsWithoutParams.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 0,
+      "lines": 7,
+      "codeLines": 6,
+      "commentLines": 0,
+      "blankLines": 1,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -1088,6 +1168,25 @@
       "complexity": 0,
       "cyclomatic": 10,
       "lcom4": 1
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "functions",
+      "fullyQualifiedName": "functions",
+      "path": "functions",
+      "kind": "COMPONENT_TYPE_DIRECTORY",
+      "files": 4,
+      "classes": 4,
+      "functions": 8,
+      "fields": 4,
+      "lines": 60,
+      "codeLines": 45,
+      "commentLines": 0,
+      "blankLines": 15,
+      "complexity": 0,
+      "cyclomatic": 6,
+      "lcom4": 0
     }
   ]
 }

From d5b26228614eb22f9aa4893775e5119791dd4901 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 20:00:03 -0800
Subject: [PATCH 49/53] Add lcom tests

---
 .../tests/lang/csharp/basic.in/lcom/lcom_0.cs |  48 +++++++++
 .../lang/csharp/basic.in/lcom/lcom_1-1.cs     |  18 ++++
 .../tests/lang/csharp/basic.in/lcom/lcom_1.cs |  20 ++++
 .../tests/lang/csharp/basic.in/lcom/lcom_2.cs |  47 ++++++++
 qlty-cli/tests/lang/csharp/basic.stdout       | 101 +++++++++++++++++-
 5 files changed, 233 insertions(+), 1 deletion(-)
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_0.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1-1.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1.cs
 create mode 100644 qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_2.cs

diff --git a/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_0.cs b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_0.cs
new file mode 100644
index 000000000..46fca6b17
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_0.cs
@@ -0,0 +1,48 @@
+// lcom = 0 for all the tests in this file, totalling 0
+
+class Foo
+{
+    private string bar;
+
+    public string foo()
+    {
+        return this.bar;
+    }
+}
+
+class Klass1
+{
+    public Klass1()
+    {
+    }
+
+    public object foo()
+    {
+        return null;
+    }
+}
+
+class Klass2
+{
+    private Bar bar;
+
+    public Klass2(Bar bar)
+    {
+        this.bar = bar;
+    }
+
+    public object foo()
+    {
+        return this.bar.getBaz();
+    }
+}
+
+class Bar
+{
+    private string baz;
+
+    public string getBaz()
+    {
+        return this.baz;
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1-1.cs b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1-1.cs
new file mode 100644
index 000000000..40c0edfa8
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1-1.cs
@@ -0,0 +1,18 @@
+// lcom=1
+
+class Klass
+{
+    public Klass()
+    {
+    }
+
+    public string Foo()
+    {
+        return this.Baz();
+    }
+
+    private string Baz()
+    {
+        return "baz method called";
+    }
+}
diff --git a/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1.cs b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1.cs
new file mode 100644
index 000000000..4573d6d94
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_1.cs
@@ -0,0 +1,20 @@
+// lcom=1
+
+class Klass
+{
+    private string bar;
+
+    public Klass()
+    {
+    }
+
+    public string GetBar()
+    {
+        return this.bar;
+    }
+
+    public string Foo()
+    {
+        return this.GetBar();
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_2.cs b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_2.cs
new file mode 100644
index 000000000..f8274b10d
--- /dev/null
+++ b/qlty-cli/tests/lang/csharp/basic.in/lcom/lcom_2.cs
@@ -0,0 +1,47 @@
+// lcom=2
+
+class KlassA
+{
+    private string aaa;
+    private string bbb;
+
+    public string GetBbb()
+    {
+        return this.bbb;
+    }
+
+    public string GetAaa()
+    {
+        return this.aaa;
+    }
+
+    private string Foo()
+    {
+        return this.GetAaa();
+    }
+
+    private string Bar()
+    {
+        return this.GetBbb();
+    }
+}
+
+class KlassB
+{
+    private string baz;
+
+    public string GetBar()
+    {
+        return this.baz;
+    }
+
+    private string Foo()
+    {
+        return this.GetBar();
+    }
+
+    private string Bar()
+    {
+        return this.GetBar();
+    }
+}
\ No newline at end of file
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index 230ef3090..a85450790 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 38,
+    "filesAnalyzed": 42,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -1112,6 +1112,86 @@
       "cyclomatic": 1,
       "lcom4": 0
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "lcom_0.cs",
+      "fullyQualifiedName": "lcom/lcom_0.cs",
+      "path": "lcom/lcom_0.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 4,
+      "functions": 6,
+      "fields": 2,
+      "lines": 48,
+      "codeLines": 33,
+      "commentLines": 0,
+      "blankLines": 15,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "lcom_1-1.cs",
+      "fullyQualifiedName": "lcom/lcom_1-1.cs",
+      "path": "lcom/lcom_1-1.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 3,
+      "fields": 0,
+      "lines": 18,
+      "codeLines": 13,
+      "commentLines": 0,
+      "blankLines": 5,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 1
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "lcom_1.cs",
+      "fullyQualifiedName": "lcom/lcom_1.cs",
+      "path": "lcom/lcom_1.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 3,
+      "fields": 1,
+      "lines": 20,
+      "codeLines": 13,
+      "commentLines": 0,
+      "blankLines": 7,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 1
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "lcom_2.cs",
+      "fullyQualifiedName": "lcom/lcom_2.cs",
+      "path": "lcom/lcom_2.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 2,
+      "functions": 7,
+      "fields": 3,
+      "lines": 47,
+      "codeLines": 31,
+      "commentLines": 0,
+      "blankLines": 16,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 2
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -1187,6 +1267,25 @@
       "complexity": 0,
       "cyclomatic": 6,
       "lcom4": 0
+    },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "lcom",
+      "fullyQualifiedName": "lcom",
+      "path": "lcom",
+      "kind": "COMPONENT_TYPE_DIRECTORY",
+      "files": 4,
+      "classes": 8,
+      "functions": 19,
+      "fields": 6,
+      "lines": 133,
+      "codeLines": 90,
+      "commentLines": 0,
+      "blankLines": 43,
+      "complexity": 0,
+      "cyclomatic": 4,
+      "lcom4": 4
     }
   ]
 }

From 0cc33420439d65b3324b11458372fc3423af2467 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Tue, 7 Jan 2025 20:03:24 -0800
Subject: [PATCH 50/53] Add real pending test

---
 qlty-cli/tests/lang.rs                        |  2 +-
 .../csharp/pending/GetterSetterDeclaration.cs | 33 ++++++++-----------
 2 files changed, 15 insertions(+), 20 deletions(-)

diff --git a/qlty-cli/tests/lang.rs b/qlty-cli/tests/lang.rs
index 8b6249777..21ec20f8c 100644
--- a/qlty-cli/tests/lang.rs
+++ b/qlty-cli/tests/lang.rs
@@ -51,6 +51,6 @@ fn go_tests() {
 }
 
 #[test]
-fn c_sharp_tests() {
+fn csharp_tests() {
     setup_and_run_test_cases("tests/lang/csharp/**/*.toml");
 }
diff --git a/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs b/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
index 6db56d8e2..73bdbff58 100644
--- a/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
+++ b/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
@@ -1,19 +1,14 @@
-package fields;
-
-public class GetterSetterDeclaration {
-  private String field;
-
-  public String getFieldName() {
-    return this.field;
-  }
-
-  public void setFieldName(String value) {
-    this.field = value;
-  }
-
-  public static void main(String[] args) {
-    GetterSetterDeclaration obj = new GetterSetterDeclaration();
-    obj.setFieldName("Hello");
-    System.out.println(obj.getFieldName());
-  }
-}
+namespace Fields
+{
+    public class GetterSetterDeclaration
+    {
+        public string Field { get; set; }
+
+        public static void Main(string[] args)
+        {
+            GetterSetterDeclaration obj = new GetterSetterDeclaration();
+            obj.Field = "Hello";
+            System.Console.WriteLine(obj.Field);
+        }
+    }
+}
\ No newline at end of file

From c7fb1e7385184108d767940decd7a4c2863cba48 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Wed, 8 Jan 2025 10:24:25 -0800
Subject: [PATCH 51/53] Count properties as fields

---
 qlty-analysis/src/lang/csharp.rs              | 11 +++---
 .../fields}/GetterSetterDeclaration.cs        |  2 +
 qlty-cli/tests/lang/csharp/basic.stdout       | 38 ++++++++++++++-----
 3 files changed, 37 insertions(+), 14 deletions(-)
 rename qlty-cli/tests/lang/csharp/{pending => basic.in/fields}/GetterSetterDeclaration.cs (83%)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index 75a82e249..7490197cd 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -27,11 +27,12 @@ const FUNCTION_DECLARATION_QUERY: &str = r#"
 "#;
 
 const FIELD_QUERY: &str = r#"
-  (field_declaration 
-    (variable_declaration 
-      (variable_declarator name: (identifier) @name)
-    )
-  ) @field
+    [(field_declaration
+        (variable_declaration 
+        (variable_declarator name: (identifier) @name)
+        )
+    ) @field
+    (property_declaration name: (identifier) @name) @field]
 "#;
 
 pub struct CSharp {
diff --git a/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs b/qlty-cli/tests/lang/csharp/basic.in/fields/GetterSetterDeclaration.cs
similarity index 83%
rename from qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
rename to qlty-cli/tests/lang/csharp/basic.in/fields/GetterSetterDeclaration.cs
index 73bdbff58..02eb16bf2 100644
--- a/qlty-cli/tests/lang/csharp/pending/GetterSetterDeclaration.cs
+++ b/qlty-cli/tests/lang/csharp/basic.in/fields/GetterSetterDeclaration.cs
@@ -3,11 +3,13 @@ namespace Fields
     public class GetterSetterDeclaration
     {
         public string Field { get; set; }
+        public string _field2
 
         public static void Main(string[] args)
         {
             GetterSetterDeclaration obj = new GetterSetterDeclaration();
             obj.Field = "Hello";
+            obj._field2 = "World";
             System.Console.WriteLine(obj.Field);
         }
     }
diff --git a/qlty-cli/tests/lang/csharp/basic.stdout b/qlty-cli/tests/lang/csharp/basic.stdout
index a85450790..9eac2aa3c 100644
--- a/qlty-cli/tests/lang/csharp/basic.stdout
+++ b/qlty-cli/tests/lang/csharp/basic.stdout
@@ -2,7 +2,7 @@
   "metadata": {
     "buildId": "[..]",
     "result": "ANALYSIS_RESULT_SUCCESS",
-    "filesAnalyzed": 42,
+    "filesAnalyzed": 43,
     "startTime": "[..]",
     "finishTime": "[..]",
     "commitMessage": "initial/n",
@@ -872,6 +872,26 @@
       "cyclomatic": 2,
       "lcom4": 1
     },
+    {
+      "buildId": "[..]",
+      "analyzedAt": "[..]",
+      "name": "GetterSetterDeclaration.cs",
+      "fullyQualifiedName": "fields/GetterSetterDeclaration.cs",
+      "path": "fields/GetterSetterDeclaration.cs",
+      "kind": "COMPONENT_TYPE_FILE",
+      "language": "LANGUAGE_C_SHARP",
+      "files": 1,
+      "classes": 1,
+      "functions": 1,
+      "fields": 2,
+      "lines": 16,
+      "codeLines": 14,
+      "commentLines": 0,
+      "blankLines": 2,
+      "complexity": 0,
+      "cyclomatic": 1,
+      "lcom4": 0
+    },
     {
       "buildId": "[..]",
       "analyzedAt": "[..]",
@@ -1237,16 +1257,16 @@
       "fullyQualifiedName": "fields",
       "path": "fields",
       "kind": "COMPONENT_TYPE_DIRECTORY",
-      "files": 9,
-      "classes": 10,
-      "functions": 12,
-      "fields": 13,
-      "lines": 138,
-      "codeLines": 112,
+      "files": 10,
+      "classes": 11,
+      "functions": 13,
+      "fields": 15,
+      "lines": 154,
+      "codeLines": 126,
       "commentLines": 4,
-      "blankLines": 22,
+      "blankLines": 24,
       "complexity": 0,
-      "cyclomatic": 10,
+      "cyclomatic": 11,
       "lcom4": 1
     },
     {

From c4216d7314bfddc09292198e144952c916ee38e1 Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Wed, 8 Jan 2025 11:30:39 -0800
Subject: [PATCH 52/53] More fully fleshed C# node types

---
 qlty-analysis/src/lang/csharp.rs | 39 +++++++++++++++++++++++++++++---
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index 7490197cd..d16bec251 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -55,8 +55,11 @@ impl CSharp {
     pub const FIELD_ACCESS: &'static str = "member_access_expression";
     pub const FOR: &'static str = "for_statement";
     pub const FOREACH: &'static str = "foreach_statement";
+    pub const GOTO: &'static str = "goto_statement";
+    pub const INTERPOLATED_STRING: &'static str = "interpolated_string_expression";
     pub const METHOD_DECLARATION: &'static str = "method_declaration";
     pub const METHOD_INVOCATION: &'static str = "invocation_expression";
+    pub const PROPERTY_DECLARATION: &'static str = "property_declaration";
     pub const IDENTIFIER: &'static str = "identifier";
     pub const IF: &'static str = "if_statement";
     pub const LAMBDA: &'static str = "lambda_expression";
@@ -150,7 +153,7 @@ impl Language for CSharp {
     }
 
     fn jump_nodes(&self) -> Vec<&str> {
-        vec![Self::BREAK, Self::CONTINUE]
+        vec![Self::BREAK, Self::CONTINUE, Self::GOTO]
     }
 
     fn return_nodes(&self) -> Vec<&str> {
@@ -166,7 +169,7 @@ impl Language for CSharp {
     }
 
     fn field_nodes(&self) -> Vec<&str> {
-        vec![Self::FIELD_DECLARATION]
+        vec![Self::FIELD_DECLARATION, Self::PROPERTY_DECLARATION]
     }
 
     fn call_nodes(&self) -> Vec<&str> {
@@ -186,7 +189,7 @@ impl Language for CSharp {
     }
 
     fn string_nodes(&self) -> Vec<&str> {
-        vec![Self::STRING]
+        vec![Self::STRING, Self::INTERPOLATED_STRING]
     }
 
     fn is_jump_label(&self, node: &Node) -> bool {
@@ -254,6 +257,36 @@ fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String
 mod test {
     use super::*;
     use tree_sitter::Tree;
+    use std::collections::HashSet;
+
+    #[test]
+    fn mutually_exclusive() {
+        let lang = CSharp::default();
+        let mut kinds: Vec<&str> = vec![];
+
+        kinds.extend(lang.if_nodes());
+        kinds.extend(lang.conditional_assignment_nodes());
+        kinds.extend(lang.switch_nodes());
+        kinds.extend(lang.case_nodes());
+        kinds.extend(lang.ternary_nodes());
+        kinds.extend(lang.loop_nodes());
+        kinds.extend(lang.except_nodes());
+        kinds.extend(lang.try_expression_nodes());
+        kinds.extend(lang.jump_nodes());
+        kinds.extend(lang.return_nodes());
+        kinds.extend(lang.binary_nodes());
+        kinds.extend(lang.field_nodes());
+        kinds.extend(lang.call_nodes());
+        kinds.extend(lang.function_nodes());
+        kinds.extend(lang.closure_nodes());
+        kinds.extend(lang.comment_nodes());
+        kinds.extend(lang.string_nodes());
+        kinds.extend(lang.boolean_operator_nodes());
+        kinds.extend(lang.block_nodes());
+
+        let unique: HashSet<_> = kinds.iter().cloned().collect();
+        assert_eq!(unique.len(), kinds.len());
+    }
 
     #[test]
     fn field_identifier_read() {

From 05c65b626c9ac11c1ffe23c0df3737aee32d081e Mon Sep 17 00:00:00 2001
From: Noah Davis <11672+noahd1@users.noreply.github.com>
Date: Wed, 8 Jan 2025 11:30:42 -0800
Subject: [PATCH 53/53] qlty fmt

---
 qlty-analysis/src/lang/csharp.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qlty-analysis/src/lang/csharp.rs b/qlty-analysis/src/lang/csharp.rs
index d16bec251..b4389a3d8 100644
--- a/qlty-analysis/src/lang/csharp.rs
+++ b/qlty-analysis/src/lang/csharp.rs
@@ -256,8 +256,8 @@ fn get_node_source_or_default(node: Option<Node>, source_file: &File) -> String
 #[cfg(test)]
 mod test {
     use super::*;
-    use tree_sitter::Tree;
     use std::collections::HashSet;
+    use tree_sitter::Tree;
 
     #[test]
     fn mutually_exclusive() {