#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use serde::Serialize;
use serde::ser::{SerializeStruct, Serializer};
use std::fmt;
use crate::checker::Checker;
use crate::langs::*;
use crate::macros::{csharp_var_decl_kinds, csharp_var_declarator_kinds, implement_metric_trait};
use crate::node::Node;
use crate::*;
#[derive(Clone, Debug, Default)]
pub struct Stats {
class_npa: usize,
interface_npa: usize,
class_na: usize,
interface_na: usize,
class_npa_sum: usize,
interface_npa_sum: usize,
class_na_sum: usize,
interface_na_sum: usize,
is_class_space: bool,
}
impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut st = serializer.serialize_struct("npa", 9)?;
st.serialize_field("classes", &self.class_npa_sum())?;
st.serialize_field("interfaces", &self.interface_npa_sum())?;
st.serialize_field("class_attributes", &self.class_na_sum())?;
st.serialize_field("interface_attributes", &self.interface_na_sum())?;
st.serialize_field("classes_average", &self.class_cda())?;
st.serialize_field("interfaces_average", &self.interface_cda())?;
st.serialize_field("total", &self.total_npa())?;
st.serialize_field("total_attributes", &self.total_na())?;
st.serialize_field("average", &self.total_cda())?;
st.end()
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"classes: {}, interfaces: {}, class_attributes: {}, interface_attributes: {}, classes_average: {}, interfaces_average: {}, total: {}, total_attributes: {}, average: {}",
self.class_npa_sum(),
self.interface_npa_sum(),
self.class_na_sum(),
self.interface_na_sum(),
self.class_cda(),
self.interface_cda(),
self.total_npa(),
self.total_na(),
self.total_cda()
)
}
}
impl Stats {
pub fn merge(&mut self, other: &Stats) {
self.class_npa_sum += other.class_npa_sum;
self.interface_npa_sum += other.interface_npa_sum;
self.class_na_sum += other.class_na_sum;
self.interface_na_sum += other.interface_na_sum;
}
#[inline]
#[must_use]
pub fn class_npa(&self) -> f64 {
self.class_npa as f64
}
#[inline]
#[must_use]
pub fn interface_npa(&self) -> f64 {
self.interface_npa as f64
}
#[inline]
#[must_use]
pub fn class_na(&self) -> f64 {
self.class_na as f64
}
#[inline]
#[must_use]
pub fn interface_na(&self) -> f64 {
self.interface_na as f64
}
#[inline]
#[must_use]
pub fn class_npa_sum(&self) -> f64 {
self.class_npa_sum as f64
}
#[inline]
#[must_use]
pub fn interface_npa_sum(&self) -> f64 {
self.interface_npa_sum as f64
}
#[inline]
#[must_use]
pub fn class_na_sum(&self) -> f64 {
self.class_na_sum as f64
}
#[inline]
#[must_use]
pub fn interface_na_sum(&self) -> f64 {
self.interface_na_sum as f64
}
#[inline]
#[must_use]
pub fn class_cda(&self) -> f64 {
self.class_npa_sum() / self.class_na_sum as f64
}
#[inline]
#[must_use]
pub fn interface_cda(&self) -> f64 {
if self.interface_npa_sum == self.interface_na_sum && self.interface_npa_sum != 0 {
1.0
} else {
self.interface_npa_sum() / self.interface_na_sum()
}
}
#[inline]
#[must_use]
pub fn total_cda(&self) -> f64 {
self.total_npa() / self.total_na()
}
#[inline]
#[must_use]
pub fn total_npa(&self) -> f64 {
self.class_npa_sum() + self.interface_npa_sum()
}
#[inline]
#[must_use]
pub fn total_na(&self) -> f64 {
self.class_na_sum() + self.interface_na_sum()
}
#[inline]
pub(crate) fn compute_sum(&mut self) {
self.class_npa_sum += self.class_npa;
self.interface_npa_sum += self.interface_npa;
self.class_na_sum += self.class_na;
self.interface_na_sum += self.interface_na;
}
#[inline]
pub(crate) fn is_disabled(&self) -> bool {
!self.is_class_space
}
}
#[doc(hidden)]
pub trait Npa
where
Self: Checker,
{
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats);
}
macro_rules! impl_npa_java_like {
($code:ty, $lang:ident) => {
impl Npa for $code {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
ClassBody | EnumBodyDeclarations => {
for declaration in node
.children()
.filter(|n| matches!(n.kind_id().into(), FieldDeclaration))
{
let attributes = declaration
.children()
.filter(|n| matches!(n.kind_id().into(), VariableDeclarator))
.count();
stats.class_na += attributes;
if declaration.child(0).is_some_and(|modifiers| {
matches!(modifiers.kind_id().into(), Modifiers)
&& modifiers.first_child(|id| id == Public).is_some()
}) {
stats.class_npa += attributes;
}
}
}
InterfaceBody | AnnotationTypeBody => {
stats.interface_na += node
.children()
.filter(|n| matches!(n.kind_id().into(), ConstantDeclaration))
.flat_map(|n| n.children())
.filter(|n| matches!(n.kind_id().into(), VariableDeclarator))
.count();
stats.interface_npa = stats.interface_na;
}
_ => {}
}
}
}
};
}
impl_npa_java_like!(JavaCode, Java);
impl Npa for GroovyCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Groovy::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
ClassBody | EnumBody => {
let is_interface_like = groovy_body_is_interface_like(node);
for declaration in node
.children()
.filter(|n| matches!(n.kind_id().into(), FieldDeclaration))
{
let attributes = declaration
.children()
.filter(|n| matches!(n.kind_id().into(), VariableDeclarator))
.count();
if is_interface_like {
stats.interface_na += attributes;
stats.interface_npa += attributes;
} else {
stats.class_na += attributes;
if groovy_has_explicit_public(&declaration) {
stats.class_npa += attributes;
}
}
}
}
_ => {}
}
}
}
pub(crate) fn groovy_body_is_interface_like(body: &Node) -> bool {
use Groovy::*;
body.parent().is_some_and(|p| {
matches!(
p.kind_id().into(),
InterfaceDeclaration | TraitDeclaration | AnnotationTypeDeclaration
)
})
}
pub(crate) fn groovy_has_explicit_public(declaration: &Node) -> bool {
declaration.first_child(|id| id == Groovy::Public).is_some()
}
pub(crate) fn csharp_is_explicit_public(declaration: &Node) -> bool {
declaration.children().any(|child| {
matches!(child.kind_id().into(), Csharp::Modifier)
&& child.first_child(|id| id == Csharp::Public).is_some()
})
}
impl Npa for CsharpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Csharp::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
if !matches!(node.kind_id().into(), DeclarationList) {
return;
}
let Some(parent_kind) = node.parent().map(|p| p.kind_id().into()) else {
return;
};
match parent_kind {
ClassDeclaration | StructDeclaration | RecordDeclaration => {
for declaration in node
.children()
.filter(|c| matches!(c.kind_id().into(), FieldDeclaration))
{
let attributes = csharp_count_field_declarators(&declaration);
stats.class_na += attributes;
if csharp_is_explicit_public(&declaration) {
stats.class_npa += attributes;
}
}
}
InterfaceDeclaration => {
for declaration in node
.children()
.filter(|c| matches!(c.kind_id().into(), FieldDeclaration))
{
let attributes = csharp_count_field_declarators(&declaration);
stats.interface_na += attributes;
stats.interface_npa = stats.interface_na;
}
}
_ => {}
}
}
}
fn csharp_count_field_declarators(field_decl: &Node) -> usize {
field_decl
.children()
.filter(|c| matches!(c.kind_id().into(), csharp_var_decl_kinds!()))
.flat_map(|c| c.children())
.filter(|c| matches!(c.kind_id().into(), csharp_var_declarator_kinds!()))
.count()
}
pub(crate) fn php_is_explicit_public(declaration: &Node) -> bool {
declaration.children().any(|child| {
matches!(child.kind_id().into(), Php::VisibilityModifier)
&& child.first_child(|id| id == Php::Public).is_some()
})
}
pub(crate) fn ruby_attr_macro_symbol_count(call: &Node) -> usize {
use Ruby::*;
call.children()
.find(|c| matches!(c.kind_id().into(), ArgumentList | ArgumentList2))
.map_or(0, |args| {
args.children()
.filter(|c| {
matches!(
c.kind_id().into(),
SimpleSymbol | DelimitedSymbol | HashKeySymbol | BareSymbol
)
})
.count()
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum RubyVisibility {
Public,
Private,
Protected,
}
pub(crate) fn ruby_visibility_marker(node: &Node, source: &[u8]) -> Option<RubyVisibility> {
if !matches!(node.kind_id().into(), Ruby::Identifier) {
return None;
}
match node.utf8_text(source)? {
"private" => Some(RubyVisibility::Private),
"public" => Some(RubyVisibility::Public),
"protected" => Some(RubyVisibility::Protected),
_ => None,
}
}
pub(crate) fn ruby_attr_macro_name(call: &Node, source: &[u8]) -> Option<&'static str> {
let ident = call
.children()
.find(|c| matches!(c.kind_id().into(), Ruby::Identifier))?;
match ident.utf8_text(source)? {
"attr_accessor" => Some("attr_accessor"),
"attr_reader" => Some("attr_reader"),
"attr_writer" => Some("attr_writer"),
_ => None,
}
}
pub(crate) fn ruby_walk_class_body(body: &Node, source: &[u8], stats: &mut Stats) {
use Ruby::*;
let mut visibility = RubyVisibility::Public;
for child in body.children() {
if let Some(marker) = ruby_visibility_marker(&child, source) {
visibility = marker;
continue;
}
match child.kind_id().into() {
Assignment | Assignment2 => {
let Some(lhs) = child.children().next() else {
continue;
};
if matches!(lhs.kind_id().into(), InstanceVariable | ClassVariable) {
stats.class_na += 1;
if visibility == RubyVisibility::Public {
stats.class_npa += 1;
}
}
}
Call | Call2 | Call3 | Call4 if ruby_attr_macro_name(&child, source).is_some() => {
let count = ruby_attr_macro_symbol_count(&child);
stats.class_na += count;
if visibility == RubyVisibility::Public {
stats.class_npa += count;
}
}
_ => {}
}
}
}
impl Npa for RubyCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use Ruby::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
if !matches!(node.kind_id().into(), BodyStatement | BodyStatement2) {
return;
}
let Some(parent_kind) = node.parent().map(|p| p.kind_id().into()) else {
return;
};
if !matches!(parent_kind, Class | SingletonClass) {
return;
}
ruby_walk_class_body(node, code, stats);
}
}
impl Npa for PhpCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Php::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
DeclarationList => {
let Some(parent_kind) = node.parent().map(|p| p.kind_id().into()) else {
return;
};
match parent_kind {
ClassDeclaration | TraitDeclaration | AnonymousClass => {
for declaration in node
.children()
.filter(|c| matches!(c.kind_id().into(), PropertyDeclaration))
{
let attributes = declaration
.children()
.filter(|c| matches!(c.kind_id().into(), PropertyElement))
.count();
stats.class_na += attributes;
if php_is_explicit_public(&declaration) {
stats.class_npa += attributes;
}
}
}
InterfaceDeclaration => {
let count: usize = node
.children()
.filter(|c| {
matches!(c.kind_id().into(), ConstDeclaration | ConstDeclaration2)
})
.map(|decl| {
decl.children()
.filter(|n| {
matches!(n.kind_id().into(), ConstElement | ConstElement2)
})
.count()
})
.sum();
stats.interface_na += count;
stats.interface_npa = stats.interface_na;
}
_ => {}
}
}
EnumDeclarationList => {
let count = node
.children()
.filter(|c| matches!(c.kind_id().into(), EnumCase))
.count();
stats.class_na += count;
stats.class_npa += count;
}
_ => {}
}
}
}
impl Npa for PythonCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use Python::*;
if !matches!(node.kind_id().into(), ClassDefinition) {
return;
}
if stats.is_disabled() {
stats.is_class_space = true;
}
let Some(body) = python_class_body(node) else {
return;
};
let class_level = python_count_class_level_attrs(&body);
let self_attrs = python_count_unique_self_attrs(&body, code);
let total = class_level + self_attrs;
stats.class_na += total;
stats.class_npa += total;
}
}
impl Npa for RustCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Rust::*;
if matches!(node.kind_id().into(), ImplItem | TraitItem) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
StructItem => {
let mut attrs = 0;
let mut public_attrs = 0;
for body in node.children() {
match body.kind_id().into() {
FieldDeclarationList => {
for field in body
.children()
.filter(|c| matches!(c.kind_id().into(), FieldDeclaration))
{
attrs += 1;
if rust_item_is_public(&field) {
public_attrs += 1;
}
}
}
OrderedFieldDeclarationList => {
let (count, public) = rust_count_tuple_struct_fields(&body);
attrs += count;
public_attrs += public;
}
_ => {}
}
}
if attrs > 0 {
if stats.is_disabled() {
stats.is_class_space = true;
}
stats.class_na += attrs;
stats.class_npa += public_attrs;
}
}
ConstItem | StaticItem => {
let Some(parent) = node.parent() else {
return;
};
let Some(grand) = parent.parent() else {
return;
};
match grand.kind_id().into() {
ImplItem if matches!(parent.kind_id().into(), DeclarationList) => {
stats.class_na += 1;
if rust_item_is_public(node) {
stats.class_npa += 1;
}
}
TraitItem if matches!(parent.kind_id().into(), DeclarationList) => {
stats.interface_na += 1;
stats.interface_npa = stats.interface_na;
}
_ => {}
}
}
AssociatedType => {
let Some(parent) = node.parent() else {
return;
};
let Some(grand) = parent.parent() else {
return;
};
if matches!(grand.kind_id().into(), TraitItem)
&& matches!(parent.kind_id().into(), DeclarationList)
{
stats.interface_na += 1;
stats.interface_npa = stats.interface_na;
}
}
_ => {}
}
}
}
impl Npa for GoCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Go as G;
if !matches!(node.kind_id().into(), G::StructType) {
return;
}
let Some(body) = node
.children()
.find(|c| matches!(c.kind_id().into(), G::FieldDeclarationList))
else {
return;
};
let attrs = body
.children()
.filter(|c| matches!(c.kind_id().into(), G::FieldDeclaration))
.count();
if attrs == 0 {
return;
}
if stats.is_disabled() {
stats.is_class_space = true;
}
stats.class_na += attrs;
stats.class_npa += attrs;
}
}
impl Npa for CppCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Cpp::*;
if matches!(node.kind_id().into(), ClassSpecifier | StructSpecifier) && stats.is_disabled()
{
stats.is_class_space = true;
}
if !matches!(node.kind_id().into(), FieldDeclarationList) {
return;
}
let Some(parent) = node.parent() else {
return;
};
let mut current_is_public = match parent.kind_id().into() {
ClassSpecifier => false,
StructSpecifier => true,
_ => return,
};
for child in node.children() {
match child.kind_id().into() {
AccessSpecifier => {
current_is_public = child
.first_child(|id| {
id == Cpp::Public || id == Cpp::Protected || id == Cpp::Private
})
.is_some_and(|tok| tok.kind_id() == Cpp::Public);
}
FieldDeclaration => {
if cpp_has_function_declarator(&child) {
continue;
}
let count = cpp_count_field_identifiers(&child);
stats.class_na += count;
if current_is_public {
stats.class_npa += count;
}
}
_ => {}
}
}
}
}
pub(crate) fn cpp_has_function_declarator(node: &Node) -> bool {
use Cpp::*;
node.children().any(|child| match child.kind_id().into() {
FunctionDeclarator | FunctionDeclarator2 | FunctionDeclarator3 => true,
PointerDeclarator | PointerDeclarator2 | ReferenceDeclarator | ReferenceDeclarator2
| ReferenceDeclarator3 | ReferenceDeclarator4 | Declaration | Declaration2
| Declaration3 | Declaration4 => cpp_has_function_declarator(&child),
_ => false,
})
}
pub(crate) fn cpp_count_field_identifiers(node: &Node) -> usize {
use Cpp::*;
let mut count = 0;
for child in node.children() {
match child.kind_id().into() {
FieldIdentifier => count += 1,
PointerDeclarator | PointerDeclarator2 | ArrayDeclarator | ArrayDeclarator2
| ArrayDeclarator3 | InitDeclarator | ReferenceDeclarator | ReferenceDeclarator2
| ReferenceDeclarator3 | ReferenceDeclarator4 => {
count += cpp_count_field_identifiers(&child);
}
_ => {}
}
}
count
}
fn rust_count_tuple_struct_fields(list: &Node) -> (usize, usize) {
use Rust::*;
let mut total = 0;
let mut public = 0;
let mut pending_pub = false;
for child in list.children() {
match child.kind_id().into() {
LPAREN | RPAREN | COMMA => {
pending_pub = false;
}
VisibilityModifier => {
pending_pub = true;
}
AttributeItem | LineComment | BlockComment => {}
_ => {
total += 1;
if pending_pub {
public += 1;
}
pending_pub = false;
}
}
}
(total, public)
}
pub(crate) fn pattern_is_bare_underscore(pat: &Node, underscore_id: u16) -> bool {
let mut found_underscore = false;
for child in pat.children() {
if child.kind_id() == underscore_id {
if found_underscore {
return false;
}
found_underscore = true;
} else if child.is_named() {
return false;
}
}
found_underscore
}
pub(crate) fn python_case_clause_counts(node: &Node, underscore_id: u16) -> bool {
let mut bare_underscore = false;
for child in node.children() {
match child.kind_id().into() {
Python::IfClause => return true,
Python::CasePattern => {
bare_underscore = pattern_is_bare_underscore(&child, underscore_id);
if !bare_underscore {
return true;
}
}
_ => {}
}
}
!bare_underscore
}
pub(crate) fn rust_item_is_public(node: &Node) -> bool {
node.children()
.any(|c| c.kind_id() == Rust::VisibilityModifier)
}
fn python_class_body<'a>(class_def: &Node<'a>) -> Option<Node<'a>> {
class_def.children().find(|c| c.kind_id() == Python::Block2)
}
fn python_count_class_level_attrs(body: &Node) -> usize {
use Python::*;
let mut count = 0_usize;
for stmt in body.children() {
if stmt.kind_id() != ExpressionStatement {
continue;
}
for child in stmt.children() {
if child.kind_id() == Assignment && child.first_child(|id| id == EQ).is_some() {
count += 1;
}
}
}
count
}
fn python_count_unique_self_attrs(body: &Node, code: &[u8]) -> usize {
let mut seen: std::collections::HashSet<&[u8]> = std::collections::HashSet::with_capacity(8);
for stmt in body.children() {
if let Some(func) = python_unwrap_function(&stmt) {
python_collect_self_attrs_in_subtree(&func, code, &mut seen);
}
}
seen.len()
}
fn python_collect_self_attrs_in_subtree<'a>(
root: &Node<'a>,
code: &'a [u8],
seen: &mut std::collections::HashSet<&'a [u8]>,
) {
use Python::*;
let mut stack: Vec<Node<'a>> = Vec::with_capacity(32);
for child in root.children() {
stack.push(child);
}
while let Some(node) = stack.pop() {
if matches!(
node.kind_id().into(),
FunctionDefinition | ClassDefinition | DecoratedDefinition | Lambda
) {
continue;
}
if node.kind_id() == Assignment
&& python_lhs_is_self_attribute(&node)
&& let Some(name) = python_self_attr_name_bytes(&node, code)
{
seen.insert(name);
}
for child in node.children() {
stack.push(child);
}
}
}
fn python_self_attr_name_bytes<'a>(assignment: &Node<'a>, code: &'a [u8]) -> Option<&'a [u8]> {
let target = assignment.child(0)?;
if target.kind_id() != Python::Attribute {
return None;
}
let id = target
.children()
.filter(|c| c.kind_id() == Python::Identifier)
.last()?;
let receiver = target.child(0)?;
if id.start_byte() == receiver.start_byte() {
return None;
}
code.get(id.start_byte()..id.end_byte())
}
fn python_unwrap_function<'a>(node: &Node<'a>) -> Option<Node<'a>> {
match node.kind_id().into() {
Python::FunctionDefinition => Some(*node),
Python::DecoratedDefinition => node
.children()
.find(|c| c.kind_id() == Python::FunctionDefinition),
_ => None,
}
}
fn python_lhs_is_self_attribute(assignment: &Node) -> bool {
use Python::*;
let Some(target) = assignment.child(0) else {
return false;
};
if target.kind_id() != Attribute {
return false;
}
target.child(0).is_some_and(|c| c.kind_id() == Identifier)
}
pub(crate) fn kotlin_class_body_is_interface(class_body: &Node) -> bool {
class_body.parent().is_some_and(|p| {
matches!(p.kind_id().into(), Kotlin::ClassDeclaration)
&& p.first_child(|id| id == Kotlin::Interface).is_some()
})
}
fn kotlin_count_property_attrs(decl: &Node) -> usize {
use Kotlin::*;
decl.children()
.map(|c| match c.kind_id().into() {
VariableDeclaration => 1,
MultiVariableDeclaration => c
.children()
.filter(|n| matches!(n.kind_id().into(), VariableDeclaration))
.count(),
_ => 0,
})
.sum::<usize>()
.max(1)
}
pub(crate) fn kotlin_is_public(decl: &Node) -> bool {
let Some(modifiers) = decl.first_child(|id| id == Kotlin::Modifiers) else {
return true;
};
let Some(visibility) = modifiers.first_child(|id| id == Kotlin::VisibilityModifier) else {
return true;
};
visibility
.first_child(|id| {
matches!(
id.into(),
Kotlin::Private | Kotlin::Protected | Kotlin::Internal
)
})
.is_none()
}
impl Npa for KotlinCode {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use Kotlin::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
ClassParameter
if node
.children()
.any(|c| matches!(c.kind_id().into(), Val | Var)) =>
{
stats.class_na += 1;
if kotlin_is_public(node) {
stats.class_npa += 1;
}
}
ClassBody => {
let is_interface = kotlin_class_body_is_interface(node);
for prop in node
.children()
.filter(|c| matches!(c.kind_id().into(), PropertyDeclaration))
{
let attrs = kotlin_count_property_attrs(&prop);
if is_interface {
stats.interface_na += attrs;
stats.interface_npa += attrs;
} else {
stats.class_na += attrs;
if kotlin_is_public(&prop) {
stats.class_npa += attrs;
}
}
}
}
_ => {}
}
}
}
macro_rules! ts_npa_compute {
($lang:ident) => {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
match node.kind_id().into() {
ClassBody => {
for member in node.children() {
match member.kind_id().into() {
PublicFieldDefinition
if member
.first_child(|id| {
id == $lang::ArrowFunction
|| id == $lang::FunctionExpression
})
.is_none() =>
{
stats.class_na += 1;
if ts_member_is_public!($lang, member) {
stats.class_npa += 1;
}
}
MethodDefinition => {
let Some(params) =
member.first_child(|id| id == $lang::FormalParameters)
else {
continue;
};
for param in params.children().filter(|c| {
matches!(
c.kind_id().into(),
RequiredParameter | RequiredParameter2
)
}) {
if param
.first_child(|id| id == $lang::AccessibilityModifier)
.is_some()
{
stats.class_na += 1;
if ts_member_is_public!($lang, param) {
stats.class_npa += 1;
}
}
}
}
_ => {}
}
}
}
InterfaceBody => {
let count = node
.children()
.filter(|c| matches!(c.kind_id().into(), PropertySignature))
.count();
stats.interface_na += count;
stats.interface_npa = stats.interface_na;
}
_ => {}
}
}
};
}
macro_rules! ts_member_is_public {
($lang:ident, $member:expr) => {{
match $member.first_child(|id| id == $lang::AccessibilityModifier) {
None => true,
Some(m) => m
.first_child(|id| id == $lang::Private || id == $lang::Protected)
.is_none(),
}
}};
}
pub(crate) use ts_member_is_public;
impl Npa for TypescriptCode {
ts_npa_compute!(Typescript);
}
impl Npa for TsxCode {
ts_npa_compute!(Tsx);
}
macro_rules! js_npa_compute {
($lang:ident) => {
fn compute<'a>(node: &Node<'a>, _code: &'a [u8], stats: &mut Stats) {
use $lang::*;
if Self::is_func_space(node) && stats.is_disabled() {
stats.is_class_space = true;
}
if !matches!(node.kind_id().into(), ClassBody) {
return;
}
for member in node.children() {
if matches!(member.kind_id().into(), FieldDefinition)
&& member
.first_child(|id| {
id == $lang::ArrowFunction || id == $lang::FunctionExpression
})
.is_none()
{
stats.class_na += 1;
stats.class_npa += 1;
}
}
}
};
}
impl Npa for JavascriptCode {
js_npa_compute!(Javascript);
}
impl Npa for MozjsCode {
js_npa_compute!(Mozjs);
}
implement_metric_trait!(
Npa,
PreprocCode,
CcommentCode,
PerlCode,
BashCode,
LuaCode,
TclCode
);
impl Npa for ElixirCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats) {
use crate::metrics::cognitive::{elixir_call_keyword, elixir_do_block_call_children};
if !stats.is_disabled() || !Self::is_func_space_with_code(node, code) {
return;
}
if !matches!(elixir_call_keyword(node, code), Some("defmodule")) {
return;
}
stats.is_class_space = true;
for stmt in elixir_do_block_call_children(node) {
if matches!(elixir_call_keyword(&stmt, code), Some("defstruct")) {
let fields = count_defstruct_fields(&stmt);
stats.class_na += fields;
stats.class_npa += fields;
}
}
}
}
fn count_defstruct_fields(call: &Node) -> usize {
use Elixir as E;
call.children()
.filter(|child| matches!(child.kind_id().into(), E::Arguments | E::List | E::Keywords))
.map(|child| count_field_entries(&child))
.sum()
}
fn count_field_entries(node: &Node) -> usize {
use Elixir as E;
node.children()
.map(|child| match child.kind_id().into() {
E::Atom | E::QuotedAtom | E::Atom2 | E::Pair => 1,
E::List | E::Keywords => count_field_entries(&child),
_ => 0,
})
.sum()
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
mod tests {
use crate::tools::{assert_child_space_kind, check_func_space, check_metrics};
use super::*;
#[test]
fn java_single_attributes() {
check_metrics::<JavaParser>(
"class X {
public byte a; // +1
public short b; // +1
public int c; // +1
public long d; // +1
public float e; // +1
public double f; // +1
public boolean g; // +1
public char h; // +1
byte i;
short j;
int k;
long l;
float m;
double n;
boolean o;
char p;
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 8.0,
"interfaces": 0.0,
"class_attributes": 16.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 8.0,
"total_attributes": 16.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_multiple_attributes() {
check_metrics::<JavaParser>(
"class X {
public byte a1; // +1
public short b1, b2; // +2
public int c1, c2, c3; // +3
public long d1, d2, d3, d4; // +4
public float e1, e2, e3, e4; // +4
public double f1, f2, f3; // +3
public boolean g1, g2; // +2
public char h1; // +1
byte i1, i2, i3, i4;
short j1, j2, j3;
int k1, k2;
long l1;
float m1;
double n1, n2;
boolean o1, o2, o3;
char p1, p2, p3, p4;
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 20.0,
"interfaces": 0.0,
"class_attributes": 40.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 20.0,
"total_attributes": 40.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_initialized_attributes() {
check_metrics::<JavaParser>(
"class X {
public byte a1 = 1; // +1
public short b1 = 2, b2; // +2
public int c1, c2 = 3, c3; // +3
public long d1 = 4, d2, d3, d4 = 5; // +4
public float e1, e2 = 6.0f, e3 = 7.0f, e4; // +4
public double f1 = 8.0, f2 = 9.0, f3 = 10.0; // +3
public boolean g1 = true, g2; // +2
public char h1 = 'a'; // +1
byte i1 = 1, i2 = 2, i3 = 3, i4 = 4;
short j1 = 5, j2, j3 = 6;
int k1, k2 = 7;
long l1 = 8;
float m1 = 9.0f;
double n1, n2 = 10.0;
boolean o1, o2 = false, o3;
char p1 = 'a', p2 = 'b', p3 = 'c', p4 = 'd';
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 20.0,
"interfaces": 0.0,
"class_attributes": 40.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 20.0,
"total_attributes": 40.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_array_attributes() {
check_metrics::<JavaParser>(
"class X {
public byte[] a1, a2, a3, a4; // +4
public short b1[], b2[], b3[]; // +3
public int[] c1 = { 1 }, c2; // +2
public long d1[] = { 1 }; // +1
public float[] e1 = { 1.0f, 2.0f, 3.0f }; // +1
public double f1[] = { 1.0, 2.0, 3.0 }, f2[]; // +2
public boolean[] g1 = new boolean[5], g2, g3; // +3
public char[] h1 = new char[5], h2[], h3[], h4[]; // +4
byte[] i1;
short j1[], j2[];
int[] k1, k2, k3 = { 1 };
long l1[], l2[] = { 1 }, l3[] = { 2 }, l4[];
float[] m1, m2, m3, m4 = { 1.0f, 2.0f, 3.0f };
double n1[], n2[] = { 1.0, 2.0, 3.0 }, n3[];
boolean[] o1, o2 = new boolean[5];
char[] p1 = new char[5];
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 20.0,
"interfaces": 0.0,
"class_attributes": 40.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 20.0,
"total_attributes": 40.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_object_attributes() {
check_metrics::<JavaParser>(
"class X {
public Integer[] a1 = { 1 }; // +1
public Integer b1, b2; // +2
public String[] c1 = { \"Hello\" }, c2, c3 = { \"World!\" }; // +3
public String d1[][] = { { \"Hello\" }, { \"World!\" } }; // +1
public Y[] e1, e2[]; // +2
public Y f1[], f2[][], f3[][][]; // +3
Integer[] g1 = { new Integer(1) };
Integer h1 = new Integer(1), h2 = new Integer(2);
String[] i1, i2 = { \"Hello World!\" }, i3;
String j1 = \"Hello World!\";
Y[] k1[], k2;
Y l1[][], l2[], l3 = new Y();
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 12.0,
"interfaces": 0.0,
"class_attributes": 24.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 12.0,
"total_attributes": 24.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn groovy_no_attributes() {
check_metrics::<GroovyParser>("class A { void foo() {} }", "foo.groovy", |metric| {
assert_eq!(metric.npa.total_na(), 0.0);
assert_eq!(metric.npa.total_npa(), 0.0);
});
}
#[test]
fn groovy_public_attributes() {
check_metrics::<GroovyParser>(
"class A {
public int x
public String name
private int hidden
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn groovy_def_attributes_not_public() {
check_metrics::<GroovyParser>(
"class A {
def field1
def field2
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
},
);
}
#[test]
fn groovy_interface_attributes() {
check_func_space::<GroovyParser, _>(
"interface I {
public static final int A = 1
public static final int B = 2
}",
"foo.groovy",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.interface_na_sum(), 2.0);
assert_eq!(metric.npa.interface_npa_sum(), 2.0);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn groovy_no_attributes_in_unit_scope() {
check_metrics::<GroovyParser>("int x = 1", "foo.groovy", |metric| {
assert_eq!(metric.npa.total_na(), 0.0);
});
}
#[test]
fn groovy_multiple_classes() {
check_metrics::<GroovyParser>(
"class A { public int a }
class B { public int b }",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn groovy_initialized_attributes() {
check_metrics::<GroovyParser>(
"class X {
public int a1 = 1, a2
public int b1 = 2
int c1, c2 = 3
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 5.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
},
);
}
#[test]
fn groovy_object_attributes() {
check_metrics::<GroovyParser>(
"class X {
public Integer a1
public String b1 = 'hello'
public Y[] c1
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
},
);
}
#[test]
fn groovy_attribute_modifiers() {
check_metrics::<GroovyParser>(
"class X {
public static int a
static public int b
public final int c = 1
final public int d = 2
private static int e
int f
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 6.0);
assert_eq!(metric.npa.class_npa_sum(), 4.0);
},
);
}
#[test]
#[ignore = "dekobon Groovy grammar v1 does not yet support inner classes inside class bodies (https://github.com/dekobon/tree-sitter-groovy SPECIFICATION.md §4 — 'Field declarations, static initialisers, and inner classes land later')"]
fn groovy_nested_inner_classes() {
check_metrics::<GroovyParser>(
"class X {
public int a
class Y {
public boolean b
class Z {
public char c
}
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
},
);
}
#[test]
fn groovy_array_attributes() {
check_metrics::<GroovyParser>(
"class X {
public int[] a
public String[] b
int[] c
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn groovy_anonymous_inner_class() {
check_metrics::<GroovyParser>(
"class X {
public Runnable r = new Runnable() {
public int x
void run() {}
}
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn groovy_enum_counts_explicit_public_fields() {
check_metrics::<GroovyParser>(
"enum Status {
ACTIVE, INACTIVE;
public int code;
private int hidden;
}",
"foo.groovy",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
},
);
}
#[test]
fn groovy_annotation_type_counts_constants_as_implicit_public() {
check_func_space::<GroovyParser, _>(
"public @interface Marker {
int VERSION = 1;
String NAME = \"x\";
}",
"foo.groovy",
|func_space| {
assert_eq!(func_space.metrics.npa.interface_na_sum(), 2.0);
assert_eq!(func_space.metrics.npa.interface_npa_sum(), 2.0);
assert_child_space_kind(&func_space, "Marker", SpaceKind::Interface);
},
);
}
#[test]
fn java_generic_attributes() {
check_metrics::<JavaParser>(
"class X<T, S extends T> {
public T a1; // +1
public Entry<T, S> b1, b2[]; // +2
public ArrayList<T> c1, c2, c3; // +3
public HashMap<Long, Double> d1, d2; // +2
public TreeSet<String> e1; // +1
S f1;
Entry<S, T> g1[], g2;
ArrayList<S> h1, h2, h3;
HashMap<Long, Float> i1, i2;
TreeSet<Entry<S, T>> j1;
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 9.0,
"interfaces": 0.0,
"class_attributes": 18.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 9.0,
"total_attributes": 18.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_attribute_modifiers() {
check_metrics::<JavaParser>(
"class X {
public transient volatile static int a; // +1
transient public volatile static int b; // +1
transient volatile public static int c; // +1
transient volatile static public int d; // +1
public transient static final int e = 1; // +1
transient public static final int f = 2; // +1
transient static public final int g = 3; // +1
transient static final public int h = 4; // +1
protected transient volatile static int i;
transient volatile static protected int j;
private transient volatile static int k;
transient volatile static private int l;
transient volatile static int m;
transient static final int n = 5;
static public final int o = 6; // +1
final public int p = 7; // +1
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 10.0,
"interfaces": 0.0,
"class_attributes": 16.0,
"interface_attributes": 0.0,
"classes_average": 0.625,
"interfaces_average": null,
"total": 10.0,
"total_attributes": 16.0,
"average": 0.625
}"###
);
},
);
}
#[test]
fn java_classes() {
check_metrics::<JavaParser>(
"class X {
public int a; // +1
public boolean b; // +1
private char c;
}
class Y {
private double d;
private long e;
public float f; // +1
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 3.0,
"interfaces": 0.0,
"class_attributes": 6.0,
"interface_attributes": 0.0,
"classes_average": 0.5,
"interfaces_average": null,
"total": 3.0,
"total_attributes": 6.0,
"average": 0.5
}"###
);
},
);
}
#[test]
fn java_nested_inner_classes() {
check_metrics::<JavaParser>(
"class X {
public int a; // +1
class Y {
public boolean b; // +1
class Z {
public char c; // +1
}
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 3.0,
"interfaces": 0.0,
"class_attributes": 3.0,
"interface_attributes": 0.0,
"classes_average": 1.0,
"interfaces_average": null,
"total": 3.0,
"total_attributes": 3.0,
"average": 1.0
}"###
);
},
);
}
#[test]
fn java_local_inner_classes() {
check_metrics::<JavaParser>(
"class X {
public int a; // +1
void x() {
class Y {
public boolean b; // +1
void y() {
class Z {
public char c; // +1
void z() {}
}
}
}
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 3.0,
"interfaces": 0.0,
"class_attributes": 3.0,
"interface_attributes": 0.0,
"classes_average": 1.0,
"interfaces_average": null,
"total": 3.0,
"total_attributes": 3.0,
"average": 1.0
}"###
);
},
);
}
#[test]
fn java_anonymous_inner_classes() {
check_metrics::<JavaParser>(
"abstract class X {
public int a; // +1
}
abstract class Y {
boolean b;
}
class Z {
public char c; // +1
public void z(){
X x1 = new X() {
public double d; // +1
};
Y y1 = new Y() {
long e;
};
}
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 3.0,
"interfaces": 0.0,
"class_attributes": 5.0,
"interface_attributes": 0.0,
"classes_average": 0.6,
"interfaces_average": null,
"total": 3.0,
"total_attributes": 5.0,
"average": 0.6
}"###
);
},
);
}
#[test]
fn java_interface() {
check_metrics::<JavaParser>(
"interface X {
public int a = 0; // +1
static boolean b = false; // +1
final char c = ' '; // +1
}",
"foo.java",
|metric| {
insta::assert_json_snapshot!(
metric.npa,
@r###"
{
"classes": 0.0,
"interfaces": 3.0,
"class_attributes": 0.0,
"interface_attributes": 3.0,
"classes_average": null,
"interfaces_average": 1.0,
"total": 3.0,
"total_attributes": 3.0,
"average": 1.0
}"###
);
},
);
}
#[test]
fn java_enum_counts_explicit_public_fields() {
check_metrics::<JavaParser>(
"enum Status {
ACTIVE, INACTIVE;
public static final int FLAG = 1; // implicit static final, still public
public int code; // +1 explicit public
private int hidden; // not public
}",
"foo.java",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn java_record_counts_explicit_body_fields() {
check_metrics::<JavaParser>(
"record Point(int x, int y) {
public static int origin = 0; // explicit body, public
private int cached; // explicit body, private
}",
"foo.java",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
},
);
}
#[test]
fn java_annotation_type_counts_constants_as_implicit_public() {
check_func_space::<JavaParser, _>(
"@interface Marker {
int VERSION = 1; // implicit public static final
String NAME = \"x\"; // implicit public static final
}",
"foo.java",
|func_space| {
assert_eq!(func_space.metrics.npa.interface_na_sum(), 2.0);
assert_eq!(func_space.metrics.npa.interface_npa_sum(), 2.0);
assert_child_space_kind(&func_space, "Marker", SpaceKind::Interface);
},
);
}
#[test]
fn php_no_class_attributes() {
check_metrics::<PhpParser>(
"<?php class A { public function f(): void {} }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn csharp_single_attributes() {
check_metrics::<CsharpParser>(
"class X {
public byte a;
public short b;
public int c;
public long d;
public float e;
public double f;
public bool g;
public char h;
byte i;
short j;
int k;
long l;
float m;
double n;
bool o;
char p;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 8.0);
assert_eq!(metric.npa.class_na_sum(), 16.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_multiple_attributes() {
check_metrics::<CsharpParser>(
"class X {
public byte a1;
public short b1, b2;
public int c1, c2, c3;
public long d1, d2, d3, d4;
public bool g1, g2;
byte i1, i2, i3, i4;
int k1, k2;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 12.0);
assert_eq!(metric.npa.class_na_sum(), 18.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_initialized_attributes() {
check_metrics::<CsharpParser>(
"class X {
public int a = 1;
public bool b = true;
public string c = \"hello\";
public double d = 3.14;
int e = 0;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 4.0);
assert_eq!(metric.npa.class_na_sum(), 5.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_array_attributes() {
check_metrics::<CsharpParser>(
"class X {
public int[] a;
public string[] b = new string[5];
int[] c;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_object_attributes() {
check_metrics::<CsharpParser>(
"class Point { public int X, Y; }
class Shape {
public Point origin;
public Point endpoint = new Point();
Point hidden;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 4.0);
assert_eq!(metric.npa.class_na_sum(), 5.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_generic_attributes() {
check_metrics::<CsharpParser>(
"class X {
public System.Collections.Generic.List<int> a;
public System.Collections.Generic.Dictionary<string, int> b;
System.Collections.Generic.List<string> c;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_attribute_modifiers() {
check_metrics::<CsharpParser>(
"class X {
public int a;
private int b;
protected int c;
internal int d;
public static int e;
public readonly int f;
public const int g = 1;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 4.0);
assert_eq!(metric.npa.class_na_sum(), 7.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_classes() {
check_metrics::<CsharpParser>(
"class A {
public int a;
public int b;
int c;
}
class B {
public string s;
int n;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 5.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_nested_inner_classes() {
check_metrics::<CsharpParser>(
"class Outer {
public int a;
int b;
public class Inner {
public string s;
int n;
}
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_struct_attributes() {
check_metrics::<CsharpParser>(
"struct Point {
public int X;
public int Y;
int Hidden;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_record_attributes() {
check_metrics::<CsharpParser>(
"record Person {
public string Name;
int Age;
}",
"foo.cs",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn csharp_interface() {
check_func_space::<CsharpParser, _>(
"interface I {
static int A = 1;
static string B = \"hello\";
}",
"foo.cs",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.interface_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn php_one_public_attribute() {
check_metrics::<PhpParser>(
"<?php class A { public int $x = 0; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_one_private_attribute() {
check_metrics::<PhpParser>(
"<?php class A { private int $x = 0; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_one_protected_attribute() {
check_metrics::<PhpParser>(
"<?php class A { protected int $x = 0; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_mixed_visibility_attributes() {
check_metrics::<PhpParser>(
"<?php
class A {
public int $a = 0;
public int $b = 0;
private int $c = 0;
protected int $d = 0;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_static_public_attribute() {
check_metrics::<PhpParser>(
"<?php class A { public static int $x = 0; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_readonly_public_attribute() {
check_metrics::<PhpParser>(
"<?php class A { public readonly int $x; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_multiple_attributes_per_declaration() {
check_metrics::<PhpParser>(
"<?php class A { public int $a = 0, $b = 0, $c = 0; }",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_interface_constants() {
check_metrics::<PhpParser>(
"<?php
interface I {
const A = 1;
const B = 2;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_enum_cases() {
check_metrics::<PhpParser>(
"<?php
enum Color {
case Red;
case Green;
case Blue;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_trait_attributes() {
check_metrics::<PhpParser>(
"<?php
trait T {
public int $a = 0;
private int $b = 0;
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_no_explicit_visibility_excluded() {
check_metrics::<PhpParser>("<?php class A { var $x = 0; }", "foo.php", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn php_anonymous_class_attributes() {
check_metrics::<PhpParser>(
"<?php
$obj = new class {
public int $a = 0;
private int $b = 0;
};",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn php_property_promotion_excluded() {
check_metrics::<PhpParser>(
"<?php
class A {
public function __construct(public string $x, public int $y) {}
}",
"foo.php",
|metric| insta::assert_json_snapshot!(metric.npa),
);
}
#[test]
fn kotlin_empty_class_no_attributes() {
check_metrics::<KotlinParser>("class C {}", "foo.kt", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn kotlin_public_val_var_default() {
check_metrics::<KotlinParser>(
"class C {
val a: Int = 1
var b: Int = 2
val c: String = \"hi\"
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_private_val_var() {
check_metrics::<KotlinParser>(
"class C {
val a: Int = 1 // public
private val b: Int = 2 // not public
var c: Int = 3 // public
private var d: Int = 4 // not public
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_protected_internal_excluded_from_public() {
check_metrics::<KotlinParser>(
"open class C {
protected val a: Int = 1
internal val b: Int = 2
public val c: Int = 3 // explicit public
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_primary_constructor_parameter_property() {
check_metrics::<KotlinParser>(
"class C(val a: Int, var b: Int, c: Int) {
val d: Int = c
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_primary_constructor_private_param_property() {
check_metrics::<KotlinParser>(
"class C(private val a: Int, val b: Int)",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_secondary_constructor_does_not_add_attrs() {
check_metrics::<KotlinParser>(
"class C {
private var a: Int = 0
constructor(n: Int) { a = n }
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_companion_object_attributes() {
check_metrics::<KotlinParser>(
"class Holder {
val instance: Int = 1
companion object {
val SCALE: Int = 10
private val SECRET: Int = 7
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_data_class_attributes() {
check_metrics::<KotlinParser>(
"data class Point(val x: Int, val y: Int)",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_object_singleton_attributes() {
check_metrics::<KotlinParser>(
"object Config {
val DEFAULT: Int = 42
private val SEED: Int = 0
var debug: Boolean = false
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_interface_attributes() {
check_func_space::<KotlinParser, _>(
"interface I {
val a: Int
val b: String
}",
"foo.kt",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.interface_npa_sum(), 2.0);
assert_eq!(metric.npa.interface_na_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn kotlin_nested_class_attributes() {
check_metrics::<KotlinParser>(
"class Outer {
val o1: Int = 1
class Nested {
val n1: Int = 1
val n2: Int = 2
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_inner_class_attributes() {
check_metrics::<KotlinParser>(
"class Outer {
val o1: Int = 1
inner class Inner {
val i1: Int = 1
}
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_top_level_properties_excluded() {
check_metrics::<KotlinParser>(
"val topVal: Int = 0
var topVar: Int = 1
class C { val x: Int = 0 }",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_multiple_classes_attributes() {
check_metrics::<KotlinParser>(
"class A {
val a1: Int = 0
var a2: Int = 0
}
class B {
val b1: Int = 0
private val b2: Int = 0
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn kotlin_class_with_methods_no_attrs() {
check_metrics::<KotlinParser>(
"class C {
fun m1() {}
fun m2(): Int = 0
}",
"foo.kt",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_empty_class_no_attributes() {
check_metrics::<TypescriptParser>("class C {}", "foo.ts", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn typescript_default_public_fields() {
check_metrics::<TypescriptParser>(
"class C {
a: number = 1;
b: string = \"\";
c: boolean = false;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_visibility_modifiers() {
check_metrics::<TypescriptParser>(
"class C {
public a: number = 1;
private b: number = 2;
protected c: number = 3;
d: number = 4;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_static_fields() {
check_metrics::<TypescriptParser>(
"class C {
static a: number = 0;
public static b: number = 0;
private static c: number = 0;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_parameter_properties() {
check_metrics::<TypescriptParser>(
"class C {
constructor(public a: number, private b: string, c: boolean) {}
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_readonly_field() {
check_metrics::<TypescriptParser>(
"class C {
readonly a: number = 1;
private readonly b: number = 2;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_abstract_class_attributes() {
check_metrics::<TypescriptParser>(
"abstract class C {
public a: number = 1;
protected b: number = 2;
abstract m(): void;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_arrow_field_is_method_not_attribute() {
check_metrics::<TypescriptParser>(
"class C {
a: number = 0;
arrow = () => this.a;
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_interface_property_signatures() {
check_func_space::<TypescriptParser, _>(
"interface I {
a: number;
b: string;
m(): void;
}",
"foo.ts",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.interface_npa_sum(), 2.0);
assert_eq!(metric.npa.interface_na_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn typescript_generic_class_attributes() {
check_metrics::<TypescriptParser>(
"class Box<T, U> {
value: T;
other: U;
constructor(v: T, o: U) { this.value = v; this.other = o; }
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_getters_setters_not_attributes() {
check_metrics::<TypescriptParser>(
"class C {
private _x: number = 0;
get x(): number { return this._x; }
set x(v: number) { this._x = v; }
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn typescript_multiple_classes_and_interface() {
check_func_space::<TypescriptParser, _>(
"class A { x: number = 0; }
class B { private y: number = 0; }
interface I { z: number; }",
"foo.ts",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.interface_npa_sum(), 1.0);
assert_eq!(metric.npa.interface_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "A", SpaceKind::Class);
assert_child_space_kind(&func_space, "B", SpaceKind::Class);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn typescript_nested_class_attributes_independent() {
check_metrics::<TypescriptParser>(
"class Outer {
a: number = 0;
static Inner = class {
b: number = 0;
c: number = 0;
};
}",
"foo.ts",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 4.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_empty_class_no_attributes() {
check_metrics::<TsxParser>("class C {}", "foo.tsx", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn tsx_default_public_fields() {
check_metrics::<TsxParser>(
"class C {
a: number = 1;
b: string = \"\";
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_visibility_modifiers() {
check_metrics::<TsxParser>(
"class C {
public a: number = 1;
private b: number = 2;
protected c: number = 3;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_parameter_properties() {
check_metrics::<TsxParser>(
"class C {
constructor(public a: number, private b: string) {}
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_abstract_class_attributes() {
check_metrics::<TsxParser>(
"abstract class C {
public a: number = 1;
private b: number = 2;
abstract m(): void;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_interface_property_signatures() {
check_func_space::<TsxParser, _>(
"interface I {
a: number;
b: string;
m(): void;
}",
"foo.tsx",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.interface_npa_sum(), 2.0);
assert_eq!(metric.npa.interface_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn tsx_arrow_field_is_method_not_attribute() {
check_metrics::<TsxParser>(
"class C {
a: number = 0;
arrow = () => this.a;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_static_fields() {
check_metrics::<TsxParser>(
"class C {
static a: number = 0;
private static b: number = 0;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_readonly_field() {
check_metrics::<TsxParser>(
"class C {
readonly a: number = 1;
private readonly b: number = 2;
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_generic_class_attributes() {
check_metrics::<TsxParser>("class Box<T> { value: T; }", "foo.tsx", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn tsx_getters_setters_not_attributes() {
check_metrics::<TsxParser>(
"class C {
private _x: number = 0;
get x(): number { return this._x; }
set x(v: number) { this._x = v; }
}",
"foo.tsx",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn tsx_multiple_classes_and_interface() {
check_func_space::<TsxParser, _>(
"class A { x: number = 0; }
class B { private y: number = 0; }
interface I { z: number; }",
"foo.tsx",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.interface_npa_sum(), 1.0);
assert_eq!(metric.npa.interface_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "A", SpaceKind::Class);
assert_child_space_kind(&func_space, "B", SpaceKind::Class);
assert_child_space_kind(&func_space, "I", SpaceKind::Interface);
},
);
}
#[test]
fn ruby_no_class_attributes() {
check_metrics::<RubyParser>(
"class A\n def f\n 1\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_instance_variable_attribute() {
check_metrics::<RubyParser>("class A\n @x = 1\nend\n", "foo.rb", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn ruby_class_variable_attribute() {
check_metrics::<RubyParser>("class A\n @@y = 1\nend\n", "foo.rb", |metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn ruby_attr_accessor_counts_symbols() {
check_metrics::<RubyParser>(
"class A\n attr_accessor :x, :y, :z\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_attr_reader_and_writer() {
check_metrics::<RubyParser>(
"class A\n attr_reader :r1, :r2\n attr_writer :w\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 3.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_mixed_attributes_and_assignments() {
check_metrics::<RubyParser>(
"class A\n attr_accessor :x, :y\n @z = 1\n @@w = 2\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 4.0);
assert_eq!(metric.npa.class_na_sum(), 4.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_private_attributes() {
check_metrics::<RubyParser>(
"class A\n attr_accessor :pub\n private\n attr_accessor :hidden\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_visibility_public_resets_private() {
check_metrics::<RubyParser>(
"class A\n attr_reader :a\n private\n attr_reader :b\n public\n attr_reader :c\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_method_scope_assignments_excluded() {
check_metrics::<RubyParser>(
"class A\n def init\n @x = 1\n @@y = 2\n end\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_module_attributes_not_counted() {
check_metrics::<RubyParser>(
"module M\n attr_accessor :x\n @@m = 1\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_inheritance_attributes() {
check_metrics::<RubyParser>(
"class A < B\n attr_accessor :x\n @y = 0\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_constant_assignments_excluded() {
check_metrics::<RubyParser>(
"class A\n PI = 3.14\n E = 2.71\n attr_reader :x\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn ruby_multiple_classes_attribute_rollup() {
check_metrics::<RubyParser>(
"class A\n attr_accessor :x\nend\nclass B\n private\n attr_accessor :y\nend\n",
"foo.rb",
|metric| {
assert_eq!(metric.npa.class_npa_sum(), 1.0);
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_empty_class_no_attributes() {
check_metrics::<PythonParser>("class C:\n pass\n", "foo.py", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn python_class_level_assignments_are_attributes() {
check_metrics::<PythonParser>("class C:\n x = 1\n y = 2\n", "foo.py", |metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn python_bare_type_annotation_not_attribute() {
check_metrics::<PythonParser>(
"class C:\n x: int\n y: int = 2\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_self_attributes_in_init() {
check_metrics::<PythonParser>(
"class C:\n def __init__(self):\n self.x = 1\n self.y = 2\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_self_attributes_in_nested_control_flow() {
check_metrics::<PythonParser>(
"class C:\n def __init__(self, flag):\n if flag:\n self.z = 1\n else:\n self.z = 2\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_defensive_reinit_self_attribute_counts_once() {
check_metrics::<PythonParser>(
"class C:\n def __init__(self):\n self.value = None\n def reset(self):\n self.value = 0\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_distinct_self_attributes_count_independently() {
check_metrics::<PythonParser>(
"class C:\n def __init__(self):\n self.x = 1\n self.y = 2\n self.z = 3\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_self_attribute_annotated_assignment_dedupes() {
check_metrics::<PythonParser>(
"class C:\n def __init__(self):\n self.value: int = 1\n def reset(self):\n self.value = 0\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_class_level_and_self_attrs_combine() {
check_metrics::<PythonParser>(
"class C:\n counter = 0\n def __init__(self):\n self.name = 'a'\n self.value = 1\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_self_attrs_isolated_per_class() {
check_metrics::<PythonParser>(
"class Outer:\n\
\x20 def __init__(self):\n\
\x20 self.x = 1\n\
\x20 class Inner:\n\
\x20 def __init__(self):\n\
\x20 self.z = 2\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_decorated_methods_do_not_inflate_attrs() {
check_metrics::<PythonParser>(
"class C:\n\
\x20 @property\n\
\x20 def p(self):\n\
\x20 return 1\n\
\x20 @staticmethod\n\
\x20 def s():\n\
\x20 return 2\n",
"foo.py",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn python_module_level_assignments_not_attributes() {
check_metrics::<PythonParser>("x = 1\ny = 2\nclass C:\n a = 3\n", "foo.py", |metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn rust_empty_unit_no_attributes() {
check_metrics::<RustParser>("", "empty.rs", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
assert_eq!(metric.npa.interface_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn rust_struct_fields_are_attributes() {
check_metrics::<RustParser>(
"struct Foo { pub a: i32, b: String, pub c: bool }",
"foo.rs",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn rust_tuple_struct_fields_are_attributes() {
check_metrics::<RustParser>("struct Bar(pub i32, String);", "foo.rs", |metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn rust_unit_struct_has_no_attributes() {
check_metrics::<RustParser>("struct Empty;", "foo.rs", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn rust_empty_struct_body_has_no_attributes() {
check_metrics::<RustParser>("struct Empty { }", "foo.rs", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn rust_impl_associated_consts_are_attributes() {
check_metrics::<RustParser>(
"struct Foo;\n\
impl Foo {\n\
\x20 const X: i32 = 1;\n\
\x20 pub const Y: i32 = 2;\n\
\x20 static Z: i32 = 3;\n\
\x20 pub static W: i32 = 4;\n\
}\n",
"foo.rs",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 4.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn rust_trait_consts_and_associated_types_are_attributes() {
check_func_space::<RustParser, _>(
"trait Drawable { const DEFAULT_COLOR: u32; type Item; }",
"foo.rs",
|func_space| {
let metric = &func_space.metrics;
assert_eq!(metric.npa.interface_na_sum(), 2.0);
assert_eq!(metric.npa.interface_npa_sum(), 2.0);
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
assert_child_space_kind(&func_space, "Drawable", SpaceKind::Trait);
},
);
}
#[test]
fn rust_multiple_impls_aggregate() {
check_metrics::<RustParser>(
"struct Foo;\n\
impl Foo { const X: i32 = 1; }\n\
impl Foo { pub const Y: i32 = 2; }\n",
"foo.rs",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn rust_module_level_consts_not_attributes() {
check_metrics::<RustParser>(
"const PI: f64 = 3.14;\nstatic Q: i32 = 0;\n",
"foo.rs",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.interface_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn go_empty_unit_no_attributes() {
check_metrics::<GoParser>("package main\n", "empty.go", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn go_empty_struct_has_no_attributes() {
check_metrics::<GoParser>("package main\ntype Empty struct{}\n", "foo.go", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn go_struct_fields_are_attributes() {
check_metrics::<GoParser>(
"package main\ntype Foo struct { X int; y string; Z float64 }\n",
"foo.go",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn go_grouped_struct_fields_each_count() {
check_metrics::<GoParser>(
"package main\ntype Point struct { X, Y int; Z float64 }\n",
"foo.go",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn go_embedded_type_counts_as_attribute() {
check_metrics::<GoParser>(
"package main\nimport \"io\"\ntype Bar struct { io.Reader; *Foo; n int }\ntype Foo struct {}\n",
"foo.go",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn go_multiple_structs_aggregate_at_unit() {
check_metrics::<GoParser>(
"package main\ntype Foo struct { x int }\ntype Bar struct { a int; b string }\n",
"foo.go",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn go_top_level_var_const_not_attributes() {
check_metrics::<GoParser>(
"package main\nvar Counter int\nconst Pi = 3.14\n",
"foo.go",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn elixir_npa_defstruct_keyword_list() {
check_metrics::<ElixirParser>(
"defmodule User do\n defstruct name: nil, age: 0, email: nil\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
},
);
}
#[test]
fn elixir_npa_defstruct_atom_list() {
check_metrics::<ElixirParser>(
"defmodule User do\n defstruct [:name, :age, :email]\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
},
);
}
#[test]
fn elixir_npa_defstruct_bracketed_keyword_list() {
check_metrics::<ElixirParser>(
"defmodule User do\n defstruct [name: nil, age: 0]\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 2.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
},
);
}
#[test]
fn elixir_npa_defstruct_single_field() {
check_metrics::<ElixirParser>(
"defmodule Box do\n defstruct value: nil\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
},
);
}
#[test]
fn elixir_npa_no_defstruct_is_zero() {
check_metrics::<ElixirParser>(
"defmodule Foo do\n def m, do: :ok\nend\n",
"foo.ex",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
},
);
}
#[test]
fn cpp_empty_unit_no_attributes() {
check_metrics::<CppParser>("", "empty.cpp", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn cpp_empty_class_no_attributes() {
check_metrics::<CppParser>("class Foo {};", "foo.cpp", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn cpp_class_public_attributes() {
check_metrics::<CppParser>(
"class Foo { public: int a; int b, c; };",
"foo.cpp",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn cpp_class_private_default_visibility() {
check_metrics::<CppParser>("class Foo { int value_; };", "foo.cpp", |metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn cpp_struct_default_public_visibility() {
check_metrics::<CppParser>("struct Bar { int value_; };", "foo.cpp", |metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn cpp_mixed_visibility_sections() {
check_metrics::<CppParser>(
"class Foo {\n\
public: int a;\n\
protected: int b;\n\
private: int c;\n\
};",
"foo.cpp",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn cpp_methods_not_counted_as_attributes() {
check_metrics::<CppParser>(
"class Foo {\n\
public:\n\
void method1() {}\n\
void method2();\n\
private:\n\
int value_;\n\
};",
"foo.cpp",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn cpp_pointer_array_fields_count() {
check_metrics::<CppParser>(
"struct S {\n\
int* p;\n\
int a[10];\n\
int x;\n\
};",
"foo.cpp",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn cpp_multiple_classes_aggregate_at_unit() {
check_metrics::<CppParser>(
"class Foo { public: int a; private: int b; };\nstruct Bar { int c; };",
"foo.cpp",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 2.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn javascript_empty_unit_no_attributes() {
check_metrics::<JavascriptParser>("", "empty.js", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn javascript_empty_class_no_attributes() {
check_metrics::<JavascriptParser>("class Foo {}", "foo.js", |metric| {
assert_eq!(metric.npa.class_na_sum(), 0.0);
assert_eq!(metric.npa.class_npa_sum(), 0.0);
insta::assert_json_snapshot!(metric.npa);
});
}
#[test]
fn javascript_class_fields_count() {
check_metrics::<JavascriptParser>(
"class Foo { x = 1; y; static z = 2; }",
"foo.js",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn javascript_arrow_field_is_method_not_attribute() {
check_metrics::<JavascriptParser>(
"class Foo { x = () => {}; y = function() {}; z = 1; }",
"foo.js",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn javascript_methods_not_counted_as_attributes() {
check_metrics::<JavascriptParser>(
"class Foo { constructor() {} bar() {} get baz() { return 1; } x = 1; }",
"foo.js",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 1.0);
assert_eq!(metric.npa.class_npa_sum(), 1.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn javascript_multiple_classes_aggregate_at_unit() {
check_metrics::<JavascriptParser>(
"class Foo { a = 1; b = 2; }\nclass Bar { c = 3; }",
"foo.js",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
#[test]
fn mozjs_class_fields_count() {
check_metrics::<MozjsParser>(
"class Foo { x = 1; y; static z = 2; }",
"foo.js",
|metric| {
assert_eq!(metric.npa.class_na_sum(), 3.0);
assert_eq!(metric.npa.class_npa_sum(), 3.0);
insta::assert_json_snapshot!(metric.npa);
},
);
}
}