Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Imports:
rex,
crayon,
codetools,
cyclocomp,
stringdist,
testthat,
digest,
Expand Down Expand Up @@ -47,6 +48,7 @@ Collate:
'commas_linter.R'
'comment_linters.R'
'comments.R'
'cyclocomp_linter.R'
'object_name_linters.R'
'deprecated.R'
'equals_na_lintr.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export(clear_cache)
export(closed_curly_linter)
export(commas_linter)
export(commented_code_linter)
export(cyclocomp_linter)
export(default_linters)
export(default_settings)
export(default_undesirable_functions)
Expand Down Expand Up @@ -58,6 +59,7 @@ export(undesirable_operator_linter)
export(unneeded_concatenation_linter)
export(with_defaults)
import(rex)
importFrom(cyclocomp,cyclocomp)
importFrom(stats,na.omit)
importFrom(utils,capture.output)
importFrom(utils,getParseData)
Expand Down
34 changes: 34 additions & 0 deletions R/cyclocomp_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#' @describeIn linters Check for overly complicated expressions. See
#' \code{\link[cyclocomp]{cyclocomp}}.
#' @param complexity_limit expressions with a cyclomatic complexity higher than
#' this are linted, defaults to 25. See \code{\link[cyclocomp]{cyclocomp}}.
#' @importFrom cyclocomp cyclocomp
#' @export
cyclocomp_linter <- function(complexity_limit = 25) {
function(source_file) {
if (!is.null(source_file[["file_lines"]])) {
# abort if source_file is entire file, not a top level expression.
return(NULL)
}
complexity <- try_silently(
cyclocomp::cyclocomp(parse(text = source_file$content))
)
if (inherits(complexity, "try-error")) return(NULL)
if (complexity <= complexity_limit) return(NULL)
Lint(
filename = source_file[["filename"]],
line_number = source_file[["line"]][1],
column_number = source_file[["column"]][1],
type = "style",
message = paste0(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too long, maybe

"functions should have cyclomatic complexity of less than", complexity_limit, "."

"Write short and simple functions and avoid complex conditionals,",
"e.g. by encapsulating distinct steps into subfunctions.",
"The expression starting on this line has a cyclomatic complexity of ",
complexity, ", should be at most ", complexity_limit, "."
),
ranges = list(c(NA, NA)),
line = source_file$lines[1],
linter = "cyclocomp_linter"
)
}
}
1 change: 1 addition & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ default_linters <- with_defaults(default = list(),
closed_curly_linter(),
commas_linter,
commented_code_linter,
cyclocomp_linter(15),
equals_na_linter,
function_left_parentheses_linter,
infix_spaces_linter,
Expand Down
24 changes: 24 additions & 0 deletions inst/example/complexity.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
complexity <- function(x) {
if (x > 0) {
if (x > 10) {
if (x > 20) {
x <- x / 2
} else {
return(x)
}
} else {
return(x)
}
} else {
if (x < -10) {
if (x < -20) {
x <- x * 2
} else {
return(x)
}
} else {
return(x)
}
}
x
}
2 changes: 1 addition & 1 deletion man/default_linters.Rd

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

33 changes: 21 additions & 12 deletions man/linters.Rd

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

26 changes: 26 additions & 0 deletions tests/testthat/test-cyclocomp_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
context("cyclocomp_linter")

test_that("returns the correct linting", {
cc_linter_1 <- cyclocomp_linter(1)
cc_linter_2 <- cyclocomp_linter(2)
msg <- rex("Write short and simple functions")

expect_lint("if(TRUE) 1 else 2", NULL, cc_linter_2)
expect_lint("if(TRUE) 1 else 2", msg, cc_linter_1)

expect_lint(
"function(x) {not parsing}",
"unexpected symbol", cc_linter_2
)

complexity <- readLines(
system.file("example/complexity.R", package = "lintr")
)
expect_lint(complexity, msg, cc_linter_2)
expect_lint(
complexity,
"cyclomatic complexity of 10, should be at most 2",
cc_linter_2
)
expect_lint(complexity, NULL, cyclocomp_linter(10))
})