Skip to content

Commit 63d1cce

Browse files
fabian-sjimhester
authored andcommitted
361 cyclocomp linter (#396)
* [WIP] linter works * adds linting for cyclomatic complexity. incurs dependency on cyclocomp. closes #361. * updates NEWS for cyclocomp_linter * less verbose message for cyclocomp_linter * update tests to new message
1 parent 2b1bccd commit 63d1cce

File tree

9 files changed

+110
-13
lines changed

9 files changed

+110
-13
lines changed

DESCRIPTION

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Imports:
1616
rex,
1717
crayon,
1818
codetools,
19+
cyclocomp,
1920
stringdist,
2021
testthat,
2122
digest,
@@ -47,6 +48,7 @@ Collate:
4748
'commas_linter.R'
4849
'comment_linters.R'
4950
'comments.R'
51+
'cyclocomp_linter.R'
5052
'object_name_linters.R'
5153
'deprecated.R'
5254
'equals_na_lintr.R'

NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export(clear_cache)
2020
export(closed_curly_linter)
2121
export(commas_linter)
2222
export(commented_code_linter)
23+
export(cyclocomp_linter)
2324
export(default_linters)
2425
export(default_settings)
2526
export(default_undesirable_functions)
@@ -58,6 +59,7 @@ export(undesirable_operator_linter)
5859
export(unneeded_concatenation_linter)
5960
export(with_defaults)
6061
import(rex)
62+
importFrom(cyclocomp,cyclocomp)
6163
importFrom(stats,na.omit)
6264
importFrom(utils,capture.output)
6365
importFrom(utils,getParseData)

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# lintr 1.0.3.9000 #
2+
* New cyclocomp_linter() identifies overly complex functions (#361, @fabian-s)
23
* Add support for overriding GitHub API Token via `GITHUB_TOKEN` environment
34
variable (#63, @mattyb)
45
* Changed the default value of the `length` argument to `object_length_linter` to 30 for consistency (#325 @DragosMG)

R/cyclocomp_linter.R

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#' @describeIn linters Check for overly complicated expressions. See
2+
#' \code{\link[cyclocomp]{cyclocomp}}.
3+
#' @param complexity_limit expressions with a cyclomatic complexity higher than
4+
#' this are linted, defaults to 25. See \code{\link[cyclocomp]{cyclocomp}}.
5+
#' @importFrom cyclocomp cyclocomp
6+
#' @export
7+
cyclocomp_linter <- function(complexity_limit = 25) {
8+
function(source_file) {
9+
if (!is.null(source_file[["file_lines"]])) {
10+
# abort if source_file is entire file, not a top level expression.
11+
return(NULL)
12+
}
13+
complexity <- try_silently(
14+
cyclocomp::cyclocomp(parse(text = source_file$content))
15+
)
16+
if (inherits(complexity, "try-error")) return(NULL)
17+
if (complexity <= complexity_limit) return(NULL)
18+
Lint(
19+
filename = source_file[["filename"]],
20+
line_number = source_file[["line"]][1],
21+
column_number = source_file[["column"]][1],
22+
type = "style",
23+
message = paste0(
24+
"functions should have cyclomatic complexity of less than ",
25+
complexity_limit, ", this has ", complexity,"."
26+
),
27+
ranges = list(c(NA, NA)),
28+
line = source_file$lines[1],
29+
linter = "cyclocomp_linter"
30+
)
31+
}
32+
}

R/zzz.R

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ default_linters <- with_defaults(default = list(),
8989
closed_curly_linter(),
9090
commas_linter,
9191
commented_code_linter,
92+
cyclocomp_linter(15),
9293
equals_na_linter,
9394
function_left_parentheses_linter,
9495
infix_spaces_linter,

inst/example/complexity.R

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
complexity <- function(x) {
2+
if (x > 0) {
3+
if (x > 10) {
4+
if (x > 20) {
5+
x <- x / 2
6+
} else {
7+
return(x)
8+
}
9+
} else {
10+
return(x)
11+
}
12+
} else {
13+
if (x < -10) {
14+
if (x < -20) {
15+
x <- x * 2
16+
} else {
17+
return(x)
18+
}
19+
} else {
20+
return(x)
21+
}
22+
}
23+
x
24+
}

man/default_linters.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/linters.Rd

+21-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
context("cyclocomp_linter")
2+
3+
test_that("returns the correct linting", {
4+
cc_linter_1 <- cyclocomp_linter(1)
5+
cc_linter_2 <- cyclocomp_linter(2)
6+
msg <- rex("functions should have cyclomatic complexity")
7+
8+
expect_lint("if(TRUE) 1 else 2", NULL, cc_linter_2)
9+
expect_lint("if(TRUE) 1 else 2", msg, cc_linter_1)
10+
11+
expect_lint(
12+
"function(x) {not parsing}",
13+
"unexpected symbol", cc_linter_2
14+
)
15+
16+
complexity <- readLines(
17+
system.file("example/complexity.R", package = "lintr")
18+
)
19+
expect_lint(complexity, msg, cc_linter_2)
20+
expect_lint(
21+
complexity,
22+
"should have cyclomatic complexity of less than 2, this has 10",
23+
cc_linter_2
24+
)
25+
expect_lint(complexity, NULL, cyclocomp_linter(10))
26+
})

0 commit comments

Comments
 (0)