Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codem8"
version = "0.7.6"
version = "0.7.7"
edition = "2021"
rust-version = "1.85"
license = "MIT"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ codem8 --report-duplicate -file-extension=ts,js -files="src/a.ts,src/b.js"
Quoting `-files` values is recommended in PowerShell when paths contain file
extensions.

Analyze files changed on the current local Git branch compared to the origin
Analyze lines changed on the current local Git branch compared to the origin
base branch:

```bash
Expand Down Expand Up @@ -111,7 +111,7 @@ The default maximum cognitive complexity is 15, and the default maximum
cyclomatic complexity is 10. Use `-max-cognitive-complexity=<value>` and
`-max-cyclomatic-complexity=<value>` to adjust them.

Use `-git-branch` to analyze complexity only in supported files changed on the
Use `-git-branch` to analyze complexity only in supported lines changed on the
current local branch. The same origin branch resolution and `-files` exclusion
rules used by the duplicate report apply.

Expand All @@ -130,7 +130,7 @@ trailing Unicode whitespace are removed before hashing and comparison. Empty
trimmed lines are ignored. CodeM8 currently expects UTF-8 source files; invalid
UTF-8 produces a clear error rather than lossy output.

Use `-git-branch` to search duplicate code only in files changed on the current
Use `-git-branch` to search duplicate code only in lines changed on the current
local branch. CodeM8 resolves that branch set from `origin/HEAD` with
`origin/main` and `origin/master` fallbacks. The option requires a Git
repository and cannot be combined with `-files`.
Expand Down
65 changes: 5 additions & 60 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ struct ClapCli {
verbose: u8,
#[arg(long = "codem8-git-branch", action = ArgAction::Count)]
git_branch: u8,
#[arg(long = "codem8-git-branch-strict", action = ArgAction::Count)]
git_branch_strict: u8,
#[arg(
long = "codem8-file-extension",
value_name = "extensions",
Expand Down Expand Up @@ -64,17 +62,14 @@ where
let report = selected_report(&parsed)?;
validate_repeated_options(&parsed)?;
let git_branch = parsed.git_branch != 0;
let git_branch_strict = parsed.git_branch_strict != 0;
let files = selected_files(&parsed, git_branch || git_branch_strict)?;
validate_git_branch_modes(git_branch, git_branch_strict)?;
let files = selected_files(&parsed, git_branch)?;
validate_complexity_limits(report, &parsed)?;
Ok(CliConfig {
report,
verbose: parsed.verbose != 0,
file_extensions: selected_file_extensions(&parsed),
files,
git_branch,
git_branch_strict,
max_cognitive_complexity: parsed
.max_cognitive_complexity
.unwrap_or(DEFAULT_MAX_COGNITIVE_COMPLEXITY),
Expand Down Expand Up @@ -114,11 +109,6 @@ fn validate_repeated_options(parsed: &ClapCli) -> Result<()> {
"git branch mode was provided more than once",
));
}
if parsed.git_branch_strict > 1 {
return Err(CodeM8Error::new(
"strict git branch mode was provided more than once",
));
}
if parsed.file_extensions.len() > 1 {
return Err(CodeM8Error::new(
"file extensions were provided more than once",
Expand All @@ -132,15 +122,6 @@ fn validate_repeated_options(parsed: &ClapCli) -> Result<()> {
Ok(())
}

fn validate_git_branch_modes(git_branch: bool, git_branch_strict: bool) -> Result<()> {
if git_branch && git_branch_strict {
return Err(CodeM8Error::new(
"git branch mode and strict git branch mode are mutually exclusive",
));
}
Ok(())
}

fn selected_files(parsed: &ClapCli, git_branch: bool) -> Result<Option<Vec<PathBuf>>> {
let files = parsed.files.first().cloned();
if git_branch && files.is_some() {
Expand Down Expand Up @@ -279,8 +260,6 @@ fn normalized_clap_arg(arg: String) -> Result<String> {
Ok("--codem8-verbose".to_owned())
} else if arg == "-git-branch" {
Ok("--codem8-git-branch".to_owned())
} else if arg == "-git-branch-strict" {
Ok("--codem8-git-branch-strict".to_owned())
} else if let Some(value) = arg.strip_prefix("-file-extension=") {
Ok(format!("--codem8-file-extension={value}"))
} else if let Some(value) = arg.strip_prefix("-files=") {
Expand Down Expand Up @@ -310,7 +289,6 @@ mod tests {
assert_eq!(config.file_extensions, supported_file_extensions());
assert_eq!(config.files, None);
assert!(!config.git_branch);
assert!(!config.git_branch_strict);
assert_eq!(
config.max_cognitive_complexity,
DEFAULT_MAX_COGNITIVE_COMPLEXITY
Expand Down Expand Up @@ -358,16 +336,6 @@ mod tests {
fn parses_git_branch_duplicate_report_config() {
let config = parse_args(["--report-duplicate", "-git-branch"]).expect("config parses");
assert!(config.git_branch);
assert!(!config.git_branch_strict);
assert_eq!(config.files, None);
}

#[test]
fn parses_strict_git_branch_duplicate_report_config() {
let config =
parse_args(["--report-duplicate", "-git-branch-strict"]).expect("config parses");
assert!(!config.git_branch);
assert!(config.git_branch_strict);
assert_eq!(config.files, None);
}

Expand Down Expand Up @@ -419,7 +387,6 @@ mod tests {
"--file-extension=js",
"--files=src/a.ts",
"--git-branch",
"--git-branch-strict",
"--max-cognitive-complexity=20",
"--max-cyclomatic-complexity=12",
] {
Expand Down Expand Up @@ -495,23 +462,10 @@ mod tests {
}

#[test]
fn rejects_repeated_strict_git_branch_arguments() {
let error = parse_args([
"--report-duplicate",
"-git-branch-strict",
"-git-branch-strict",
])
.expect_err("repeated strict git branch mode fails");
assert!(error
.to_string()
.contains("strict git branch mode was provided more than once"));
}

#[test]
fn rejects_git_branch_with_strict_git_branch() {
let error = parse_args(["--report-duplicate", "-git-branch", "-git-branch-strict"])
.expect_err("exclusive git branch modes fail");
assert!(error.to_string().contains("mutually exclusive"));
fn rejects_removed_git_branch_strict_argument() {
let error = parse_args(["--report-duplicate", "-git-branch-strict"])
.expect_err("removed git branch mode fails");
assert!(error.to_string().contains("unexpected argument"));
}

#[test]
Expand All @@ -523,15 +477,6 @@ mod tests {
.contains("git branch mode cannot be combined with explicit files"));
}

#[test]
fn rejects_strict_git_branch_with_explicit_files() {
let error = parse_args(["--report-duplicate", "-git-branch-strict", "-files=a.ts"])
.expect_err("exclusive strict file modes fail");
assert!(error
.to_string()
.contains("git branch mode cannot be combined with explicit files"));
}

#[test]
fn parses_explicit_file_list() {
let files = parse_file_list("src/a.ts, ./src/b.ts").expect("files parse");
Expand Down
35 changes: 12 additions & 23 deletions src/cli/help.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::fmt::Write as _;

use super::version::codem8_version_from_cargo_lock;

const HELP_TEXT_BODY: &str = "\
USAGE:
codem8 help
codem8 -h
codem8 --version
codem8 --report-complexity [OPTIONS]
codem8 --report-duplicate [OPTIONS]

Expand All @@ -14,6 +11,9 @@ COMMANDS:
-h
Display this detailed documentation.

--version
Display the current CodeM8 version.

REQUIRED REPORT SWITCHES:
--report-complexity
Analyze supported source files and print a function complexity report.
Expand All @@ -35,13 +35,9 @@ OPTIONS:
Example: -files=\"src/a.ts,src/b.js\"

-git-branch
Search only in files changed on the current local Git
Limit the report to lines changed on the current local Git
branch. Cannot be combined with -files.

-git-branch-strict
Limit the report to lines changed on the current git branch.
Cannot be combined with -files or -git-branch.

-max-cognitive-complexity=<value>
Maximum allowed cognitive complexity for --report-complexity.
Defaults to 15.
Expand All @@ -52,7 +48,7 @@ OPTIONS:

-verbose
Include analyzed files and timings in report output, plus duplicate block details.
In -git-branch-strict mode, analyzed files include changed line ranges.
In -git-branch mode, analyzed files include changed line ranges.

COMPLEXITY REPORT PURPOSE:
The complexity report helps you find functions whose cognitive or cyclomatic
Expand All @@ -69,22 +65,16 @@ EXAMPLES:
codem8 --report-complexity
codem8 --report-complexity -file-extension=rs -max-cognitive-complexity=12
codem8 --report-complexity -git-branch
codem8 --report-complexity -git-branch-strict
codem8 --report-duplicate
codem8 --report-duplicate -file-extension=ts,tsx,js,jsx
codem8 --report-duplicate -file-extension=ts,js -files=\"src/a.ts,src/b.js\"
codem8 --report-duplicate -git-branch
codem8 --report-duplicate -git-branch-strict
";

#[must_use]
pub fn help_text() -> String {
let version = codem8_version_from_cargo_lock().unwrap_or("unknown");
let mut output = String::new();
let _ = writeln!(
output,
"CodeM8 {version} - deterministic source code analysis reports."
);
output.push_str("CodeM8 - deterministic source code analysis reports.\n");
output.push('\n');
output.push_str(HELP_TEXT_BODY);
output
Expand All @@ -93,7 +83,6 @@ pub fn help_text() -> String {
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::version::codem8_version_from_cargo_lock;

#[test]
fn exposes_detailed_help_text() {
Expand All @@ -107,7 +96,9 @@ mod tests {
fn assert_help_includes_expected_sections(help: &str) {
assert!(help.contains("USAGE:"));
assert!(help.contains("codem8 -h"));
assert!(help.contains("codem8 --version"));
assert!(help.contains(" -h"));
assert!(help.contains(" --version"));
assert!(help.contains("--report-duplicate"));
assert!(help.contains("--report-complexity"));
assert!(help.contains("helps you find repeated code"));
Expand All @@ -120,7 +111,6 @@ mod tests {
assert!(help.contains("-file-extension=<extensions>"));
assert!(help.contains("-files=<paths>"));
assert!(help.contains("-git-branch"));
assert!(help.contains("-git-branch-strict"));
assert!(help.contains("-max-cognitive-complexity=<value>"));
assert!(help.contains("-max-cyclomatic-complexity=<value>"));
}
Expand All @@ -130,7 +120,6 @@ mod tests {
assert!(!help.contains("--file-extension=<extensions>"));
assert!(!help.contains("--files=<paths>"));
assert!(!help.contains("--git-branch"));
assert!(!help.contains("--git-branch-strict"));
assert!(!help.contains("--max-cognitive-complexity=<value>"));
assert!(!help.contains("--max-cyclomatic-complexity=<value>"));
}
Expand Down Expand Up @@ -160,8 +149,8 @@ mod tests {
}

#[test]
fn help_text_includes_version_from_cargo_lock() {
let version = codem8_version_from_cargo_lock().expect("codem8 version exists");
assert!(help_text().starts_with(&format!("CodeM8 {version} - ")));
fn help_text_header_excludes_version() {
assert!(help_text().starts_with("CodeM8 - "));
assert!(!help_text().starts_with("CodeM8 0."));
}
}
12 changes: 11 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ mod version;

pub use args::{parse_args, parse_file_extensions, parse_file_list};
pub use help::help_text;
pub use version::codem8_version_from_cargo_lock;

use crate::error::Result;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CliCommand {
Help,
Report(CliConfig),
Version,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -28,7 +30,6 @@ pub struct CliConfig {
pub file_extensions: Vec<String>,
pub files: Option<Vec<PathBuf>>,
pub git_branch: bool,
pub git_branch_strict: bool,
pub max_cognitive_complexity: u32,
pub max_cyclomatic_complexity: u32,
}
Expand All @@ -48,6 +49,9 @@ where
if args.len() == 1 && is_help_argument(&args[0]) {
return Ok(CliCommand::Help);
}
if args.len() == 1 && args[0] == "--version" {
return Ok(CliCommand::Version);
}
parse_args(args).map(CliCommand::Report)
}

Expand All @@ -70,4 +74,10 @@ mod tests {
let command = parse_command(["-h"]).expect("short help parses");
assert_eq!(command, CliCommand::Help);
}

#[test]
fn parses_version_option() {
let command = parse_command(["--version"]).expect("version parses");
assert_eq!(command, CliCommand::Version);
}
}
3 changes: 2 additions & 1 deletion src/cli/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ struct CargoLockPackage<'a> {
version: &'a str,
}

pub(super) fn codem8_version_from_cargo_lock() -> Option<&'static str> {
#[must_use]
pub fn codem8_version_from_cargo_lock() -> Option<&'static str> {
cargo_lock_packages(CARGO_LOCK)
.find(|package| package.name == "codem8")
.map(|package| package.version)
Expand Down
Loading
Loading