|
| 1 | +test_that("vector_logic_linter skips allowed usages", { |
| 2 | + linter <- vector_logic_linter() |
| 3 | + |
| 4 | + expect_no_lint("if (TRUE) 5 else if (TRUE) 2", linter) |
| 5 | + expect_no_lint("if (TRUE || FALSE) 1; while (TRUE && FALSE) 2", linter) |
| 6 | + |
| 7 | + # function calls and extractions may aggregate to scalars -- only catch |
| 8 | + # usages at the highest logical level |
| 9 | + expect_no_lint("if (agg_function(x & y)) 1", linter) |
| 10 | + expect_no_lint("if (DT[x | y, cond]) 1", linter) |
| 11 | + |
| 12 | + # don't match potentially OK usages nested within calls |
| 13 | + expect_no_lint("if (TRUE && any(TRUE | FALSE)) 4", linter) |
| 14 | + # even if the usage is nested in those calls (b/181915948) |
| 15 | + expect_no_lint("if (TRUE && any(TRUE | FALSE | TRUE)) 4", linter) |
| 16 | + |
| 17 | + # don't match potentially OK usages in the branch itself |
| 18 | + expect_no_lint( |
| 19 | + trim_some( |
| 20 | + " |
| 21 | + if (TRUE) { |
| 22 | + x | y |
| 23 | + } |
| 24 | + " |
| 25 | + ), |
| 26 | + linter |
| 27 | + ) |
| 28 | + |
| 29 | + # valid nested usage within aggregator |
| 30 | + expect_no_lint("testthat::expect_false(any(TRUE | TRUE))", linter) |
| 31 | +}) |
| 32 | + |
| 33 | +test_that("vector_logic_linter blocks simple disallowed usages", { |
| 34 | + linter <- vector_logic_linter() |
| 35 | + |
| 36 | + expect_lint( |
| 37 | + "if (TRUE & FALSE) 1", |
| 38 | + "Use `&&` in conditional expressions.", |
| 39 | + linter |
| 40 | + ) |
| 41 | + expect_lint( |
| 42 | + "while (TRUE | TRUE) 2", |
| 43 | + "Use `||` in conditional expressions.", |
| 44 | + linter |
| 45 | + ) |
| 46 | +}) |
| 47 | + |
| 48 | +test_that("vector_logic_linter detects nested conditions", { |
| 49 | + linter <- vector_logic_linter() |
| 50 | + |
| 51 | + expect_lint( |
| 52 | + "if (TRUE & TRUE || FALSE) 4", |
| 53 | + "Use `&&` in conditional expressions.", |
| 54 | + linter |
| 55 | + ) |
| 56 | + expect_lint( |
| 57 | + "if (TRUE && (TRUE | FALSE)) 4", |
| 58 | + "Use `||` in conditional expressions.", |
| 59 | + linter |
| 60 | + ) |
| 61 | +}) |
| 62 | + |
| 63 | +test_that("vector_logic_linter catches usages in expect_true()/expect_false()", { |
| 64 | + linter <- vector_logic_linter() |
| 65 | + and_msg <- "Use `&&` in conditional expressions." |
| 66 | + or_msg <- "Use `||` in conditional expressions." |
| 67 | + |
| 68 | + expect_lint("expect_true(TRUE & FALSE)", and_msg, linter) |
| 69 | + expect_lint("expect_false(TRUE | TRUE)", or_msg, linter) |
| 70 | + |
| 71 | + # ditto with namespace qualification |
| 72 | + expect_lint("testthat::expect_true(TRUE & FALSE)", and_msg, linter) |
| 73 | + expect_lint("testthat::expect_false(TRUE | TRUE)", or_msg, linter) |
| 74 | +}) |
| 75 | + |
| 76 | +test_that("vector_logic_linter doesn't get mixed up from complex usage", { |
| 77 | + expect_no_lint( |
| 78 | + trim_some( |
| 79 | + " |
| 80 | + if (a) { |
| 81 | + expect_true(ok) |
| 82 | + x <- 2 |
| 83 | + a | b |
| 84 | + } |
| 85 | + " |
| 86 | + ), |
| 87 | + vector_logic_linter() |
| 88 | + ) |
| 89 | +}) |
| 90 | + |
| 91 | +test_that("vector_logic_linter recognizes some false positives around bitwise &/|", { |
| 92 | + linter <- vector_logic_linter() |
| 93 | + |
| 94 | + expect_no_lint("if (info & as.raw(12)) { }", linter) |
| 95 | + expect_no_lint("if (as.raw(12) & info) { }", linter) |
| 96 | + expect_no_lint("if (info | as.raw(12)) { }", linter) |
| 97 | + expect_no_lint("if (info & as.octmode('100')) { }", linter) |
| 98 | + expect_no_lint("if (info | as.octmode('011')) { }", linter) |
| 99 | + expect_no_lint("if (info & as.hexmode('100')) { }", linter) |
| 100 | + expect_no_lint("if (info | as.hexmode('011')) { }", linter) |
| 101 | + # implicit as.octmode() coercion |
| 102 | + expect_no_lint("if (info & '100') { }", linter) |
| 103 | + expect_no_lint("if (info | '011') { }", linter) |
| 104 | + expect_no_lint("if ('011' | info) { }", linter) |
| 105 | + |
| 106 | + # further nesting |
| 107 | + expect_no_lint("if ((info & as.raw(12)) == as.raw(12)) { }", linter) |
| 108 | + expect_no_lint("if ((info | as.raw(12)) == as.raw(12)) { }", linter) |
| 109 | + expect_no_lint('if ((mode & "111") != as.octmode("111")) { }', linter) |
| 110 | + expect_no_lint('if ((mode | "111") != as.octmode("111")) { }', linter) |
| 111 | + expect_no_lint('if ((mode & "111") != as.hexmode("111")) { }', linter) |
| 112 | + expect_no_lint('if ((mode | "111") != as.hexmode("111")) { }', linter) |
| 113 | +}) |
| 114 | + |
| 115 | +# TODO: subsetting expressions for filter/subset |
| 116 | +# |
| 117 | +# test_that("incorrect subset/filter usage is caught", { |
| 118 | +# linter <- vector_logic_linter() |
| 119 | +# and_msg <- "Use `&` in subsetting expressions" |
| 120 | +# or_msg <- "Use `|` in subsetting expressions" |
| 121 | +# |
| 122 | +# expect_lint("filter(x, y && z)", and_msg, linter) |
| 123 | +# expect_lint("filter(x, y || z)", or_msg, linter) |
| 124 | +# expect_lint("subset(x, y && z)", and_msg, linter) |
| 125 | +# expect_lint("subset(x, y || z)", or_msg, linter) |
| 126 | +# |
| 127 | +# expect_lint("x %>% filter(y && z)", and_msg, linter) |
| 128 | +# expect_lint( |
| 129 | +# "filter(x, a & b, c | d, e && f)", |
| 130 | +# list(and_msg, column_number = 27L), |
| 131 | +# linter |
| 132 | +# ) |
| 133 | +# }) |
| 134 | +# |
| 135 | +# test_that("native pipe usage is caught in subset/filter logic", { |
| 136 | +# skip_if_not_r_version("4.1.0") |
| 137 | +# |
| 138 | +# expect_lint( |
| 139 | +# "x |> filter(y && z)", |
| 140 | +# "Use `&` in subsetting", |
| 141 | +# vector_logic_linter() |
| 142 | +# ) |
| 143 | +# }) |
| 144 | +# |
| 145 | +# test_that("subsetting logic handles nesting", { |
| 146 | +# linter <- vector_logic_linter() |
| 147 | +# and_msg <- "Use `&` in subsetting expressions" |
| 148 | +# or_msg <- "Use `|` in subsetting expressions" |
| 149 | +# |
| 150 | +# expect_lint("filter(x, a & b || c)", or_msg, linter) |
| 151 | +# expect_lint("filter(x, a && b | c)", and_msg, linter) |
| 152 | +# |
| 153 | +# # adversarial commenting |
| 154 | +# expect_lint( |
| 155 | +# trim_some( |
| 156 | +# " |
| 157 | +# filter(x, a #comment |
| 158 | +# && b | c) |
| 159 | +# " |
| 160 | +# ), |
| 161 | +# and_msg, |
| 162 | +# linter |
| 163 | +# ) |
| 164 | +# |
| 165 | +# expect_lint( |
| 166 | +# trim_some( |
| 167 | +# " |
| 168 | +# filter(x, a && #comment |
| 169 | +# b | c) |
| 170 | +# " |
| 171 | +# ), |
| 172 | +# and_msg, |
| 173 | +# linter |
| 174 | +# ) |
| 175 | +# |
| 176 | +# # but not valid usage |
| 177 | +# expect_no_lint("filter(x, y < mean(y, na.rm = AA && BB))", linter) |
| 178 | +# expect_no_lint("subset(x, y < mean(y, na.rm = AA && BB) & y > 0)", linter) |
| 179 | +# expect_no_lint("subset(x, y < x[y > 0, drop = AA && BB, y])", linter) |
| 180 | +# }) |
| 181 | +# |
| 182 | +# test_that("filter() handling is conservative about stats::filter()", { |
| 183 | +# linter <- vector_logic_linter() |
| 184 | +# and_msg <- "Use `&` in subsetting expressions" |
| 185 | +# |
| 186 | +# # NB: this should be invalid, filter= is a vector argument |
| 187 | +# expect_no_lint("stats::filter(x, y && z)", linter) |
| 188 | +# # The only logical argument to stats::filter(), exclude by keyword |
| 189 | +# expect_no_lint("filter(x, circular = y && z)", linter) |
| 190 | +# # But presence of circular= doesn't invalidate lint |
| 191 | +# expect_lint("filter(x, circular = TRUE, y && z)", and_msg, linter) |
| 192 | +# expect_lint("filter(x, y && z, circular = TRUE)", and_msg, linter) |
| 193 | +# expect_no_lint( |
| 194 | +# trim_some( |
| 195 | +# " |
| 196 | +# filter(x, circular # comment |
| 197 | +# = y && z) |
| 198 | +# " |
| 199 | +# ), |
| 200 | +# linter |
| 201 | +# ) |
| 202 | +# expect_no_lint( |
| 203 | +# trim_some( |
| 204 | +# " |
| 205 | +# filter(x, circular = # comment |
| 206 | +# y && z) |
| 207 | +# " |
| 208 | +# ), |
| 209 | +# linter |
| 210 | +# ) |
| 211 | +# expect_no_lint( |
| 212 | +# trim_some( |
| 213 | +# " |
| 214 | +# filter(x, circular # comment |
| 215 | +# = # comment |
| 216 | +# y && z) |
| 217 | +# " |
| 218 | +# ), |
| 219 | +# linter |
| 220 | +# ) |
| 221 | +# }) |
0 commit comments