diff --git a/rust-code-analysis-cli/src/main.rs b/rust-code-analysis-cli/src/main.rs index 2f2bc5907..c1a236ec8 100644 --- a/rust-code-analysis-cli/src/main.rs +++ b/rust-code-analysis-cli/src/main.rs @@ -112,7 +112,7 @@ fn act_on_file(language: Option, path: PathBuf, cfg: &Config) -> std::io:: action::(&language, source, &path, pr, cfg) } else if !cfg.find_filter.is_empty() { let cfg = FindCfg { - path: Some(path.clone()), + path: path.clone(), filters: cfg.find_filter.clone(), line_start: cfg.line_start, line_end: cfg.line_end, @@ -120,7 +120,6 @@ fn act_on_file(language: Option, path: PathBuf, cfg: &Config) -> std::io:: action::(&language, source, &path, pr, cfg) } else if cfg.count_lock.is_some() { let cfg = CountCfg { - path: Some(path.clone()), filters: cfg.count_filter.clone(), stats: cfg.count_lock.as_ref().unwrap().clone(), }; diff --git a/src/ast.rs b/src/ast.rs index 6cd8ee3ff..b5cc330c2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -3,28 +3,52 @@ use serde::{Deserialize, Serialize}; use crate::*; +/// Start and end positions of a node in a code in terms of rows and columns. +/// +/// The first and second fields represent the row and column associated to +/// the start position of a node. +/// +/// The third and fourth fields represent the row and column associated to +/// the end position of a node. pub type Span = Option<(usize, usize, usize, usize)>; +/// The payload of an `Ast` request. #[derive(Debug, Deserialize, Serialize)] pub struct AstPayload { + /// The id associated to a request for an `AST` pub id: String, + /// The filename associated to a source code file pub file_name: String, + /// The code to be represented as an `AST` pub code: String, + /// If `true`, nodes representing comments are ignored pub comment: bool, + /// If `true`, the start and end positions of a node in a code + /// are considered pub span: bool, } +/// The response of an `AST` request. #[derive(Debug, Serialize)] pub struct AstResponse { + /// The id associated to a request for an `AST` id: String, + /// The root node of an `AST` + /// + /// If `None`, an error has occurred root: Option, } +/// Information on an `AST` node. #[derive(Debug)] pub struct AstNode { + /// The type of node pub r#type: &'static str, + /// The code associated to a node pub value: String, + /// The start and end positions of a node in a code pub span: Span, + /// The children of a node pub children: Vec, } @@ -99,11 +123,18 @@ fn build(parser: &T, span: bool, comment: bool) -> Option(node: &'a Node<'a>, level: usize) -> Option> { Some(node) } +#[doc(hidden)] #[macro_export] macro_rules! has_ancestors { ($node:expr, $( $typs:pat )|*, $( $typ:pat ),+) => {{ diff --git a/src/comment_rm.rs b/src/comment_rm.rs index 25987237b..176261261 100644 --- a/src/comment_rm.rs +++ b/src/comment_rm.rs @@ -7,6 +7,7 @@ use crate::traits::*; const CR: [u8; 8192] = [b'\n'; 8192]; +/// Removes comments from a code. pub fn rm_comments(parser: &T) -> Option> { let node = parser.get_root(); let mut stack = Vec::new(); @@ -59,12 +60,17 @@ fn remove_from_code(code: &[u8], mut spans: Vec<(usize, usize, usize)>) -> Vec; diff --git a/src/count.rs b/src/count.rs index ec79e14de..50d604120 100644 --- a/src/count.rs +++ b/src/count.rs @@ -2,11 +2,12 @@ extern crate num_format; use num_format::{Locale, ToFormattedString}; use std::fmt; -use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::traits::*; +/// Counts the types of nodes specified in the input slice +/// and the number of nodes in a code. pub fn count<'a, T: TSParserTrait>(parser: &'a T, filters: &[String]) -> (usize, usize) { let filters = parser.get_filters(filters); let node = parser.get_root(); @@ -35,15 +36,21 @@ pub fn count<'a, T: TSParserTrait>(parser: &'a T, filters: &[String]) -> (usize, (good, total) } +/// Configuration options for counting different +/// types of nodes in a code. pub struct CountCfg { - pub path: Option, + /// Types of nodes to count pub filters: Vec, + /// Number of nodes of a certain type counted by each thread pub stats: Arc>, } +/// Count of different types of nodes in a code. #[derive(Debug, Default)] pub struct Count { + /// The number of specific types of nodes searched in a code pub good: usize, + /// The total number of nodes in a code pub total: usize, } diff --git a/src/find.rs b/src/find.rs index c1508e19b..70d2d7689 100644 --- a/src/find.rs +++ b/src/find.rs @@ -4,6 +4,7 @@ use tree_sitter::Node; use crate::dump::*; use crate::traits::*; +/// Finds the types of nodes specified in the input slice. pub fn find<'a, T: TSParserTrait>(parser: &'a T, filters: &[String]) -> Option>> { let filters = parser.get_filters(filters); let node = parser.get_root(); @@ -34,14 +35,28 @@ pub fn find<'a, T: TSParserTrait>(parser: &'a T, filters: &[String]) -> Option, + /// Path to the file containing the code + pub path: PathBuf, + /// Types of nodes to find pub filters: Vec, + /// The first line of code considered in the search + /// + /// If `None`, the search starts from the + /// first line of code in a file pub line_start: Option, + /// The end line of code considered in the search + /// + /// If `None`, the search ends at the + /// last line of code in a file pub line_end: Option, } -pub struct Find {} +pub struct Find { + _guard: (), +} impl Callback for Find { type Res = std::io::Result<()>; @@ -50,7 +65,7 @@ impl Callback for Find { fn call(cfg: Self::Cfg, parser: &T) -> Self::Res { if let Some(good) = find(parser, &cfg.filters) { if !good.is_empty() { - println!("In file {:?}", cfg.path); + println!("In file {}", cfg.path.to_str().unwrap()); for node in good { dump_node(parser.get_code(), &node, 1, cfg.line_start, cfg.line_end)?; } diff --git a/src/function.rs b/src/function.rs index 5fd633cc4..f4e701f0a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -7,14 +7,25 @@ use crate::traits::*; use crate::checker::Checker; use crate::getter::Getter; +/// Function span data. #[derive(Debug, Serialize)] pub struct FunctionSpan { + /// The function name pub name: String, + /// The first line of a function pub start_line: usize, + /// The last line of a function pub end_line: usize, + /// If `true`, an error is occured in determining the span + /// of a function pub error: bool, } +/// Detects the span of each function in a code. +/// +/// Returns a vector containing the [`FunctionSpan`] of each function +/// +/// [`FunctionSpan`]: struct.FunctionSpan.html pub fn function(parser: &T) -> Vec { let root = parser.get_root(); let code = parser.get_code(); @@ -96,11 +107,16 @@ fn dump_spans(mut spans: Vec, path: PathBuf) -> std::io::Result<() Ok(()) } +/// Configuration options for detecting the span of +/// each function in a code. pub struct FunctionCfg { + /// Path to the file containing the code pub path: PathBuf, } -pub struct Function {} +pub struct Function { + _guard: (), +} impl Callback for Function { type Res = std::io::Result<()>; diff --git a/src/langs.rs b/src/langs.rs index 6ff628c58..c906cd9a0 100644 --- a/src/langs.rs +++ b/src/langs.rs @@ -8,14 +8,16 @@ use crate::*; mk_langs!( // 1) Name for enum - // 2) Display name - // 3) Empty struct name to implement - // 4) Parser name - // 5) tree-sitter function to call to get a Language - // 6) file extensions - // 7) emacs modes + // 2) Language description + // 3) Display name + // 4) Empty struct name to implement + // 5) Parser name + // 6) tree-sitter function to call to get a Language + // 7) file extensions + // 8) emacs modes ( Mozjs, + "The `Mozjs` language is variant of the `JavaScript` language", "javascript", MozjsCode, MozjsParser, @@ -25,6 +27,7 @@ mk_langs!( ), ( Javascript, + "The `JavaScript` language", "javascript", JavascriptCode, JavascriptParser, @@ -34,6 +37,7 @@ mk_langs!( ), ( Java, + "The `Java` language", "java", JavaCode, JavaParser, @@ -41,9 +45,19 @@ mk_langs!( [java], ["java"] ), - (Go, "go", GoCode, GoParser, tree_sitter_go, [go], ["go"]), + ( + Go, + "The `Go` language", + "go", + GoCode, + GoParser, + tree_sitter_go, + [go], + ["go"] + ), ( Html, + "The `HTML` language", "html", HtmlCode, HtmlParser, @@ -53,6 +67,7 @@ mk_langs!( ), ( CSharp, + "The `C#` language", "c#", CSharpCode, CSharpParser, @@ -62,6 +77,7 @@ mk_langs!( ), ( Rust, + "The `Rust` language", "rust", RustCode, RustParser, @@ -71,6 +87,7 @@ mk_langs!( ), ( Css, + "The `CSS` language", "css", CssCode, CssParser, @@ -80,6 +97,7 @@ mk_langs!( ), ( Cpp, + "The `C/C++` language", "c/c++", CppCode, CppParser, @@ -89,6 +107,7 @@ mk_langs!( ), ( Python, + "The `Python` language", "python", PythonCode, PythonParser, @@ -98,6 +117,7 @@ mk_langs!( ), ( Tsx, + "The `Tsx` language incorporates the `JSX` syntax inside `TypeScript`", "typescript", TsxCode, TsxParser, @@ -107,6 +127,7 @@ mk_langs!( ), ( Typescript, + "The `TypeScript` language", "typescript", TypescriptCode, TypescriptParser, @@ -116,6 +137,7 @@ mk_langs!( ), ( Ccomment, + "The `Ccomment` language is a variant of the `C` language focused on comments", "ccomment", CcommentCode, CcommentParser, @@ -125,6 +147,7 @@ mk_langs!( ), ( Preproc, + "The `PreProc` language is a variant of the `C/C++` language focused on macros", "preproc", PreprocCode, PreprocParser, diff --git a/src/lib.rs b/src/lib.rs index b35cdb9ef..0f3a0ff35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,49 @@ +//! rust-code-analysis is a library to analyze and extract information +//! from source codes written in many different programming languages. +//! +//! You can find the source code of this software on +//! GitHub, +//! while issues and feature requests can be posted on the respective +//! GitHub Issue Tracker. +//! +//! ## Supported Languages +//! +//! - C++ +//! - C# +//! - CSS +//! - Go +//! - HTML +//! - Java +//! - JavaScript +//! - The JavaScript used in Firefox internal +//! - Python +//! - Rust +//! - Typescript +//! +//! ## Supported Metrics +//! +//! - CC: it calculates the code complexity examining the +//! control flow of a program. +//! - SLOC: it counts the number of lines in a source file. +//! - PLOC: it counts the number of physical lines (instructions) +//! contained in a source file. +//! - LLOC: it counts the number of logical lines (statements) +//! contained in a source file. +//! - CLOC: it counts the number of comments in a source file. +//! - BLANK: it counts the number of blank lines in a source file. +//! - HALSTEAD: it is a suite that provides a series of information, +//! such as the effort required to maintain the analyzed code, +//! the size in bits to store the program, the difficulty to understand +//! the code, an estimate of the number of bugs present in the codebase, +//! and an estimate of the time needed to implement the software. +//! - MI: it is a suite that allows to evaluate the maintainability +//! of a software. +//! - NOM: it counts the number of functions and closures +//! in a file/trait/class. +//! - NEXITS: it counts the number of possible exit points +//! from a method/function. +//! - NARGS: it counts the number of arguments of a function/method. + #[macro_use] extern crate lazy_static; #[macro_use] @@ -7,20 +53,21 @@ extern crate serde_json; extern crate serde_yaml; extern crate toml; -pub(crate) mod c_macro; - #[macro_use] mod asttools; - +mod c_macro; #[macro_use] mod macros; -pub use crate::macros::*; +mod getter; + +mod alterator; +pub(crate) use alterator::*; -pub mod node; +mod node; pub use crate::node::*; mod metrics; -pub(crate) use metrics::*; +pub use metrics::*; mod languages; pub(crate) use languages::*; @@ -31,28 +78,22 @@ pub(crate) use checker::*; mod output; pub use output::*; -pub mod spaces; +mod spaces; pub use crate::spaces::*; -pub mod getter; -pub use crate::getter::*; - -pub mod find; +mod find; pub use crate::find::*; -pub mod function; +mod function; pub use crate::function::*; -mod alterator; -pub(crate) use crate::alterator::*; - mod ast; pub use crate::ast::*; -pub mod count; +mod count; pub use crate::count::*; -pub mod preproc; +mod preproc; pub use crate::preproc::*; mod langs; diff --git a/src/macros.rs b/src/macros.rs index 80f590864..59f5fdec7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,3 +1,4 @@ +#[doc(hidden)] #[macro_export] macro_rules! mk_checker { ( $name:ident, $( $type:ident ),* ) => { @@ -13,33 +14,39 @@ macro_rules! mk_checker { }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_extern { ( $( $name:ident ),* ) => { $( - extern "C" { pub fn $name() -> Language; } + extern "C" { pub(crate) fn $name() -> Language; } )* }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_enum { - ( $( $camel:ident ),* ) => { + ( $( $camel:ident, $description:expr ),* ) => { + /// The list of supported languages. #[derive(Clone, Copy, Debug, IntoEnumIterator, PartialEq)] pub enum LANG { $( + #[doc = $description] $camel, )* } }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_impl_lang { ( $( ($camel:ident, $name:ident, $display: expr) ),* ) => { impl LANG { - pub fn get_language(&self) -> Language { + #[allow(dead_code)] + pub(crate) fn get_language(&self) -> Language { unsafe { match self { $( @@ -49,6 +56,17 @@ macro_rules! mk_impl_lang { } } + /// Returns the name of a language as a `&str`. + /// + /// # Examples + /// + /// ``` + /// use rust_code_analysis::LANG; + /// + /// # fn main() { + /// println!("{}", LANG::Rust.get_name()); + /// # } + /// ``` pub fn get_name(&self) -> &'static str { match self { $( @@ -60,9 +78,44 @@ macro_rules! mk_impl_lang { }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_action { ( $( ($camel:ident, $parser:ident) ),* ) => { + /// Runs a function, which implements the [`Callback`] trait, + /// on a code written in one of the supported languages. + /// + /// # Examples + /// + /// The following example dumps to shell every metric computed using + /// the dummy source code. + /// + /// ``` + /// use std::path::PathBuf; + /// + /// use rust_code_analysis::{action, Callback, LANG, Metrics, MetricsCfg}; + /// + /// # fn main() { + /// let source_code = "int a = 42;"; + /// let language = LANG::Cpp; + /// + /// // The path to a dummy file used to contain the source code + /// let path = PathBuf::from("foo.c"); + /// let source_as_vec = source_code.as_bytes().to_vec(); + /// + /// // Configuration options used by the function which computes the metrics + /// let cfg = MetricsCfg { + /// path, + /// output_format: None, + /// pretty: false, + /// output_path: None, + /// }; + /// + /// action::(&language, source_as_vec, &cfg.path.clone(), None, cfg); + /// # } + /// ``` + /// + /// [`Callback`]: trait.Callback.html #[inline(always)] pub fn action(lang: &LANG, source: Vec, path: &PathBuf, pr: Option>, cfg: T::Cfg) -> T::Res { match lang { @@ -77,9 +130,23 @@ macro_rules! mk_action { }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_extensions { ( $( ($camel:ident, [ $( $ext:ident ),* ]) ),* ) => { + /// Detects the language associated to the input file extension. + /// + /// # Examples + /// + /// ``` + /// use rust_code_analysis::get_from_ext; + /// + /// # fn main() { + /// let ext = "rs"; + /// + /// get_from_ext(ext).unwrap(); + /// # } + /// ``` pub fn get_from_ext(ext: &str) -> Option{ match ext { $( @@ -93,9 +160,26 @@ macro_rules! mk_extensions { }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_emacs_mode { ( $( ($camel:ident, [ $( $emacs_mode:expr ),* ]) ),* ) => { + /// Detects the language associated to the input `Emacs` mode. + /// + /// An `Emacs` mode is used to detect a language according to + /// particular text-information contained in a file. + /// + /// # Examples + /// + /// ``` + /// use rust_code_analysis::get_from_emacs_mode; + /// + /// # fn main() { + /// let emacs_mode = "rust"; + /// + /// get_from_emacs_mode(emacs_mode).unwrap(); + /// # } + /// ``` pub fn get_from_emacs_mode(mode: &str) -> Option{ match mode { $( @@ -109,11 +193,12 @@ macro_rules! mk_emacs_mode { }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_code { - ( $( ($camel:ident, $code:ident, $parser:ident, $name:ident) ),* ) => { + ( $( ($camel:ident, $code:ident, $parser:ident, $name:ident, $docname:expr) ),* ) => { $( - pub struct $code { } + pub struct $code { _guard: (), } impl CodeMetricsT for $code { } impl TSLanguage for $code { @@ -131,24 +216,29 @@ macro_rules! mk_code { stringify!($camel) } } + #[doc = "The `"] + #[doc = $docname] + #[doc = "` language parser."] pub type $parser = TSParser<$code>; )* }; } +#[doc(hidden)] #[macro_export] macro_rules! mk_langs { - ( $( ($camel:ident, $display: expr, $code:ident, $parser:ident, $name:ident, [ $( $ext:ident ),* ], [ $( $emacs_mode:expr ),* ]) ),* ) => { + ( $( ($camel:ident, $description: expr, $display: expr, $code:ident, $parser:ident, $name:ident, [ $( $ext:ident ),* ], [ $( $emacs_mode:expr ),* ]) ),* ) => { mk_extern!($( $name ),*); - mk_enum!($( $camel ),*); + mk_enum!($( $camel, $description ),*); mk_impl_lang!($( ($camel, $name, $display) ),*); mk_action!($( ($camel, $parser) ),*); mk_extensions!($( ($camel, [ $( $ext ),* ]) ),*); mk_emacs_mode!($( ($camel, [ $( $emacs_mode ),* ]) ),*); - mk_code!($( ($camel, $code, $parser, $name) ),*); + mk_code!($( ($camel, $code, $parser, $name, stringify!($camel)) ),*); }; } +#[doc(hidden)] #[macro_export] macro_rules! color { ( $stdout: ident, $color: ident) => { @@ -163,6 +253,7 @@ macro_rules! color { }; } +#[doc(hidden)] #[macro_export] macro_rules! check_metrics { ($source: expr, $file: expr, $parser: ident, $metric: ident, diff --git a/src/metrics/cyclomatic.rs b/src/metrics/cyclomatic.rs index 7d829569c..92cbc0089 100644 --- a/src/metrics/cyclomatic.rs +++ b/src/metrics/cyclomatic.rs @@ -6,6 +6,7 @@ use tree_sitter::Node; use crate::checker::Checker; use crate::*; +/// The `Cyclomatic` metric. #[derive(Debug)] pub struct Stats { cyclomatic: f64, @@ -37,16 +38,19 @@ impl fmt::Display for Stats { } impl Stats { + /// Merges a second `Cyclomatic` metric into the first one pub fn merge(&mut self, other: &Stats) { self.cyclomatic += other.cyclomatic; self.n += other.n; } + /// Returns the `Cyclomatic` metric value pub fn cyclomatic(&self) -> f64 { self.cyclomatic / self.n as f64 } } +#[doc(hidden)] pub trait Cyclomatic where Self: Checker, diff --git a/src/metrics/exit.rs b/src/metrics/exit.rs index d6f4eeb45..23eed0a23 100644 --- a/src/metrics/exit.rs +++ b/src/metrics/exit.rs @@ -6,6 +6,10 @@ use tree_sitter::Node; use crate::checker::Checker; use crate::*; +/// The `NExit` metric. +/// +/// This metric counts the number of possible exit points +/// from a function/method. #[derive(Debug)] pub struct Stats { exit: usize, @@ -33,15 +37,18 @@ impl fmt::Display for Stats { } impl Stats { + /// Merges a second `NExit` metric into the first one pub fn merge(&mut self, other: &Stats) { self.exit += other.exit; } + /// Returns the `NExit` metric value pub fn exit(&self) -> f64 { self.exit as f64 } } +#[doc(hidden)] pub trait Exit where Self: Checker, diff --git a/src/metrics/fn_args.rs b/src/metrics/fn_args.rs index 393fb509b..1b43192b0 100644 --- a/src/metrics/fn_args.rs +++ b/src/metrics/fn_args.rs @@ -6,6 +6,10 @@ use tree_sitter::Node; use crate::checker::Checker; use crate::*; +/// The `NArgs` metric. +/// +/// This metric counts the number of arguments +/// of a function/method. #[derive(Debug)] pub struct Stats { n_args: usize, @@ -33,13 +37,16 @@ impl fmt::Display for Stats { } impl Stats { + #[doc(hidden)] pub fn merge(&mut self, _other: &Stats) {} + /// Returns the `NArgs` metric value pub fn n_args(&self) -> f64 { self.n_args as f64 } } +#[doc(hidden)] pub trait NArgs where Self: Checker, diff --git a/src/metrics/halstead.rs b/src/metrics/halstead.rs index 9003dd2d0..67367f66e 100644 --- a/src/metrics/halstead.rs +++ b/src/metrics/halstead.rs @@ -8,10 +8,11 @@ use crate::checker::Checker; use crate::*; +/// The `Halstead` metric suite. #[derive(Default, Debug)] pub struct Stats<'a> { - pub operators: FxHashMap, - pub operands: FxHashMap<&'a [u8], u64>, + operators: FxHashMap, + operands: FxHashMap<&'a [u8], u64>, } impl Serialize for Stats<'_> { @@ -75,6 +76,7 @@ impl<'a> fmt::Display for Stats<'a> { } impl<'a> Stats<'a> { + /// 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; @@ -84,78 +86,93 @@ impl<'a> Stats<'a> { } } + /// Returns `η1`, the number of distinct operators #[inline(always)] - pub fn u_operands(&self) -> f64 { - self.operands.len() as f64 + pub fn u_operators(&self) -> f64 { + self.operators.len() as f64 } + /// Returns `N1`, the number of total operators #[inline(always)] - pub fn operands(&self) -> f64 { - self.operands.values().sum::() as f64 + pub fn operators(&self) -> f64 { + self.operators.values().sum::() as f64 } + /// Returns `η2`, the number of distinct operands #[inline(always)] - pub fn u_operators(&self) -> f64 { - self.operators.len() as f64 + pub fn u_operands(&self) -> f64 { + self.operands.len() as f64 } + /// Returns `N2`, the number of total operands #[inline(always)] - pub fn operators(&self) -> f64 { - self.operators.values().sum::() as f64 + pub fn operands(&self) -> f64 { + self.operands.values().sum::() as f64 } + /// Returns the program length #[inline(always)] pub fn length(&self) -> f64 { self.operands() + self.operators() } + /// Returns the calculated estimated program length #[inline(always)] pub fn estimated_program_length(&self) -> f64 { self.u_operators() * self.u_operators().log2() + self.u_operands() * self.u_operands().log2() } + /// Returns the purity ratio #[inline(always)] pub fn purity_ratio(&self) -> f64 { self.estimated_program_length() / self.length() } + /// Returns the program vocabulary #[inline(always)] pub fn vocabulary(&self) -> f64 { self.u_operands() + self.u_operators() } + /// Returns the program volume #[inline(always)] pub fn volume(&self) -> f64 { self.length() * self.vocabulary().log2() } + /// Returns the estimated difficulty required to program #[inline(always)] pub fn difficulty(&self) -> f64 { self.u_operators() / 2. * self.operands() / self.u_operands() } + /// Returns the estimated level of difficulty required to program #[inline(always)] pub fn level(&self) -> f64 { 1. / self.difficulty() } + /// Returns the estimated effort required to program #[inline(always)] pub fn effort(&self) -> f64 { self.difficulty() * self.volume() } + /// Returns the estimated time required to program #[inline(always)] pub fn time(&self) -> f64 { self.effort() / 18. } + /// Returns the number of delivered bugs #[inline(always)] pub fn bugs(&self) -> f64 { self.effort().powf(2. / 3.) / 3000. } } +#[doc(hidden)] pub trait Halstead where Self: Checker, diff --git a/src/metrics/loc.rs b/src/metrics/loc.rs index 4af93de81..b386f0164 100644 --- a/src/metrics/loc.rs +++ b/src/metrics/loc.rs @@ -7,6 +7,7 @@ use tree_sitter::Node; use crate::*; +/// The `Loc` metric suite. #[derive(Debug, Default)] pub struct Stats { start: usize, @@ -47,6 +48,7 @@ impl fmt::Display for Stats { } impl Stats { + /// Merges a second `Loc` metric suite into the first one pub fn merge(&mut self, other: &Stats) { // Merge ploc lines for l in other.lines.iter() { @@ -60,11 +62,14 @@ impl Stats { self.comment_lines += other.comment_lines; } + /// The `Sloc` metric. + /// + /// Counts the number of lines in a scope #[inline(always)] pub fn sloc(&self) -> f64 { - // This metric counts the number of lines of a file - // The if construct is needed to count the line that represents - // the signature of a function in a function space + // This metric counts the number of lines in a file + // The if construct is needed to count the line of code that represents + // the function signature in a function space let sloc = if self.unit { self.end - self.start } else { @@ -73,20 +78,29 @@ impl Stats { sloc as f64 } + /// The `Ploc` metric. + /// + /// Counts the number of instruction lines in a scope #[inline(always)] pub fn ploc(&self) -> f64 { - // This metric counts the number of instruction lines present in the code + // This metric counts the number of instruction lines in a code // https://en.wikipedia.org/wiki/Source_lines_of_code self.lines.len() as f64 } + /// The `Lloc` metric. + /// + /// Counts the number of statements in a scope #[inline(always)] pub fn lloc(&self) -> f64 { - // This metric counts the number of statements present in the code + // This metric counts the number of statements in a code // https://en.wikipedia.org/wiki/Source_lines_of_code self.logical_lines as f64 } + /// The `Ploc` metric. + /// + /// Counts the number of comments in a scope #[inline(always)] pub fn cloc(&self) -> f64 { // Comments are counted regardless of their placement @@ -94,9 +108,12 @@ impl Stats { self.comment_lines as f64 } + /// The `Blank` metric. + /// + /// Counts the number of blank lines in a scope #[inline(always)] pub fn blank(&self) -> f64 { - // This metric counts the number of blank lines present in the code + // This metric counts the number of blank lines in a code // The if construct is needed because sometimes lloc and cloc // coincide on the same lines, in that case lloc + cloc could be greater // than the number of lines of a file. @@ -105,6 +122,7 @@ impl Stats { } } +#[doc(hidden)] pub trait Loc where Self: Checker, diff --git a/src/metrics/mi.rs b/src/metrics/mi.rs index f16e89b87..6661bb5d7 100644 --- a/src/metrics/mi.rs +++ b/src/metrics/mi.rs @@ -11,6 +11,7 @@ use crate::checker::Checker; use crate::*; +/// The `Mi` metric. #[derive(Default, Debug)] pub struct Stats { halstead_length: f64, @@ -47,18 +48,22 @@ impl fmt::Display for Stats { } impl Stats { + /// Merges a second `Mi` metric into the first one pub fn merge(&mut self, other: &Stats) { self.halstead_length += other.halstead_length; self.halstead_vocabulary += other.halstead_vocabulary; self.halstead_volume = self.halstead_length * self.halstead_vocabulary.log2(); } + /// Returns the `Mi` metric calculated using the original formula #[inline(always)] pub fn mi_original(&self) -> f64 { // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm 171.0 - 5.2 * (self.halstead_volume).ln() - 0.23 * self.cyclomatic - 16.2 * self.sloc.ln() } + /// Returns the `Mi` metric calculated using the derivative formula + /// employed by the Software Engineering Insitute (SEI) #[inline(always)] pub fn mi_sei(&self) -> f64 { // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm @@ -66,6 +71,8 @@ impl Stats { + 50.0 * (self.comments_percentage * 2.4).sqrt().sin() } + /// Returns the `Mi` metric calculated using the derivative formula + /// employed by Microsoft Visual Studio #[inline(always)] pub fn mi_visual_studio(&self) -> f64 { // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm @@ -77,6 +84,7 @@ impl Stats { } } +#[doc(hidden)] pub trait Mi where Self: Checker, diff --git a/src/metrics/nom.rs b/src/metrics/nom.rs index e05838c92..0fab0465c 100644 --- a/src/metrics/nom.rs +++ b/src/metrics/nom.rs @@ -7,6 +7,7 @@ use crate::checker::Checker; use crate::*; +/// The `Nom` metric suite. #[derive(Default, Debug)] pub struct Stats { functions: usize, @@ -41,28 +42,34 @@ impl fmt::Display for Stats { } impl Stats { + /// Merges a second `Nom` metric suite into the first one pub fn merge(&mut self, other: &Stats) { self.functions += other.functions; self.closures += other.closures; } + /// Counts the number of function definitions in a scope #[inline(always)] pub fn functions(&self) -> f64 { // Only function definitions are considered, not general declarations self.functions as f64 } + /// Counts the number of closures in a scope #[inline(always)] pub fn closures(&self) -> f64 { self.closures as f64 } + /// Returns the total number of function definitions and + /// closures in a scope #[inline(always)] pub fn total(&self) -> f64 { self.functions() + self.closures() } } +#[doc(hidden)] pub trait Nom where Self: Checker, diff --git a/src/output/dump.rs b/src/output/dump.rs index 2b87b6bf5..b2661bf54 100644 --- a/src/output/dump.rs +++ b/src/output/dump.rs @@ -4,6 +4,36 @@ use tree_sitter::Node; use crate::traits::*; +/// Dumps the `AST` of a code. +/// +/// Returns a [`Result`] value, when an error occurs. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::{dump_node, CppParser, TSParserTrait}; +/// +/// # fn main() { +/// let source_code = "int a = 42;"; +/// +/// // The path to a dummy file used to contain the source code +/// let path = PathBuf::from("foo.c"); +/// let source_as_vec = source_code.as_bytes().to_vec(); +/// +/// // The parser of the code, in this case a CPP parser +/// let parser = CppParser::new(source_as_vec.clone(), &path, None); +/// +/// // The root of the AST +/// let root = parser.get_root(); +/// +/// // Dump the AST from the first line of code in a file to the last one +/// dump_node(&source_as_vec, &root, -1, None, None).unwrap(); +/// # } +/// ``` +/// +/// [`Result`]: #variant.Result pub fn dump_node( code: &[u8], node: &Node, @@ -120,12 +150,23 @@ fn dump_tree_helper( Ok(()) } +/// Configuration options for dumping the `AST` of a code. pub struct DumpCfg { + /// The first line of code to dump + /// + /// If `None`, the code is dumped from the first line of code + /// in a file pub line_start: Option, + /// The last line of code to dump + /// + /// If `None`, the code is dumped until the last line of code + /// in a file pub line_end: Option, } -pub struct Dump {} +pub struct Dump { + _guard: (), +} impl Callback for Dump { type Res = std::io::Result<()>; diff --git a/src/output/dump_formats.rs b/src/output/dump_formats.rs index fc2e92635..bce5b96e1 100644 --- a/src/output/dump_formats.rs +++ b/src/output/dump_formats.rs @@ -8,14 +8,32 @@ use std::str::FromStr; use crate::spaces::FuncSpace; #[derive(Debug, Clone)] +/// The list of implemented output formats. pub enum Format { + /// The `CBOR` format Cbor, + /// The `JSON` format Json, + /// The `TOML` format Toml, + /// The `YAML` format Yaml, } impl Format { + /// Returns the list of implemented output formats as a slice of `&str`. + /// + /// # Examples + /// + /// ``` + /// use rust_code_analysis::Format; + /// + /// # fn main() { + /// for format in Format::all().iter(){ + /// println!("{}", format); + /// } + /// # } + /// ``` pub fn all() -> &'static [&'static str] { &["cbor", "json", "toml", "yaml"] } diff --git a/src/output/mod.rs b/src/output/mod.rs index 39ac9510a..df708d66c 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,7 +1,7 @@ -pub mod dump; +pub(crate) mod dump; pub use dump::*; -pub mod dump_formats; +pub(crate) mod dump_formats; pub use dump_formats::*; -pub mod dump_metrics; +pub(crate) mod dump_metrics; diff --git a/src/preproc.rs b/src/preproc.rs index f99260205..7c9299eca 100644 --- a/src/preproc.rs +++ b/src/preproc.rs @@ -12,19 +12,27 @@ use crate::traits::*; include!(concat!(env!("OUT_DIR"), "/gen_c_specials.rs")); +/// Preprocessor data of a `C/C++` file. #[derive(Debug, Default, Deserialize, Serialize)] pub struct PreprocFile { - pub includes: HashSet, - pub extra_includes: HashSet, + /// The set of include directives explicitly written in a file + pub direct_includes: HashSet, + /// The set of include directives implicitly imported in a file + /// from other files + pub indirect_includes: HashSet, + /// The set of macros of a file pub macros: HashSet, } +/// Preprocessor data of a series of `C/C++` files. #[derive(Debug, Default, Deserialize, Serialize)] pub struct PreprocResults { + /// The preprocessor data of each `C/C++` file pub files: HashMap, } impl PreprocFile { + /// Adds new macros to the set of macro of a file. pub fn new_macros(macros: &[&str]) -> Self { let mut pf = Self::default(); for m in macros { @@ -34,6 +42,7 @@ impl PreprocFile { } } +/// Returns the macros contained in a `C/C++` file. pub fn get_macros( file: &PathBuf, files: &HashMap, @@ -43,7 +52,7 @@ pub fn get_macros( for m in pf.macros.iter() { macros.insert(m.to_string()); } - for f in pf.extra_includes.iter() { + for f in pf.indirect_includes.iter() { if let Some(pf) = files.get(&PathBuf::from(f)) { for m in pf.macros.iter() { macros.insert(m.to_string()); @@ -54,6 +63,11 @@ pub fn get_macros( macros } +/// Constructs a dependency graph of the include directives +/// in a `C/C++` file. +/// +/// The dependency graph is built using both preprocessor data and not +/// extracted from the considered `C/C++` files. pub fn fix_includes( files: &mut HashMap, all_files: &HashMap, S>, @@ -69,8 +83,8 @@ pub fn fix_includes( hash_map::Entry::Occupied(l) => *l.get(), hash_map::Entry::Vacant(p) => *p.insert(g.add_node(file.clone())), }; - let includes = &pf.includes; - for i in includes { + let direct_includes = &pf.direct_includes; + for i in direct_includes { let possibilities = guess_file(&file, i, all_files); for i in possibilities { if &i != file { @@ -141,7 +155,7 @@ pub fn fix_includes( for (path, node) in nodes { let mut dfs = Dfs::new(&g, node); if let Some(pf) = files.get_mut(&path) { - let x_inc = &mut pf.extra_includes; + let x_inc = &mut pf.indirect_includes; while let Some(node) = dfs.next(&g) { let w = g.node_weight(node).unwrap(); if w == &PathBuf::from("") { @@ -163,6 +177,12 @@ pub fn fix_includes( } } +/// Extracts preprocessor data from a `C/C++` file +/// and inserts these data in a [`PreprocResults`] +/// object shared among threads. +/// +/// +/// [`PreprocResults`]: struct.PreprocResults.html pub fn preprocess(parser: &PreprocParser, path: &PathBuf, results: Arc>) { let node = parser.get_root(); let mut cursor = node.walk(); @@ -211,7 +231,7 @@ pub fn preprocess(parser: &PreprocParser, path: &PathBuf, results: Arc {} diff --git a/src/spaces.rs b/src/spaces.rs index 2bf8a2aae..43ca90ce3 100644 --- a/src/spaces.rs +++ b/src/spaces.rs @@ -17,16 +17,25 @@ use crate::dump_formats::*; use crate::dump_metrics::*; use crate::traits::*; +/// The list of supported space kinds. #[derive(Clone, Copy, Debug, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum SpaceKind { + /// An unknown space Unknown, + /// A function space Function, + /// A class space Class, + /// A struct space Struct, + /// A `Rust` trait space Trait, + /// A `Rust` implementation space Impl, + /// A general space Unit, + /// A `C/C++` namespace Namespace, } @@ -46,14 +55,22 @@ impl fmt::Display for SpaceKind { } } +/// All metrics data. #[derive(Debug, Serialize)] pub struct CodeMetrics<'a> { + /// `NArgs` data pub nargs: fn_args::Stats, + /// `NExits` data pub nexits: exit::Stats, + /// `Cyclomatic` data pub cyclomatic: cyclomatic::Stats, + /// `Halstead` data pub halstead: halstead::Stats<'a>, + /// `Loc` data pub loc: loc::Stats, + /// `Nom` data pub nom: nom::Stats, + /// `Mi` data pub mi: mi::Stats, } @@ -95,13 +112,23 @@ impl<'a> CodeMetrics<'a> { } } +/// Function space data. #[derive(Debug, Serialize)] pub struct FuncSpace<'a> { + /// The name of a function space + /// + /// If `None`, an error is occured in parsing + /// the name of a function space pub name: Option<&'a str>, + /// The first line of a function space pub start_line: usize, + /// The last line of a function space pub end_line: usize, + /// The space kind pub kind: SpaceKind, + /// All subspaces contained in a function space pub spaces: Vec>, + /// All metrics of a function space pub metrics: CodeMetrics<'a>, } @@ -140,6 +167,29 @@ fn finalize<'a>(space_stack: &mut Vec>, diff_level: usize) { } } +/// Returns function space data of the code in a file. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::{CppParser, metrics, TSParserTrait}; +/// +/// # fn main() { +/// let source_code = "int a = 42;"; +/// +/// // The path to a dummy file used to contain the source code +/// let path = PathBuf::from("foo.c"); +/// let source_as_vec = source_code.as_bytes().to_vec(); +/// +/// // The parser of the code, in this case a CPP parser +/// let parser = CppParser::new(source_as_vec.clone(), &path, None); +/// +/// // Gets the function space data of the code contained in foo.c +/// metrics(&parser, &path).unwrap(); +/// # } +/// ``` pub fn metrics<'a, T: TSParserTrait>(parser: &'a T, path: &'a PathBuf) -> Option> { let code = parser.get_code(); let node = parser.get_root(); @@ -208,14 +258,25 @@ pub fn metrics<'a, T: TSParserTrait>(parser: &'a T, path: &'a PathBuf) -> Option }) } +/// Configuration options for computing +/// the metrics of a code. pub struct MetricsCfg { + /// Path to the file containing the code pub path: PathBuf, + /// The output format pub output_format: Option, + /// If `true`, the `CBOR` and `JSON` output formats are + /// pretty-printed pub pretty: bool, + /// Path to the output file containing the metrics + /// + /// If `None`, the metrics are dumped on shell pub output_path: Option, } -pub struct Metrics {} +pub struct Metrics { + _guard: (), +} impl Callback for Metrics { type Res = std::io::Result<()>; diff --git a/src/tools.rs b/src/tools.rs index 33ce0ddef..691f85b00 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -7,6 +7,20 @@ use std::fs::File; use std::io::{Read, Write}; use std::path::{Component, Path, PathBuf}; +/// Reads a file. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::read_file; +/// +/// # fn main() { +/// let path = PathBuf::from("Cargo.toml"); +/// read_file(&path).unwrap(); +/// # } +/// ``` pub fn read_file(path: &PathBuf) -> std::io::Result> { let mut file = File::open(path)?; let mut data = Vec::new(); @@ -15,6 +29,20 @@ pub fn read_file(path: &PathBuf) -> std::io::Result> { Ok(data) } +/// Reads a file and adds an `EOL` at its end. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::read_file_with_eol; +/// +/// # fn main() { +/// let path = PathBuf::from("Cargo.toml"); +/// read_file_with_eol(&path).unwrap(); +/// # } +/// ``` pub fn read_file_with_eol(path: &PathBuf) -> std::io::Result> { let mut file = File::open(path)?; let mut data = Vec::new(); @@ -49,6 +77,21 @@ pub fn read_file_with_eol(path: &PathBuf) -> std::io::Result> { Ok(data) } +/// Writes data to a file. +/// +/// # Examples +/// +/// ```no_run +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::write_file; +/// +/// # fn main() { +/// let path = PathBuf::from("foo.txt"); +/// let data: [u8; 4] = [0; 4]; +/// write_file(&path, &data).unwrap(); +/// # } +/// ``` pub fn write_file(path: &PathBuf, data: &[u8]) -> std::io::Result<()> { let mut file = File::create(path)?; file.write_all(data)?; @@ -56,6 +99,21 @@ pub fn write_file(path: &PathBuf, data: &[u8]) -> std::io::Result<()> { Ok(()) } +/// Detects the language of a code using +/// the extension of a file. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::get_language_for_file; +/// +/// # fn main() { +/// let path = PathBuf::from("build.rs"); +/// get_language_for_file(&path).unwrap(); +/// # } +/// ``` pub fn get_language_for_file(path: &PathBuf) -> Option { if let Some(ext) = path.extension() { let ext = ext.to_str().unwrap().to_lowercase(); @@ -103,6 +161,31 @@ fn get_emacs_mode(buf: &[u8]) -> Option { None } +/// Guesses the language of a code. +/// +/// Returns a tuple containing a [`LANG`] as first argument +/// and the language name as a second one. +/// +/// # Examples +/// +/// ``` +/// use std::path::PathBuf; +/// +/// use rust_code_analysis::guess_language; +/// +/// # fn main() { +/// let source_code = "int a = 42;"; +/// +/// // The path to a dummy file used to contain the source code +/// let path = PathBuf::from("foo.c"); +/// let source_slice = source_code.as_bytes(); +/// +/// // Guess the language of a code +/// guess_language(&source_slice, &path); +/// # } +/// ``` +/// +/// [`LANG`]: enum.LANG.html pub fn guess_language>(buf: &[u8], path: P) -> (Option, String) { let ext = path .as_ref() @@ -146,7 +229,7 @@ pub fn guess_language>(buf: &[u8], path: P) -> (Option, Str } } -pub fn normalize_path>(path: P) -> Option { +pub(crate) fn normalize_path>(path: P) -> Option { // Copied from Cargo sources: https://github.com/rust-lang/cargo/blob/master/src/cargo/util/paths.rs#L65 let mut components = path.as_ref().components().peekable(); let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { @@ -174,7 +257,7 @@ pub fn normalize_path>(path: P) -> Option { Some(ret) } -pub fn get_paths_dist(path1: &PathBuf, path2: &PathBuf) -> Option { +pub(crate) fn get_paths_dist(path1: &PathBuf, path2: &PathBuf) -> Option { for ancestor in path1.ancestors() { if path2.starts_with(ancestor) && !ancestor.as_os_str().is_empty() { let path1 = path1.strip_prefix(ancestor).unwrap(); @@ -185,7 +268,7 @@ pub fn get_paths_dist(path1: &PathBuf, path2: &PathBuf) -> Option { None } -pub fn guess_file( +pub(crate) fn guess_file( current_path: &PathBuf, include_path: &str, all_files: &HashMap, S>, diff --git a/src/traits.rs b/src/traits.rs index b7da5e951..dc066c75b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -16,8 +16,24 @@ use crate::nom::Nom; use crate::preproc::PreprocResults; use crate::ts_parser::Filter; +/// A trait for callback functions. +/// +/// Allows to call a private library function, getting as result +/// its output value. +pub trait Callback { + /// The output type returned by the callee + type Res; + /// The input type used by the caller to pass the arguments to the callee + type Cfg; + + /// Calls a function inside the library and returns its value + fn call(cfg: Self::Cfg, parser: &T) -> Self::Res; +} + +#[doc(hidden)] pub trait CodeMetricsT: Cyclomatic + Exit + Halstead + NArgs + Loc + Nom + Mi {} +#[doc(hidden)] pub trait TSLanguage { type BaseLang; @@ -26,6 +42,7 @@ pub trait TSLanguage { fn get_lang_name() -> &'static str; } +#[doc(hidden)] pub trait TSParserTrait { type Checker: Alterator + Checker; type Getter: Getter; @@ -44,14 +61,7 @@ pub trait TSParserTrait { fn get_filters(&self, filters: &[String]) -> Filter; } -pub trait Callback { - type Res; - type Cfg; - - fn call(cfg: Self::Cfg, parser: &T) -> Self::Res; -} - -pub trait Search<'a> { +pub(crate) trait Search<'a> { fn first_occurence(&self, pred: fn(u16) -> bool) -> Option>; fn act_on_node(&self, pred: &mut dyn FnMut(&Node<'a>)); fn first_child(&self, pred: fn(u16) -> bool) -> Option>;