PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/R/diagnose.R

https://gitlab.com/kumarsiva07/shiny
R | 157 lines | 117 code | 22 blank | 18 comment | 54 complexity | 776e0c0c4e11677b365eeb18dd7754cc MD5 | raw file
  1. # Analyze an R file for possible extra or missing commas. Returns FALSE if any
  2. # problems detected, TRUE otherwise.
  3. diagnoseCode <- function(path = NULL, text = NULL) {
  4. if (!xor(is.null(path), is.null(text))) {
  5. stop("Must specify `path` or `text`, but not both.")
  6. }
  7. if (!is.null(path)) {
  8. tokens <- sourcetools::tokenize_file(path)
  9. } else {
  10. tokens <- sourcetools::tokenize_string(text)
  11. }
  12. find_scopes <- function(tokens) {
  13. # Strip whitespace and comments
  14. tokens <- tokens[!(tokens$type %in% c("whitespace", "comment")),]
  15. # Replace various types of things with "value"
  16. tokens$type[tokens$type %in% c("string", "number", "symbol", "keyword")] <- "value"
  17. # Record types for close and open brace/bracket/parens, and commas
  18. brace_idx <- tokens$value %in% c("(", ")", "{", "}", "[", "]", ",")
  19. tokens$type[brace_idx] <- tokens$value[brace_idx]
  20. # Stack-related function for recording scope. Starting scope is "{"
  21. stack <- "{"
  22. push <- function(x) {
  23. stack <<- c(stack, x)
  24. }
  25. pop <- function() {
  26. if (length(stack) == 1) {
  27. # Stack underflow, but we need to keep going
  28. return(NA_character_)
  29. }
  30. res <- stack[length(stack)]
  31. stack <<- stack[-length(stack)]
  32. res
  33. }
  34. peek <- function() {
  35. stack[length(stack)]
  36. }
  37. # First, establish a scope for each token. For opening and closing
  38. # braces/brackets/parens, the scope at that location is the *surrounding*
  39. # scope, not the new scope created by the brace/bracket/paren.
  40. for (i in seq_len(nrow(tokens))) {
  41. value <- tokens$value[i]
  42. tokens$scope[i] <- peek()
  43. if (value %in% c("{", "(", "[")) {
  44. push(value)
  45. } else if (value == "}") {
  46. if (!identical(pop(), "{"))
  47. tokens$err[i] <- "unmatched_brace"
  48. # For closing brace/paren/bracket, get the scope after popping
  49. tokens$scope[i] <- peek()
  50. } else if (value == ")") {
  51. if (!identical(pop(), "("))
  52. tokens$err[i] <- "unmatched_paren"
  53. tokens$scope[i] <- peek()
  54. } else if (value == "]") {
  55. if (!identical(pop(), "["))
  56. tokens$err[i] <- "unmatched_bracket"
  57. tokens$scope[i] <- peek()
  58. }
  59. }
  60. tokens
  61. }
  62. check_commas <- function(tokens) {
  63. # Find extra and missing commas
  64. tokens$err <- mapply(
  65. tokens$type,
  66. c("", tokens$type[-length(tokens$type)]),
  67. c(tokens$type[-1], ""),
  68. tokens$scope,
  69. tokens$err,
  70. SIMPLIFY = FALSE,
  71. FUN = function(type, prevType, nextType, scope, err) {
  72. # If an error was already found, just return it. This could have
  73. # happened in the brace/paren/bracket matching phase.
  74. if (!is.na(err)) {
  75. return(err)
  76. }
  77. if (scope == "(") {
  78. if (type == "," &&
  79. (prevType == "(" || prevType == "," || nextType == ")"))
  80. {
  81. return("extra_comma")
  82. }
  83. if ((prevType == ")" && type == "value") ||
  84. (prevType == "value" && type == "value")) {
  85. return("missing_comma")
  86. }
  87. }
  88. NA_character_
  89. }
  90. )
  91. tokens
  92. }
  93. tokens$err <- NA_character_
  94. tokens <- find_scopes(tokens)
  95. tokens <- check_commas(tokens)
  96. # No errors found
  97. if (all(is.na(tokens$err))) {
  98. return(TRUE)
  99. }
  100. # If we got here, errors were found; print messages.
  101. if (!is.null(path)) {
  102. lines <- readLines(path)
  103. } else {
  104. lines <- strsplit(text, "\n")[[1]]
  105. }
  106. # Print out the line of code with the error, and point to the column with
  107. # the error.
  108. show_code_error <- function(msg, lines, row, col) {
  109. message(paste0(
  110. msg, "\n",
  111. row, ":", lines[row], "\n",
  112. paste0(rep.int(" ", nchar(as.character(row)) + 1), collapse = ""),
  113. gsub(perl = TRUE, "[^\\s]", " ", substr(lines[row], 1, col-1)), "^"
  114. ))
  115. }
  116. err_idx <- which(!is.na(tokens$err))
  117. msg <- ""
  118. for (i in err_idx) {
  119. row <- tokens$row[i]
  120. col <- tokens$column[i]
  121. err <- tokens$err[i]
  122. if (err == "missing_comma") {
  123. show_code_error("Possible missing comma at:", lines, row, col)
  124. } else if (err == "extra_comma") {
  125. show_code_error("Possible extra comma at:", lines, row, col)
  126. } else if (err == "unmatched_brace") {
  127. show_code_error("Possible unmatched '}' at:", lines, row, col)
  128. } else if (err == "unmatched_paren") {
  129. show_code_error("Possible unmatched ')' at:", lines, row, col)
  130. } else if (err == "unmatched_bracket") {
  131. show_code_error("Possible unmatched ']' at:", lines, row, col)
  132. }
  133. }
  134. return(FALSE)
  135. }