Skip to content

Commit 9f93dbb

Browse files
Merge a3bceb7 into 984a399
2 parents 984a399 + a3bceb7 commit 9f93dbb

File tree

11 files changed

+184
-3
lines changed

11 files changed

+184
-3
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Collate:
156156
'regex_subset_linter.R'
157157
'repeat_linter.R'
158158
'routine_registration_linter.R'
159+
'sample_int_linter.R'
159160
'scalar_in_linter.R'
160161
'semicolon_linter.R'
161162
'seq_linter.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export(redundant_ifelse_linter)
119119
export(regex_subset_linter)
120120
export(repeat_linter)
121121
export(routine_registration_linter)
122+
export(sample_int_linter)
122123
export(sarif_output)
123124
export(scalar_in_linter)
124125
export(semicolon_linter)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
### New linters
88

9+
* `sample_int_linter()` for encouraging `sample.int(n, ...)` over equivalents like `sample(1:n, ...)` (part of #884, @MichaelChirico).
910
* `stopifnot_all_linter()` discourages tests with `all()` like `stopifnot(all(x > 0))`; `stopifnot()` runs `all()` itself, and uses a better error message (part of #884, @MichaelChirico).
1011
* `comparison_negation_linter()` for discouraging negated comparisons when a direct negation is preferable, e.g. `!(x == y)` could be `x != y` (part of #884, @MichaelChirico).
1112
* `terminal_close_linter()` for discouraging using `close()` to end functions (part of #884, @MichaelChirico). Such usages are not robust to errors, where `close()` will not be run as intended. Put `close()` in an `on.exit()` hook, or use {withr} to manage connections with proper cleanup.

R/sample_int_linter.R

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#' Require usage of sample.int(n, m, ...) over sample(1:n, m, ...)
2+
#'
3+
#' [sample.int()] is preferable to `sample()` for the case of sampling numbers
4+
#' between 1 and `n`. `sample` calls `sample.int()` "under the hood".
5+
#'
6+
#' @evalRd rd_tags("sample_int_linter")
7+
#' @seealso [linters] for a complete list of linters available in lintr.
8+
#' @export
9+
sample_int_linter <- function() {
10+
# looking for anything like sample(1: that doesn't come after a $ extraction
11+
# exclude TRUE/FALSE for sample(replace = TRUE, ...) usage. better
12+
# would be match.arg() but this also works.
13+
xpath <- glue("
14+
//SYMBOL_FUNCTION_CALL[text() = 'sample']
15+
/parent::expr[not(OP-DOLLAR or OP-AT)]
16+
/following-sibling::expr[1][
17+
(
18+
expr[1]/NUM_CONST[text() = '1' or text() = '1L']
19+
and OP-COLON
20+
)
21+
or expr/SYMBOL_FUNCTION_CALL[text() = 'seq_len']
22+
or (
23+
expr/SYMBOL_FUNCTION_CALL[text() = 'seq']
24+
and (
25+
count(expr) = 2
26+
or (
27+
expr[2]/NUM_CONST[text() = '1' or text() = '1L']
28+
and not(SYMBOL_SUB[
29+
text() = 'by'
30+
and not(following-sibling::expr[1]/NUM_CONST[text() = '1' or text() = '1L'])
31+
])
32+
)
33+
)
34+
)
35+
or NUM_CONST[not(text() = 'TRUE' or text() = 'FALSE')]
36+
]
37+
/parent::expr
38+
")
39+
40+
Linter(function(source_expression) {
41+
if (!is_lint_level(source_expression, "expression")) {
42+
return(list())
43+
}
44+
45+
xml <- source_expression$xml_parsed_content
46+
47+
bad_expr <- xml_find_all(xml, xpath)
48+
first_call <- xp_call_name(bad_expr, depth = 2L)
49+
original <- sprintf("%s(n)", first_call)
50+
original[!is.na(xml_find_first(bad_expr, "expr[2]/OP-COLON"))] <- "1:n"
51+
original[!is.na(xml_find_first(bad_expr, "expr[2]/NUM_CONST"))] <- "n"
52+
53+
xml_nodes_to_lints(
54+
bad_expr,
55+
source_expression = source_expression,
56+
lint_message = glue("sample.int(n, m, ...) is preferable to sample({original}, m, ...)."),
57+
type = "warning"
58+
)
59+
})
60+
}

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ redundant_ifelse_linter,best_practices efficiency consistency configurable
7676
regex_subset_linter,best_practices efficiency
7777
repeat_linter,style readability
7878
routine_registration_linter,best_practices efficiency robustness
79+
sample_int_linter,efficiency readability robustness
7980
scalar_in_linter,readability consistency best_practices efficiency
8081
semicolon_linter,style readability default configurable
8182
semicolon_terminator_linter,style readability deprecated configurable

man/efficiency_linters.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/linters.Rd

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/readability_linters.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/robustness_linters.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/sample_int_linter.Rd

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)