Package implementation added
This commit is contained in:
@@ -13,5 +13,4 @@ Imports:
|
|||||||
cyclocomp,
|
cyclocomp,
|
||||||
dplyr,
|
dplyr,
|
||||||
lintr,
|
lintr,
|
||||||
rex,
|
|
||||||
tibble
|
tibble
|
||||||
|
|||||||
5
NAMESPACE
Normal file
5
NAMESPACE
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generated by roxygen2: do not edit by hand
|
||||||
|
|
||||||
|
export(complexityDir)
|
||||||
|
export(complexityFile)
|
||||||
|
importFrom(dplyr,.data)
|
||||||
20
R/complexityDir.R
Normal file
20
R/complexityDir.R
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#' Scans a directory for R files and run a cyclic complexity analyser on each
|
||||||
|
#' function found within those files
|
||||||
|
#' @param dirname <character> The directory to scan (relative or absolute)
|
||||||
|
#' @param sort <logical> Sort the output table by complexity
|
||||||
|
#' @export
|
||||||
|
complexityDir <- function(dirname = ".", sort = FALSE) {
|
||||||
|
files <- list.files(dirname, "\\.[Rr]$", full.names = TRUE, recursive = TRUE)
|
||||||
|
result_rows <- lapply(files, function(f) {
|
||||||
|
cat(".")
|
||||||
|
complexityFile(f)
|
||||||
|
})
|
||||||
|
result <- dplyr::bind_rows(result_rows)
|
||||||
|
cat("\n")
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
result <- dplyr::arrange(result, dplyr::desc(.data$complexity))
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
46
R/complexityFile.R
Normal file
46
R/complexityFile.R
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#' Scans a file and runs a cyclic complexity analyser on each function found
|
||||||
|
#' @param filename <character> The filename to scan (relative or absolute)
|
||||||
|
#' @export
|
||||||
|
complexityFile <- function(filename) {
|
||||||
|
exprs <- lintr::get_source_expressions(filename)
|
||||||
|
if (!is.null(exprs$error)) {
|
||||||
|
if (length(names(exprs$error)) > 0) {
|
||||||
|
exprs$error <- list(exprs$error)
|
||||||
|
}
|
||||||
|
complexity_rows <- lapply(exprs$error, function(exprerr) {
|
||||||
|
tibble::tibble(
|
||||||
|
where = paste(
|
||||||
|
filename, exprerr$line_number, exprerr$column_number,
|
||||||
|
sep = ":"
|
||||||
|
),
|
||||||
|
what = exprerr$message,
|
||||||
|
type = "FILE_ERROR",
|
||||||
|
complexity = .Machine$integer.max,
|
||||||
|
line = exprerr$line_number,
|
||||||
|
col = exprerr$column_number
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
file_expr <- exprs$expressions[[length(exprs$expressions)]]
|
||||||
|
complexity_rows <- dplyr::bind_rows(
|
||||||
|
getFunctionComplexities(file_expr),
|
||||||
|
getOutputComplexities(file_expr),
|
||||||
|
getObserverComplexities(file_expr)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
complexity_results <- dplyr::bind_rows(
|
||||||
|
complexity_rows,
|
||||||
|
tibble::tibble(
|
||||||
|
where = character(0),
|
||||||
|
what = character(0),
|
||||||
|
type = character(0),
|
||||||
|
complexity = integer(0),
|
||||||
|
line = integer(0),
|
||||||
|
col = integer(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dplyr::select(
|
||||||
|
dplyr::arrange(complexity_results, .data$line, .data$col),
|
||||||
|
-.data$line, -.data$col
|
||||||
|
)
|
||||||
|
}
|
||||||
280
R/complexityFunction.R
Normal file
280
R/complexityFunction.R
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
|
||||||
|
getFileSegmentComplexities <- function(filename) {
|
||||||
|
exprs <- lintr::get_source_expressions(filename)
|
||||||
|
|
||||||
|
last_expr <- length(exprs$expressions)
|
||||||
|
|
||||||
|
filter_expressions <- sapply(exprs$expressions[-last_expr], function(expr) {
|
||||||
|
nrow(expr$parsed_content) <= 1
|
||||||
|
})
|
||||||
|
filter_expressions <- which(filter_expressions)
|
||||||
|
|
||||||
|
complexity_rows <- lapply(
|
||||||
|
exprs$expressions[-c(filter_expressions, last_expr)],
|
||||||
|
function(expr) {
|
||||||
|
complexity <- cyclocomp::cyclocomp(parse(text = expr$content))
|
||||||
|
list(
|
||||||
|
where = paste0(expr$filename, ":", expr$line, ":", expr$column),
|
||||||
|
what = expr$parsed_content[2, "text"],
|
||||||
|
type = expr$parsed_content[2, "token"],
|
||||||
|
complexity = complexity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
result <- dplyr::bind_rows(
|
||||||
|
complexity_rows,
|
||||||
|
tibble::tibble(
|
||||||
|
where = character(0),
|
||||||
|
what = character(0),
|
||||||
|
type = character(0),
|
||||||
|
complexity = integer(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
dplyr::filter(
|
||||||
|
result, .data$type %in% c("FUNCTION", "SYMBOL")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpressionLines <- function(lines, line1, col1, line2, col2) {
|
||||||
|
scoped_lines <- lines[line1:line2]
|
||||||
|
|
||||||
|
fn_code <- c()
|
||||||
|
if (line1 == line2) {
|
||||||
|
# 1 lines
|
||||||
|
fn_code <- substr(scoped_lines[[1]], col1, col2)
|
||||||
|
} else if ((line2 - line1) == 1) {
|
||||||
|
# 2 lines
|
||||||
|
fn_code[[1]] <- substr(scoped_lines[[1]], col1, nchar(scoped_lines[[1]]))
|
||||||
|
fn_code[[2]] <- substr(scoped_lines[[2]], 0, col2)
|
||||||
|
} else {
|
||||||
|
# n lines
|
||||||
|
last_line <- length(scoped_lines)
|
||||||
|
fn_open <- substr(scoped_lines[[1]], col1, nchar(scoped_lines[[1]]))
|
||||||
|
fn_close <- substr(scoped_lines[[last_line]], 0, col2)
|
||||||
|
fn_code <- c(fn_open, scoped_lines[2:(last_line - 1)], fn_close)
|
||||||
|
}
|
||||||
|
|
||||||
|
paste0(fn_code, collapse = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
getFunctionComplexities <- function(file_expr) {
|
||||||
|
parsed_content <- tibble::as_tibble(file_expr$full_parsed_content)
|
||||||
|
|
||||||
|
functions <- dplyr::filter(parsed_content, .data$token == "FUNCTION")
|
||||||
|
|
||||||
|
function_exprs <- dplyr::filter(
|
||||||
|
parsed_content, .data$id %in% functions$parent
|
||||||
|
)
|
||||||
|
|
||||||
|
function_complexities <- lapply(
|
||||||
|
seq_len(nrow(function_exprs)),
|
||||||
|
function(idx) {
|
||||||
|
fn_expr <- function_exprs[idx, ]
|
||||||
|
|
||||||
|
has_left_assign_rows <- dplyr::filter(
|
||||||
|
parsed_content,
|
||||||
|
.data$token == "LEFT_ASSIGN",
|
||||||
|
.data$parent == fn_expr$parent
|
||||||
|
)
|
||||||
|
has_left_assign <- nrow(has_left_assign_rows) > 0
|
||||||
|
|
||||||
|
fn_name <- "[[anon_fn]]"
|
||||||
|
if (has_left_assign) {
|
||||||
|
name_expr <- dplyr::filter(
|
||||||
|
parsed_content,
|
||||||
|
.data$token == "expr",
|
||||||
|
.data$parent == fn_expr$parent,
|
||||||
|
.data$id != fn_expr$id
|
||||||
|
)
|
||||||
|
|
||||||
|
fn_name <- getExpressionLines(
|
||||||
|
file_expr$file_lines,
|
||||||
|
name_expr$line1,
|
||||||
|
name_expr$col1,
|
||||||
|
name_expr$line2,
|
||||||
|
name_expr$col2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_lines <- getExpressionLines(
|
||||||
|
file_expr$file_lines,
|
||||||
|
fn_expr$line1,
|
||||||
|
fn_expr$col1,
|
||||||
|
fn_expr$line2,
|
||||||
|
fn_expr$col2
|
||||||
|
)
|
||||||
|
|
||||||
|
fn_file <- tempfile()
|
||||||
|
con <- file(fn_file, open = "w", encoding = "utf8")
|
||||||
|
on.exit(unlink(fn_file), add = TRUE)
|
||||||
|
writeLines(text = fn_lines, con = con, sep = "\n")
|
||||||
|
close(con)
|
||||||
|
|
||||||
|
res <- getFileSegmentComplexities(fn_file)
|
||||||
|
|
||||||
|
if (nrow(res) == 0) {
|
||||||
|
print(file_expr$filename)
|
||||||
|
print(fn_lines)
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
tibble::tibble_row(
|
||||||
|
where = paste(
|
||||||
|
file_expr$filename, fn_expr$line1, fn_expr$col1, sep = ":"
|
||||||
|
),
|
||||||
|
what = fn_name,
|
||||||
|
type = "FUNCTION",
|
||||||
|
complexity = res$complexity,
|
||||||
|
line = fn_expr$line1,
|
||||||
|
col = fn_expr$col1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dplyr::bind_rows(function_complexities)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputComplexities <- function(file_expr) {
|
||||||
|
parsed_content <- tibble::as_tibble(file_expr$full_parsed_content)
|
||||||
|
|
||||||
|
outputs <- dplyr::filter(
|
||||||
|
parsed_content, .data$token == "SYMBOL", .data$text == "output"
|
||||||
|
)
|
||||||
|
|
||||||
|
output_exprs <- dplyr::filter(parsed_content, .data$id %in% outputs$parent)
|
||||||
|
|
||||||
|
output_complexities <- lapply(
|
||||||
|
seq_len(nrow(output_exprs)),
|
||||||
|
function(idx) {
|
||||||
|
op_expr <- output_exprs[idx, ]
|
||||||
|
|
||||||
|
has_accessor_rows <- dplyr::filter(
|
||||||
|
parsed_content, .data$token == "'$'", .data$parent == op_expr$parent
|
||||||
|
)
|
||||||
|
|
||||||
|
has_accessor <- nrow(has_accessor_rows) > 0
|
||||||
|
|
||||||
|
if (!has_accessor) {
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
op_name_rows <- dplyr::filter(
|
||||||
|
parsed_content, .data$token == "SYMBOL", .data$parent == op_expr$parent
|
||||||
|
)
|
||||||
|
op_name <- dplyr::pull(op_name_rows, .data$text)
|
||||||
|
|
||||||
|
op_expr_parent <- dplyr::filter(
|
||||||
|
parsed_content, .data$id == op_expr$parent
|
||||||
|
)
|
||||||
|
op_defn_expr <- dplyr::filter(
|
||||||
|
parsed_content,
|
||||||
|
.data$token == "expr",
|
||||||
|
.data$parent == op_expr_parent$parent,
|
||||||
|
.data$id != op_expr_parent$id
|
||||||
|
)
|
||||||
|
op_lines <- getExpressionLines(
|
||||||
|
file_expr$file_lines,
|
||||||
|
op_defn_expr$line1,
|
||||||
|
op_defn_expr$col1,
|
||||||
|
op_defn_expr$line2,
|
||||||
|
op_defn_expr$col2
|
||||||
|
)
|
||||||
|
|
||||||
|
op_file <- tempfile()
|
||||||
|
con <- file(op_file, open = "w", encoding = "utf8")
|
||||||
|
on.exit(unlink(op_file), add = TRUE)
|
||||||
|
writeLines(text = "function() {\n", con = con, sep = "\n")
|
||||||
|
writeLines(text = op_lines, con = con, sep = "\n")
|
||||||
|
writeLines(text = "\n}", con = con, sep = "\n")
|
||||||
|
close(con)
|
||||||
|
|
||||||
|
res <- getFileSegmentComplexities(op_file)
|
||||||
|
|
||||||
|
if (nrow(res) == 0) {
|
||||||
|
print(op_lines)
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
tibble::tibble_row(
|
||||||
|
where = paste(
|
||||||
|
file_expr$filename,
|
||||||
|
op_expr_parent$line1,
|
||||||
|
op_expr_parent$col1,
|
||||||
|
sep = ":"
|
||||||
|
),
|
||||||
|
what = op_name,
|
||||||
|
type = "OUTPUT",
|
||||||
|
complexity = res$complexity,
|
||||||
|
line = op_expr_parent$line1,
|
||||||
|
col = op_expr_parent$col1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dplyr::bind_rows(output_complexities)
|
||||||
|
}
|
||||||
|
|
||||||
|
getObserverComplexities <- function(file_expr) {
|
||||||
|
parsed_content <- tibble::as_tibble(file_expr$full_parsed_content)
|
||||||
|
|
||||||
|
observers <- dplyr::filter(
|
||||||
|
parsed_content,
|
||||||
|
.data$token == "SYMBOL_FUNCTION_CALL",
|
||||||
|
.data$text %in% c("observe", "observeEvent")
|
||||||
|
)
|
||||||
|
|
||||||
|
observer_exprs <- dplyr::filter(
|
||||||
|
parsed_content, .data$id %in% observers$parent
|
||||||
|
)
|
||||||
|
|
||||||
|
observer_complexities <- lapply(
|
||||||
|
seq_len(nrow(observer_exprs)),
|
||||||
|
function(idx) {
|
||||||
|
ob_expr <- observer_exprs[idx, ]
|
||||||
|
|
||||||
|
ob_expr_parent <- dplyr::filter(
|
||||||
|
parsed_content, .data$id == ob_expr$parent
|
||||||
|
)
|
||||||
|
ob_lines <- getExpressionLines(
|
||||||
|
file_expr$file_lines,
|
||||||
|
ob_expr_parent$line1,
|
||||||
|
ob_expr_parent$col1,
|
||||||
|
ob_expr_parent$line2,
|
||||||
|
ob_expr_parent$col2
|
||||||
|
)
|
||||||
|
|
||||||
|
op_file <- tempfile()
|
||||||
|
con <- file(op_file, open = "w", encoding = "utf8")
|
||||||
|
on.exit(unlink(op_file), add = TRUE)
|
||||||
|
writeLines(text = "function() {\n", con = con, sep = "\n")
|
||||||
|
writeLines(text = ob_lines, con = con, sep = "\n")
|
||||||
|
writeLines(text = "\n}", con = con, sep = "\n")
|
||||||
|
close(con)
|
||||||
|
|
||||||
|
res <- getFileSegmentComplexities(op_file)
|
||||||
|
|
||||||
|
if (nrow(res) == 0) {
|
||||||
|
print(ob_lines)
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
tibble::tibble_row(
|
||||||
|
where = paste(
|
||||||
|
file_expr$filename,
|
||||||
|
ob_expr_parent$line1,
|
||||||
|
ob_expr_parent$col1,
|
||||||
|
sep = ":"
|
||||||
|
),
|
||||||
|
what = "[[observer]]",
|
||||||
|
type = "OBSERVER",
|
||||||
|
complexity = res$complexity,
|
||||||
|
line = ob_expr_parent$line1,
|
||||||
|
col = ob_expr_parent$col1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dplyr::bind_rows(observer_complexities)
|
||||||
|
}
|
||||||
35
README.Rmd
Normal file
35
README.Rmd
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
output: github_document
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- README.md is generated from README.Rmd. Please edit that file -->
|
||||||
|
|
||||||
|
```{r, include = FALSE}
|
||||||
|
knitr::opts_chunk$set(
|
||||||
|
collapse = TRUE,
|
||||||
|
comment = "#>",
|
||||||
|
fig.path = "man/figures/README-",
|
||||||
|
out.width = "100%"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# RComplexity
|
||||||
|
|
||||||
|
<!-- badges: start -->
|
||||||
|
<!-- badges: end -->
|
||||||
|
|
||||||
|
The goal of RComplexity is to scan a directory or file for R functions provide a cyclic complexity score for each.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install the development version of RComplexity like so:
|
||||||
|
|
||||||
|
``` r
|
||||||
|
remotes::install_git("https://git.avsdev.uk/R/RComplexity")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```{r example}
|
||||||
|
RComplexity::complexityDir("R", TRUE)
|
||||||
|
```
|
||||||
41
README.md
Normal file
41
README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
<!-- README.md is generated from README.Rmd. Please edit that file -->
|
||||||
|
|
||||||
|
# RComplexity
|
||||||
|
|
||||||
|
<!-- badges: start -->
|
||||||
|
<!-- badges: end -->
|
||||||
|
|
||||||
|
The goal of RComplexity is to scan a directory or file for R functions
|
||||||
|
provide a cyclic complexity score for each.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install the development version of RComplexity like so:
|
||||||
|
|
||||||
|
``` r
|
||||||
|
remotes::install_git("https://git.avsdev.uk/R/RComplexity")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
``` r
|
||||||
|
RComplexity::complexityDir("R", TRUE)
|
||||||
|
#> ...
|
||||||
|
#> # A tibble: 13 × 4
|
||||||
|
#> where what type complexity
|
||||||
|
#> <chr> <chr> <chr> <int>
|
||||||
|
#> 1 R/complexityFunction.R:141:26 getOutputComplexities FUNCTION 5
|
||||||
|
#> 2 R/complexityFunction.R:152:5 [[anon_fn]] FUNCTION 5
|
||||||
|
#> 3 R/complexityFunction.R:64:28 getFunctionComplexities FUNCTION 4
|
||||||
|
#> 4 R/complexityFunction.R:75:5 [[anon_fn]] FUNCTION 4
|
||||||
|
#> 5 R/complexityFunction.R:42:23 getExpressionLines FUNCTION 3
|
||||||
|
#> 6 R/complexityFunction.R:221:28 getObserverComplexities FUNCTION 3
|
||||||
|
#> 7 R/complexityFunction.R:234:5 [[anon_fn]] FUNCTION 3
|
||||||
|
#> 8 R/complexityDir.R:6:18 complexityDir FUNCTION 2
|
||||||
|
#> 9 R/complexityFunction.R:2:24 getFileComplexities FUNCTION 2
|
||||||
|
#> 10 R/complexityDir.R:11:32 [[anon_fn]] FUNCTION 1
|
||||||
|
#> 11 R/complexityFile.R:4:19 complexityFile FUNCTION 1
|
||||||
|
#> 12 R/complexityFunction.R:6:63 [[anon_fn]] FUNCTION 1
|
||||||
|
#> 13 R/complexityFunction.R:13:5 [[anon_fn]] FUNCTION 1
|
||||||
|
```
|
||||||
18
man/complexityDir.Rd
Normal file
18
man/complexityDir.Rd
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/complexityDir.R
|
||||||
|
\name{complexityDir}
|
||||||
|
\alias{complexityDir}
|
||||||
|
\title{Scans a directory for R files and run a cyclic complexity analyser on each
|
||||||
|
function found within those files}
|
||||||
|
\usage{
|
||||||
|
complexityDir(dirname = ".", sort = FALSE)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{dirname}{\if{html}{\out{<character>}} The directory to scan (relative or absolute)}
|
||||||
|
|
||||||
|
\item{sort}{\if{html}{\out{<logical>}} Sort the output table by complexity}
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Scans a directory for R files and run a cyclic complexity analyser on each
|
||||||
|
function found within those files
|
||||||
|
}
|
||||||
14
man/complexityFile.Rd
Normal file
14
man/complexityFile.Rd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
% Generated by roxygen2: do not edit by hand
|
||||||
|
% Please edit documentation in R/complexityFile.R
|
||||||
|
\name{complexityFile}
|
||||||
|
\alias{complexityFile}
|
||||||
|
\title{Scans a file and runs a cyclic complexity analyser on each function found}
|
||||||
|
\usage{
|
||||||
|
complexityFile(filename)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{filename}{\if{html}{\out{<character>}} The filename to scan (relative or absolute)}
|
||||||
|
}
|
||||||
|
\description{
|
||||||
|
Scans a file and runs a cyclic complexity analyser on each function found
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user