Skip to content

Commit

Permalink
Change Halstead implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Luni-4 committed Jun 25, 2020
1 parent 2d75c2d commit 45fb0f5
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 73 deletions.
2 changes: 1 addition & 1 deletion src/metrics/cyclomatic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::checker::Checker;
use crate::*;

/// The `Cyclomatic` metric.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Stats {
cyclomatic: f64,
n: usize,
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::*;
///
/// This metric counts the number of possible exit points
/// from a function/method.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Stats {
exit: usize,
}
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/fn_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::*;
///
/// This metric counts the number of arguments
/// of a function/method.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Stats {
n_args: usize,
}
Expand Down
214 changes: 173 additions & 41 deletions src/metrics/halstead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,50 @@ use crate::getter::Getter;
use crate::*;

/// The `Halstead` metric suite.
#[derive(Default, Debug)]
pub struct Stats<'a> {
#[derive(Default, Clone, Debug)]
pub struct Stats {
u_operators: u64,
operators: u64,
u_operands: u64,
operands: u64,
}

/// Specifies the type of nodes accepted by the `Halstead` metric.
pub enum HalsteadType {
/// The node is an `Halstead` operator
Operator,
/// The node is an `Halstead` operand
Operand,
/// The node is unknown to the `Halstead` metric
Unknown,
}

#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct HalsteadMaps<'a> {
operators: FxHashMap<u16, u64>,
operands: FxHashMap<&'a [u8], u64>,
}

impl Serialize for Stats<'_> {
impl<'a> HalsteadMaps<'a> {
pub fn new() -> Self {
HalsteadMaps {
operators: FxHashMap::default(),
operands: FxHashMap::default(),
}
}

pub fn merge(&mut self, other: &HalsteadMaps<'a>) {
for (k, v) in other.operators.iter() {
*self.operators.entry(*k).or_insert(0) += v;
}
for (k, v) in other.operands.iter() {
*self.operands.entry(*k).or_insert(0) += v;
}
}
}

impl Serialize for Stats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Expand All @@ -40,7 +77,7 @@ impl Serialize for Stats<'_> {
}
}

impl<'a> fmt::Display for Stats<'a> {
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
Expand Down Expand Up @@ -76,39 +113,37 @@ impl<'a> fmt::Display for Stats<'a> {
}
}

impl<'a> Stats<'a> {
impl Stats {
/// Merges a second `Halstead` metric suite into the first one
pub fn merge(&mut self, other: &Stats<'a>) {
for (k, v) in other.operators.iter() {
*self.operators.entry(*k).or_insert(0) += v;
}
for (k, v) in other.operands.iter() {
*self.operands.entry(*k).or_insert(0) += v;
}
pub fn merge(&mut self, other: &Stats) {
self.u_operators += other.u_operators;
self.operators += other.operators;
self.u_operands += other.u_operands;
self.operands += other.operands;
}

/// Returns `η1`, the number of distinct operators
#[inline(always)]
pub fn u_operators(&self) -> f64 {
self.operators.len() as f64
self.u_operators as f64
}

/// Returns `N1`, the number of total operators
#[inline(always)]
pub fn operators(&self) -> f64 {
self.operators.values().sum::<u64>() as f64
self.operators as f64
}

/// Returns `η2`, the number of distinct operands
#[inline(always)]
pub fn u_operands(&self) -> f64 {
self.operands.len() as f64
self.u_operands as f64
}

/// Returns `N2`, the number of total operands
#[inline(always)]
pub fn operands(&self) -> f64 {
self.operands.values().sum::<u64>() as f64
self.operands as f64
}

/// Returns the program length
Expand Down Expand Up @@ -178,14 +213,13 @@ pub trait Halstead
where
Self: Checker,
{
fn compute<'a>(_node: &Node<'a>, _code: &'a [u8], _stats: &mut Stats<'a>) {}
}

#[doc(hidden)]
pub enum HalsteadType {
Operator,
Operand,
Unknown,
fn compute<'a>(
_node: &Node<'a>,
_code: &'a [u8],
_stats: &mut Stats,
_halstead_maps: &mut HalsteadMaps<'a>,
) {
}
}

#[inline(always)]
Expand All @@ -194,53 +228,104 @@ fn get_id<'a>(node: &Node<'a>, code: &'a [u8]) -> &'a [u8] {
}

#[inline(always)]
fn compute_halstead<'a, T: Getter>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
fn compute_halstead<'a, T: Getter>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
match T::get_op_type(&node) {
HalsteadType::Operator => *stats.operators.entry(node.kind_id()).or_insert(0) += 1,
HalsteadType::Operand => *stats.operands.entry(get_id(node, code)).or_insert(0) += 1,
HalsteadType::Operator => {
*halstead_maps.operators.entry(node.kind_id()).or_insert(0) += 1;
stats.u_operators = halstead_maps.operators.len() as u64;
stats.operators += 1;
}
HalsteadType::Operand => {
*halstead_maps
.operands
.entry(get_id(node, code))
.or_insert(0) += 1;
stats.u_operands = halstead_maps.operands.len() as u64;
stats.operands += 1;
}
_ => {}
}
}

impl Halstead for PythonCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for MozjsCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for JavascriptCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for TypescriptCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for TsxCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for RustCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

impl Halstead for CppCode {
fn compute<'a>(node: &Node<'a>, code: &'a [u8], stats: &mut Stats<'a>) {
compute_halstead::<Self>(node, code, stats);
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
stats: &mut Stats,
halstead_maps: &mut HalsteadMaps<'a>,
) {
compute_halstead::<Self>(node, code, stats, halstead_maps);
}
}

Expand All @@ -251,3 +336,50 @@ impl Halstead for JavaCode {}
impl Halstead for GoCode {}
impl Halstead for CssCode {}
impl Halstead for HtmlCode {}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::*;

#[test]
fn test_halstead_operators_and_operands() {
check_metrics!(
"def f():\n
pass\n",
"foo.py",
PythonParser,
halstead,
[
(u_operators, 2, usize),
(operators, 2, usize),
(u_operands, 1, usize),
(operands, 1, usize)
]
);
}

#[test]
fn test_halstead_formulas() {
check_metrics!(
"def f():\n
pass\n",
"foo.py",
PythonParser,
halstead,
[
(vocabulary, 3, usize),
(length, 3, usize),
(volume, 4.754_887_502_163_468),
(estimated_program_length, 2.0),
(difficulty, 1.0),
(effort, 4.754_887_502_163_468),
(purity_ratio, 0.666_666_666_666_666_6),
(level, 1.0),
(time, 0.264_160_416_786_859_36),
(bugs, 0.000_942_552_557_372_941_4)
]
);
}
}
2 changes: 1 addition & 1 deletion src/metrics/loc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tree_sitter::Node;
use crate::*;

/// The `Loc` metric suite.
#[derive(Debug, Default)]
#[derive(Debug, Clone, Default)]
pub struct Stats {
start: usize,
end: usize,
Expand Down
4 changes: 2 additions & 2 deletions src/metrics/mi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::checker::Checker;
use crate::*;

/// The `Mi` metric.
#[derive(Default, Debug)]
#[derive(Default, Clone, Debug)]
pub struct Stats {
halstead_length: f64,
halstead_vocabulary: f64,
Expand Down Expand Up @@ -93,7 +93,7 @@ where
_node: &Node<'a>,
loc: &loc::Stats,
cyclomatic: &cyclomatic::Stats,
halstead: &halstead::Stats<'a>,
halstead: &halstead::Stats,
stats: &mut Stats,
) {
stats.halstead_length = halstead.length();
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/nom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::checker::Checker;
use crate::*;

/// The `Nom` metric suite.
#[derive(Default, Debug)]
#[derive(Default, Clone, Debug)]
pub struct Stats {
functions: usize,
closures: usize,
Expand Down
Loading

0 comments on commit 45fb0f5

Please sign in to comment.