├── .github ├── .gitignore ├── dependabot.yaml └── workflows │ ├── lint.yaml │ ├── ast-fuzz.yaml │ ├── test-package-vigilant.yaml │ ├── check-all-examples.yaml │ ├── pkgdown.yaml │ └── repo-meta-tests.yaml ├── paper ├── .gitignore └── 10.21105.joss.07240.pdf ├── vignettes ├── .gitignore ├── atom.png ├── emacs.gif ├── vscode.png ├── rstudio.png ├── sublime.gif ├── emacs-still.gif ├── sublime-still.gif ├── vim-syntastic.gif └── vim-syntastic-still.gif ├── .covrignore ├── revdep ├── failures.md ├── problems.md ├── .gitignore └── cran.md ├── LICENSE ├── tests ├── testthat │ ├── dummy_packages │ │ ├── RConfigInvalid │ │ │ ├── R │ │ │ │ └── lint_me.R │ │ │ ├── lintr_test_config.R │ │ │ └── DESCRIPTION │ │ ├── github_lintr_file │ │ │ ├── R │ │ │ │ └── abc.R │ │ │ ├── tests │ │ │ │ ├── testthat │ │ │ │ │ └── test-abc.R │ │ │ │ └── testthat.R │ │ │ ├── NAMESPACE │ │ │ └── DESCRIPTION │ │ ├── package │ │ │ ├── .gitignore │ │ │ ├── .Rbuildignore │ │ │ ├── lintr_test_config │ │ │ ├── NAMESPACE │ │ │ ├── exec │ │ │ │ └── script.R │ │ │ ├── package.Rproj │ │ │ ├── DESCRIPTION │ │ │ ├── vignettes │ │ │ │ ├── test.Rrst │ │ │ │ ├── test.Rtxt │ │ │ │ ├── test.Rnw │ │ │ │ ├── test.Rtex │ │ │ │ ├── test.Rhtml │ │ │ │ └── test.Rmd │ │ │ ├── R │ │ │ │ └── default_linter_testcode.R │ │ │ ├── data-raw │ │ │ │ └── default_linter_testcode.R │ │ │ └── inst │ │ │ │ └── data-raw │ │ │ │ └── default_linter_testcode.R │ │ ├── desc_dir_pkg │ │ │ └── DESCRIPTION │ │ │ │ └── R │ │ │ │ └── foo.R │ │ ├── no_export_dep │ │ │ ├── NAMESPACE │ │ │ ├── DESCRIPTION │ │ │ └── R │ │ │ │ └── foo.R │ │ ├── assignmentLinter │ │ │ ├── R │ │ │ │ ├── abc.R │ │ │ │ └── jkl.R │ │ │ ├── NAMESPACE │ │ │ ├── tests │ │ │ │ ├── testthat │ │ │ │ │ └── test-abc.R │ │ │ │ └── testthat.R │ │ │ ├── exec │ │ │ │ └── script.R │ │ │ └── DESCRIPTION │ │ ├── clean_subdir │ │ │ ├── r │ │ │ │ ├── NAMESPACE │ │ │ │ ├── DESCRIPTION │ │ │ │ └── R │ │ │ │ │ ├── default_linter_testcode.R │ │ │ │ │ └── imported_methods.R │ │ │ └── lintr_test_config │ │ ├── cp1252 │ │ │ ├── NAMESPACE │ │ │ ├── .Rbuildignore │ │ │ ├── R │ │ │ │ └── cp1252.R │ │ │ ├── DESCRIPTION │ │ │ └── cp1252.Rproj │ │ ├── RConfig │ │ │ ├── DESCRIPTION │ │ │ ├── lintr_test_config.R │ │ │ ├── lintr_test_config_conflict.R │ │ │ ├── lintr_test_config_conflict │ │ │ ├── R │ │ │ │ └── lint_me.R │ │ │ ├── tests │ │ │ │ └── testthat.R │ │ │ └── lintr_test_config_extraneous.R │ │ ├── clean │ │ │ ├── lintr_test_config │ │ │ ├── DESCRIPTION │ │ │ ├── R │ │ │ │ ├── default_linter_testcode.R │ │ │ │ ├── eat_me.R │ │ │ │ └── clean_generics.R │ │ │ └── NAMESPACE │ │ └── missing_dep │ │ │ ├── DESCRIPTION │ │ │ ├── NAMESPACE │ │ │ └── R │ │ │ └── foo.R │ ├── dummy_projects │ │ └── project │ │ │ ├── one_start_no_end.R │ │ │ ├── cp1252.R │ │ │ ├── cp1252_parseable.R │ │ │ ├── mismatched_starts_ends.R │ │ │ ├── partially_matched_exclusions.R │ │ │ ├── project.Rproj │ │ │ └── default_linter_testcode.R │ ├── knitr_extended_formats │ │ ├── tufte.Rmd │ │ └── bookdown.Rmd │ ├── test-is_lint_level.R │ ├── exclusions-test │ ├── test-ids_with_token.R │ ├── checkstyle.xml │ ├── test-with_id.R │ ├── test-lintr-package.R │ ├── knitr_malformed │ │ ├── incomplete_r_block.Rmd │ │ └── incomplete_r_block.qmd │ ├── test-length_levels_linter.R │ ├── knitr_formats │ │ ├── test.Rrst │ │ ├── test.Rtxt │ │ ├── test.Rnw │ │ ├── test.Rtex │ │ ├── test.Rhtml │ │ └── test.Rmd │ ├── test-knitr_extended_formats.R │ ├── test-routine_registration_linter.R │ ├── test-xp_utils.R │ ├── test-stopifnot_all_linter.R │ ├── test-which_grepl_linter.R │ ├── test-repeat_linter.R │ ├── test-checkstyle_output.R │ ├── test-system_file_linter.R │ ├── test-print_linter.R │ ├── test-list_comparison_linter.R │ ├── test-list2df_linter.R │ ├── default_linter_testcode.R │ ├── test-sarif_output.R │ ├── test-expect_not_linter.R │ ├── test-expect_s4_class_linter.R │ ├── test-for_loop_index_linter.R │ └── test-numeric_leading_zero_linter.R └── testthat.R ├── man ├── figures │ ├── demo.gif │ └── logo.png ├── sarif_output.Rd ├── checkstyle_output.Rd ├── expect_lint_free.Rd ├── clear_cache.Rd ├── deprecated_linters.Rd ├── tidy_design_linters.Rd ├── regex_linters.Rd ├── repeat_linter.Rd ├── correctness_linters.Rd ├── missing_package_linter.Rd ├── length_levels_linter.Rd ├── paren_body_linter.Rd ├── pipe_call_linter.Rd ├── gitlab_output.Rd ├── lint-s3.Rd ├── which_grepl_linter.Rd ├── trailing_blank_lines_linter.Rd ├── list_comparison_linter.Rd ├── rep_len_linter.Rd ├── Linter.Rd ├── executing_linters.Rd ├── common_mistakes_linters.Rd ├── lintr-package.Rd ├── spaces_left_parentheses_linter.Rd ├── numeric_leading_zero_linter.Rd ├── whitespace_linter.Rd ├── commented_code_linter.Rd ├── stopifnot_all_linter.Rd ├── pkg_testthat_linters.Rd ├── empty_assignment_linter.Rd ├── all_linters.Rd ├── package_development_linters.Rd ├── lengths_linter.Rd ├── for_loop_index_linter.Rd ├── spaces_inside_linter.Rd ├── boolean_arithmetic_linter.Rd ├── system_file_linter.Rd ├── equals_na_linter.Rd ├── expect_length_linter.Rd ├── cyclocomp_linter.Rd ├── outer_negation_linter.Rd ├── sprintf_linter.Rd ├── comparison_negation_linter.Rd ├── any_is_na_linter.Rd ├── expect_not_linter.Rd ├── sample_int_linter.Rd ├── expect_s4_class_linter.Rd ├── routine_registration_linter.Rd ├── is_lint_level.Rd ├── list2df_linter.Rd ├── terminal_close_linter.Rd ├── absolute_path_linter.Rd ├── consecutive_assertion_linter.Rd ├── missing_argument_linter.Rd ├── use_lintr.Rd ├── ifelse_censor_linter.Rd ├── yoda_test_linter.Rd ├── robustness_linters.Rd ├── unnecessary_placeholder_linter.Rd ├── pipe_return_linter.Rd ├── quotes_linter.Rd ├── nonportable_path_linter.Rd ├── T_and_F_symbol_linter.Rd ├── coalesce_linter.Rd ├── expect_named_linter.Rd ├── expect_type_linter.Rd ├── nrow_subset_linter.Rd ├── is_numeric_linter.Rd ├── class_equals_linter.Rd ├── print_linter.Rd ├── implicit_integer_linter.Rd ├── expect_null_linter.Rd ├── expect_true_false_linter.Rd ├── trailing_whitespace_linter.Rd ├── pipe_consistency_linter.Rd └── download_file_linter.Rd ├── pkgdown ├── favicon │ ├── favicon.ico │ ├── favicon-96x96.png │ ├── apple-touch-icon.png │ ├── web-app-manifest-192x192.png │ ├── web-app-manifest-512x512.png │ └── site.webmanifest └── _pkgdown.yaml ├── .devcontainer ├── devcontainer.json ├── r-base │ ├── devcontainer.json │ └── Dockerfile └── r-devel │ └── Dockerfile ├── .gitattributes ├── .dev ├── .gitignore ├── revdep-extra-repos └── defunct_linters_test.R ├── inst ├── example │ └── bad.R ├── rstudio │ └── addins.dcf └── CITATION ├── codecov.yaml ├── .gitignore ├── lintr.Rproj ├── R ├── declared_functions.R ├── with_id.R ├── actions.R ├── deprecated.R ├── tree_utils.R ├── length_levels_linter.R ├── addins.R ├── which_grepl_linter.R ├── nonportable_path_linter.R ├── whitespace_linter.R ├── AAA.R ├── expect_not_linter.R ├── absolute_path_linter.R ├── lengths_linter.R ├── repeat_linter.R ├── rep_len_linter.R ├── lintr-package.R ├── numeric_leading_zero_linter.R ├── for_loop_index_linter.R ├── routine_registration_linter.R ├── stopifnot_all_linter.R ├── empty_assignment_linter.R ├── ids_with_token.R ├── pipe_return_linter.R ├── terminal_close_linter.R └── expect_length_linter.R ├── .Rbuildignore ├── COPYING └── LICENSE.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /paper/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /.covrignore: -------------------------------------------------------------------------------- 1 | R/deprec-*.R 2 | R/compat-*.R 3 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2014-2025 2 | COPYRIGHT HOLDER: lintr authors 3 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfigInvalid/R/lint_me.R: -------------------------------------------------------------------------------- 1 | a <- 1 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/github_lintr_file/R/abc.R: -------------------------------------------------------------------------------- 1 | 'abc' 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/desc_dir_pkg/DESCRIPTION/R/foo.R: -------------------------------------------------------------------------------- 1 | x <- 4 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/no_export_dep/NAMESPACE: -------------------------------------------------------------------------------- 1 | import(datasets) 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/R/abc.R: -------------------------------------------------------------------------------- 1 | abc = 123 2 | ghi <- 456 3 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/R/jkl.R: -------------------------------------------------------------------------------- 1 | jkl = 456 2 | mno = 789 3 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean_subdir/r/NAMESPACE: -------------------------------------------------------------------------------- 1 | importFrom(utils,head) 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/cp1252/NAMESPACE: -------------------------------------------------------------------------------- 1 | exportPattern("^[[:alpha:]]+") 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/github_lintr_file/tests/testthat/test-abc.R: -------------------------------------------------------------------------------- 1 | 'abc' 2 | -------------------------------------------------------------------------------- /vignettes/atom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/atom.png -------------------------------------------------------------------------------- /man/figures/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/man/figures/demo.gif -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/cp1252/.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /vignettes/emacs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/emacs.gif -------------------------------------------------------------------------------- /vignettes/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/vscode.png -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/NAMESPACE: -------------------------------------------------------------------------------- 1 | exportPattern("^[[:alpha:]]+") 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/github_lintr_file/NAMESPACE: -------------------------------------------------------------------------------- 1 | exportPattern("^[[:alpha:]]+") 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^lintr\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/lintr_test_config: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults() 2 | -------------------------------------------------------------------------------- /vignettes/rstudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/rstudio.png -------------------------------------------------------------------------------- /vignettes/sublime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/sublime.gif -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: RConfig 2 | Version: 0.0.1 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfigInvalid/lintr_test_config.R: -------------------------------------------------------------------------------- 1 | # invalid R syntax 2 | 1 + 3 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean_subdir/r/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: clean 2 | Version: 0.0.1 3 | -------------------------------------------------------------------------------- /vignettes/emacs-still.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/emacs-still.gif -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/lintr_test_config: -------------------------------------------------------------------------------- 1 | exclusions: "R/default_linter_testcode.R" 2 | -------------------------------------------------------------------------------- /vignettes/sublime-still.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/sublime-still.gif -------------------------------------------------------------------------------- /vignettes/vim-syntastic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/vim-syntastic.gif -------------------------------------------------------------------------------- /paper/10.21105.joss.07240.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/paper/10.21105.joss.07240.pdf -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | data.sqlite 6 | *.html 7 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfigInvalid/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: RConfigInvalid 2 | Version: 0.0.1 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: clean 2 | Version: 0.0.1 3 | RoxygenNote: 7.2.2 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean_subdir/lintr_test_config: -------------------------------------------------------------------------------- 1 | exclusions: "R/default_linter_testcode.R" 2 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/one_start_no_end.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #TeSt_NoLiNt_StArT 4 | 5 | c(1,2) 6 | -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/pkgdown/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /vignettes/vim-syntastic-still.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/vignettes/vim-syntastic-still.gif -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { "dockerfile": "r-devel/Dockerfile", "context": ".."} 3 | } 4 | -------------------------------------------------------------------------------- /.devcontainer/r-base/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { "dockerfile": "Dockerfile", "context": "../.." } 3 | } 4 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/no_export_dep/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: downstream 2 | Version: 0.0.1 3 | Imports: datasets 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/missing_dep/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: downstream 2 | Version: 0.0.1 3 | Imports: myFancyUpstream 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/no_export_dep/R/foo.R: -------------------------------------------------------------------------------- 1 | foo <- function() { 2 | a_really_really_long_local_object_name <- 1 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | DESCRIPTION eol=lf 2 | NAMESPACE eol=lf 3 | *R text eol=lf 4 | *Rd text eol=lf 5 | NEWS.md text merge=union eol=lf 6 | -------------------------------------------------------------------------------- /pkgdown/favicon/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/pkgdown/favicon/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /pkgdown/favicon/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/pkgdown/favicon/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/tests/testthat/test-abc.R: -------------------------------------------------------------------------------- 1 | test_that("test abc", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/R/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | f <- function(x, y = 1) x + y 2 | message("hello") 3 | y <- 2 + (1:10) 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/github_lintr_file/tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | 3 | test_check('github_lintr_file') # nolint 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/cp1252/R/cp1252.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/tests/testthat/dummy_packages/cp1252/R/cp1252.R -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/cp1252.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/tests/testthat/dummy_projects/project/cp1252.R -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean_subdir/r/R/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | f <- function(x, y = 1) x + y 2 | message("hello") 3 | y <- 2 + (1:10) 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(assignmentLinter) 3 | 4 | test_check("assignmentLinter") 5 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/cp1252_parseable.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/lintr/HEAD/tests/testthat/dummy_projects/project/cp1252_parseable.R -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean_subdir/r/R/imported_methods.R: -------------------------------------------------------------------------------- 1 | #' head on my_s3_object 2 | #' @export 3 | head.my_s3_object <- function(x, ...) { 4 | 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/exec/script.R: -------------------------------------------------------------------------------- 1 | # lint errors should be included in test-lint_package.R 2 | x = 1:4 3 | res<- lapply(x, function(y) y+1) 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: fake comment so roxygen2 overwrites silently. 2 | exportPattern("^[^\\.]") 3 | import(lintr) 4 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/R/eat_me.R: -------------------------------------------------------------------------------- 1 | #' eat_me 2 | #' @description empty 3 | #' 4 | #' @export 5 | eat_me <- function(x, ...) { 6 | UseMethod("drink_me") 7 | } 8 | -------------------------------------------------------------------------------- /tests/testthat/knitr_extended_formats/tufte.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: tufte::tufte_html 3 | --- 4 | 5 | ```{marginfigure} 6 | "Hi" 7 | - X 8 | ``` 9 | 10 | ```{r} 11 | a = 1 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/mismatched_starts_ends.R: -------------------------------------------------------------------------------- 1 | #TeSt_NoLiNt_EnD 2 | 3 | #TeSt_NoLiNt_StArT 4 | 5 | c(1,2) 6 | 7 | #TeSt_NoLiNt_StArT 8 | 9 | #TeSt_NoLiNt_EnD 10 | 11 | #TeSt_NoLiNt_StArT 12 | -------------------------------------------------------------------------------- /.dev/.gitignore: -------------------------------------------------------------------------------- 1 | # these are artefacts, so skip storing them on .git. 2 | # moreover, revdep-no-repos contains e-mail addresses 3 | *.csv 4 | revdep-repos 5 | revdep-no-repos 6 | revdep_comparison 7 | revdep_emails 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/missing_dep/NAMESPACE: -------------------------------------------------------------------------------- 1 | import(myFancyUpstream) 2 | importFrom(aGemOfAPackage, a_veritably_dilettantish_function, 3 | sPoNgEbOb_cAsE.FuNcTiOn) 4 | export(downstream) 5 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/exec/script.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint for assignment_linter or object_name_linter on this file 2 | x = 1:4 3 | res <- lapply(x, function(y) y + 1) 4 | -------------------------------------------------------------------------------- /inst/example/bad.R: -------------------------------------------------------------------------------- 1 | fun = function(one) 2 | { 3 | one.plus.one <- oen + 1 4 | four <- newVar <- matrix(1:10,nrow = 2) 5 | four[ 1, ] 6 | txt <- 'hi' 7 | three <- two+ 1 8 | if(txt == 'hi') 4 9 | 5} 10 | { 11 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/lintr_test_config.R: -------------------------------------------------------------------------------- 1 | linters <- linters_with_defaults( 2 | any_duplicated_linter(), 3 | assignment_linter = NULL 4 | ) 5 | exclude <- "# NOLINT" 6 | exclusions <- list("tests/testthat.R") 7 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(lintr) 3 | 4 | # suppress printing environment name (noisy) 5 | invisible({ 6 | loadNamespace("patrick") 7 | loadNamespace("withr") 8 | }) 9 | 10 | test_check("lintr") 11 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/lintr_test_config_conflict.R: -------------------------------------------------------------------------------- 1 | linters <- linters_with_defaults( 2 | expect_null_linter(), 3 | assignment_linter = NULL 4 | ) 5 | exclude <- "# SKIP_LINT" 6 | exclusions <- list("R/lint_me.R") 7 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/lintr_test_config_conflict: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | any_duplicated_linter(), 3 | assignment_linter = NULL 4 | ) 5 | exclude: "# NOLINT" 6 | exclusions: list("tests/testthat.R") 7 | -------------------------------------------------------------------------------- /.dev/revdep-extra-repos: -------------------------------------------------------------------------------- 1 | # This file tracks manually-supplied GitHub repos for packages 2 | # where {lintr} is not in Suggests or Imports 3 | package,repo 4 | arrow,https://github.com/apache/arrow 5 | lightgbm,https://github.com/microsoft/LightGBM 6 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 117 reverse dependencies (110 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 0 new problems 6 | * We failed to check 0 packages 7 | 8 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 1% 7 | informational: true 8 | patch: 9 | default: 10 | target: auto 11 | threshold: 1% 12 | informational: true 13 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Lint current file 2 | Description: Runs lintr::lint on the current file 3 | Binding: addin_lint 4 | Interative: false 5 | 6 | Name: Lint current package 7 | Description: Runs lintr::lint_package 8 | Binding: addin_lint_package 9 | Interative: false 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .idea 6 | bad.R 7 | script.R 8 | *~ 9 | \#*\# 10 | *.swp 11 | 12 | *.o 13 | *.so 14 | 15 | *.Rcheck 16 | lintr_*.tar.gz 17 | tests/testthat/_problems 18 | testthat-problems.rds 19 | 20 | docs 21 | inst/doc 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tests/testthat/test-is_lint_level.R: -------------------------------------------------------------------------------- 1 | test_that("is_lint_level works", { 2 | pc <- list(parsed_content = 1L) 3 | expect_true(is_lint_level(pc, "expression")) 4 | expect_false(is_lint_level(pc, "file")) 5 | 6 | names(pc) <- "full_parsed_content" 7 | expect_true(is_lint_level(pc, "file")) 8 | expect_false(is_lint_level(pc, "expression")) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/R/lint_me.R: -------------------------------------------------------------------------------- 1 | # config excludes assignment_linter() so this doesn't lint 2 | a = 1 3 | # default config includes infix_spaces_linter() so this lints 4 | b=a + 2 5 | # config extends defaults with any_duplicated_linter() so this lints 6 | any(duplicated(b)) 7 | # custom exclude setting is also picked up so this doesn't lint 8 | 1+1 # NOLINT 9 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is in 'exclusions' & nothing lints under R config. 2 | # Under DCF config, '# SKIP_LINT' is the exclusion & this line won't lint 3 | 1+1 # SKIP_LINT 4 | # This is included as a linter in the DCF, thus this should lint 5 | expect_equal(foo(x), NULL) 6 | 7 | # trailing blank line next will lint under DCF 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/partially_matched_exclusions.R: -------------------------------------------------------------------------------- 1 | # nolint start: assign. 2 | a = 2 3 | # nolint end 4 | 5 | # nolint start: s. warn (and lint) because of non-unique identifier 6 | x <- 42; y <- 2 7 | # nolint end 8 | 9 | # nolint start: bogus_linter. warn because of nonexistent identifier 10 | 11 | # nolint end 12 | 13 | # nolint: hocus_pocus, bogus. 14 | -------------------------------------------------------------------------------- /tests/testthat/knitr_extended_formats/bookdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | documentclass: book 3 | output: bookdown::html_document2 4 | --- 5 | 6 | # Examples 7 | 8 | ```{definition} 9 | The characteristic function of a random variable $X$ is defined by 10 | $$\varphi _{X}(t)=\operatorname {E} \left[e^{itX}\right], \; t\in\mathcal{R}$$ 11 | ``` 12 | 13 | ```{r} 14 | a = 1 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("names<-",my_custom_class) 4 | S3method(drink_me,data.frame) 5 | S3method(drink_me,default) 6 | S3method(drink_me,list) 7 | S3method(eat_me,liiiiiiiiiiiiiiiiiiiiiiiiiiist) 8 | S3method(head,my_s3_object) 9 | export(drink_me) 10 | export(eat_me) 11 | importFrom(utils,head) 12 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/github_lintr_file/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: assignmentLinter 2 | Type: Package 3 | Title: What the package does (short line) 4 | Version: 1.0 5 | Date: 2019-12-17 6 | Author: Who wrote it 7 | Maintainer: Who to complain to 8 | Description: More about what it does (maybe more than one line) 9 | License: What license is it under? 10 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/project.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: ISO8859-1 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /tests/testthat/exclusions-test: -------------------------------------------------------------------------------- 1 | a <- function() { 2 | x = 1 #TeSt_NoLiNt 3 | 4 | b = 2 5 | 6 | c = 3 #TeSt_NoLiNt_StArT 7 | b + 1 8 | d = 4 9 | e = 5 #TeSt_NoLiNt_EnD 10 | f = 6 11 | f + 1 12 | a.b = 42 #TeSt_NoLiNt: assignment_linter. 13 | 14 | #TeSt_NoLiNt_StArT: assignment_linter, object_name_linter. 15 | a.b.c = 33 16 | F = T 17 | #TeSt_NoLiNt_EnD 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/test-ids_with_token.R: -------------------------------------------------------------------------------- 1 | test_that("ids_with_token works as expected", { 2 | source_expression <- get_source_expressions("tmp.R", "a <- 42L")$expressions[[1L]] 3 | ref <- ids_with_token(source_expression = source_expression, value = "expr") 4 | expect_identical(ref, c(1L, 3L, 6L)) 5 | expect_identical(source_expression$parsed_content$token[ref], rep_len("expr", length(ref))) 6 | }) 7 | -------------------------------------------------------------------------------- /.dev/defunct_linters_test.R: -------------------------------------------------------------------------------- 1 | defunct_linters <- subset( 2 | read.csv("inst/lintr/linters.csv"), 3 | grepl("\\bdefunct\\b", tags), 4 | "linter", 5 | drop = TRUE 6 | ) 7 | 8 | pkgload::load_all() 9 | found_idx <- defunct_linters %in% getNamespaceExports("lintr") 10 | if (!all(found_idx)) { 11 | stop( 12 | "Missing 'defunct'-tagged linters: ", toString(defunct_linters[!found_idx]), "." 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /tests/testthat/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/package.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | Encoding: UTF-8 9 | 10 | AutoAppendNewline: Yes 11 | StripTrailingWhitespace: Yes 12 | 13 | BuildType: Package 14 | PackageUseDevtools: Yes 15 | PackageInstallArgs: --no-multiarch --with-keep.source 16 | PackageRoxygenize: rd,collate,namespace 17 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/assignmentLinter/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: assignmentLinter 2 | Type: Package 3 | Title: What the package does (short line) 4 | Version: 1.0 5 | Date: 2019-12-17 6 | Author: Who wrote it 7 | Maintainer: Who to complain to 8 | Description: More about what it does (maybe more than one line) 9 | License: What license is it under? 10 | Suggests: 11 | testthat (>= 3.0.0) 12 | Config/testthat/edition: 3 13 | -------------------------------------------------------------------------------- /tests/testthat/test-with_id.R: -------------------------------------------------------------------------------- 1 | test_that("with_id works as expected", { 2 | source_expression <- get_source_expressions("tmp.R", "a <- 42L")$expressions[[1L]] 3 | ref <- with_id( 4 | source_expression = source_expression, 5 | ids_with_token(source_expression = source_expression, value = "expr") 6 | ) 7 | expect_identical(ref, source_expression$parsed_content[c(1L, 3L, 6L), ]) 8 | expect_identical(ref$token, rep_len("expr", nrow(ref))) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/missing_dep/R/foo.R: -------------------------------------------------------------------------------- 1 | downstream <- function(x) { 2 | a_really_really_long_local_object_name <- 1 3 | aSilLLyObjEctNaME <- 2 4 | 5 | myFancyUpstream::upstreams_really_long_imported_object_name() 6 | myFancyUpstream::uPStreams_SilLY.objecT.Name() 7 | } 8 | 9 | # importFrom() functions in unknown namespace 10 | a_veritably_dilettantish_function.my_class <- function(x, ...) x 11 | sPoNgEbOb_cAsE.FuNcTiOn.my_class <- function(x, ...) x 12 | -------------------------------------------------------------------------------- /lintr.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: No 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/RConfig/lintr_test_config_extraneous.R: -------------------------------------------------------------------------------- 1 | # here are some extraneous variables that are not part of the config directly 2 | 3 | non_default_linter <- any_duplicated_linter() 4 | attr(non_default_linter, "name") <- "any_duplicated_linter" 5 | excluded_files <- "tests/testthat.R" 6 | 7 | linters <- linters_with_defaults( 8 | non_default_linter, 9 | assignment_linter = NULL 10 | ) 11 | exclude <- "# NOLINT" 12 | exclusions <- list(excluded_files) 13 | -------------------------------------------------------------------------------- /.devcontainer/r-base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rocker/r-base 2 | 3 | RUN apt-get -qq update && \ 4 | apt-get install -y --no-install-recommends git libxml2-dev 5 | 6 | COPY DESCRIPTION . 7 | 8 | RUN Rscript -e ' \ 9 | install.packages("remotes"); \ 10 | remotes::install_deps(dependencies = c( \ 11 | "Imports", \ 12 | "Config/needs/development" \ 13 | )) \ 14 | ' 15 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/cp1252/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cp1252 2 | Type: Package 3 | Title: What the Package Does (Title Case) 4 | Version: 0.1.0 5 | Author: Who wrote it 6 | Maintainer: The package maintainer 7 | Description: More about what it does (maybe more than one line) 8 | Use four spaces when indenting paragraphs within the Description. 9 | License: What license is it under? 10 | Encoding: UTF-8 11 | LazyData: true 12 | Roxygen: list(markdown = TRUE) 13 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/cp1252/cp1252.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: ISO8859-1 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /R/declared_functions.R: -------------------------------------------------------------------------------- 1 | declared_s3_generics <- function(x) { 2 | # Top level expression which assigns to a symbol 3 | # and is an S3 Generic (contains call to UseMethod) 4 | # Retrieve assigned name of the function 5 | xpath <- "/exprlist 6 | /*[ 7 | (LEFT_ASSIGN or EQ_ASSIGN) 8 | and expr[FUNCTION or OP-LAMBDA] 9 | and .//SYMBOL_FUNCTION_CALL[text() = 'UseMethod'] 10 | ] 11 | /expr 12 | /SYMBOL 13 | " 14 | 15 | xml_text(xml_find_all(x, xpath)) 16 | } 17 | -------------------------------------------------------------------------------- /man/sarif_output.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lint.R 3 | \name{sarif_output} 4 | \alias{sarif_output} 5 | \title{SARIF Report for lint results} 6 | \usage{ 7 | sarif_output(lints, filename = "lintr_results.sarif") 8 | } 9 | \arguments{ 10 | \item{lints}{the linting results.} 11 | 12 | \item{filename}{the name of the output report} 13 | } 14 | \description{ 15 | Generate a report of the linting results using the \href{https://sarifweb.azurewebsites.net/}{SARIF} format. 16 | } 17 | -------------------------------------------------------------------------------- /pkgdown/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /tests/testthat/test-lintr-package.R: -------------------------------------------------------------------------------- 1 | test_that("All linter help files have examples", { 2 | help_db <- safe_load_help_db() 3 | linter_db <- help_db[endsWith(names(help_db), "_linter.Rd")] 4 | rd_has_examples <- function(rd) any(vapply(rd, attr, "Rd_tag", FUN.VALUE = character(1L)) == "\\examples") 5 | linter_has_examples <- vapply(linter_db, rd_has_examples, logical(1L)) 6 | for (ii in seq_along(linter_has_examples)) { 7 | expect_true(linter_has_examples[ii], label = paste("Linter", names(linter_db)[ii], "has examples")) 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: package 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person(given = "First", 6 | family = "Last", 7 | role = c("aut", "cre"), 8 | email = "first.last@example.com", 9 | comment = c(ORCID = "YOUR-ORCID-ID")) 10 | Description: What the package does (one paragraph). 11 | License: What license it uses 12 | Encoding: UTF-8 13 | Imports: lintr 14 | LazyData: true 15 | Roxygen: list(markdown = TRUE) 16 | -------------------------------------------------------------------------------- /man/checkstyle_output.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lint.R 3 | \name{checkstyle_output} 4 | \alias{checkstyle_output} 5 | \title{Checkstyle Report for lint results} 6 | \usage{ 7 | checkstyle_output(lints, filename = "lintr_results.xml") 8 | } 9 | \arguments{ 10 | \item{lints}{the linting results.} 11 | 12 | \item{filename}{the name of the output report} 13 | } 14 | \description{ 15 | Generate a report of the linting results using the \href{https://checkstyle.sourceforge.io}{Checkstyle} XML format. 16 | } 17 | -------------------------------------------------------------------------------- /man/expect_lint_free.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_lint.R 3 | \name{expect_lint_free} 4 | \alias{expect_lint_free} 5 | \title{Test that the package is lint free} 6 | \usage{ 7 | expect_lint_free(...) 8 | } 9 | \arguments{ 10 | \item{...}{arguments passed to \code{\link[=lint_package]{lint_package()}}} 11 | } 12 | \description{ 13 | This function is a thin wrapper around lint_package that simply tests there are no 14 | lints in the package. It can be used to ensure that your tests fail if the package 15 | contains lints. 16 | } 17 | -------------------------------------------------------------------------------- /man/clear_cache.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cache.R 3 | \name{clear_cache} 4 | \alias{clear_cache} 5 | \title{Clear the lintr cache} 6 | \usage{ 7 | clear_cache(file = NULL, path = NULL) 8 | } 9 | \arguments{ 10 | \item{file}{filename whose cache to clear. If you pass \code{NULL}, it will delete all of the caches.} 11 | 12 | \item{path}{directory to store caches. Reads option 'lintr.cache_directory' as the default.} 13 | } 14 | \value{ 15 | 0 for success, 1 for failure, invisibly. 16 | } 17 | \description{ 18 | Clear the lintr cache 19 | } 20 | -------------------------------------------------------------------------------- /man/deprecated_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{deprecated_linters} 4 | \alias{deprecated_linters} 5 | \title{Deprecated linters} 6 | \description{ 7 | Linters that are deprecated and provided for backwards compatibility only. 8 | These linters will be excluded from \code{\link[=linters_with_tags]{linters_with_tags()}} by default. 9 | } 10 | \seealso{ 11 | \link{linters} for a complete list of linters available in lintr. 12 | } 13 | \section{Linters}{ 14 | There are not currently any linters tagged with 'deprecated'. 15 | } 16 | -------------------------------------------------------------------------------- /man/tidy_design_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{tidy_design_linters} 4 | \alias{tidy_design_linters} 5 | \title{Tidyverse design linters} 6 | \description{ 7 | Linters based on guidelines described in the 'Tidy design principles' book. 8 | } 9 | \seealso{ 10 | \itemize{ 11 | \item \link{linters} for a complete list of linters available in lintr. 12 | \item \url{https://design.tidyverse.org/} 13 | } 14 | } 15 | \section{Linters}{ 16 | The following linters are tagged with 'tidy_design': 17 | \itemize{ 18 | \item{\code{\link{condition_call_linter}}} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # This Dependabot configuration is designed to keep GitHub Actions dependencies up-to-date 2 | # by running weekly checks and creating pull requests to update them as needed. 3 | # 4 | # This ensures that the GitHub Actions workflows remain compatible with the latest versions 5 | # of their dependencies, potentially improving the stability and security of automation processes. 6 | # 7 | # For more, see: 8 | # https://docs.github.com/en/code-security/dependabot/dependabot-alerts/about-dependabot-alerts 9 | version: 2 10 | 11 | updates: 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /R/with_id.R: -------------------------------------------------------------------------------- 1 | #' Extract row by ID 2 | #' 3 | #' @describeIn ids_with_token 4 | #' Return the row of the `parsed_content` entry of the `[get_source_expressions]()` object. Typically used in 5 | #' conjunction with `ids_with_token` to iterate over rows containing desired tokens. 6 | #' 7 | #' @param id Integer. The index corresponding to the desired row 8 | #' of `parsed_content`. 9 | #' @return `with_id`: A data frame corresponding to the row(s) specified in `id`. 10 | #' @export 11 | with_id <- function(source_expression, id) { 12 | if (!is_lint_level(source_expression, "expression")) { 13 | return(data.frame()) 14 | } 15 | source_expression$parsed_content[id, ] 16 | } 17 | -------------------------------------------------------------------------------- /man/regex_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{regex_linters} 4 | \alias{regex_linters} 5 | \title{Regular expression linters} 6 | \description{ 7 | Linters that examine the usage of regular expressions and functions executing them in user code. 8 | } 9 | \seealso{ 10 | \link{linters} for a complete list of linters available in lintr. 11 | } 12 | \section{Linters}{ 13 | The following linters are tagged with 'regex': 14 | \itemize{ 15 | \item{\code{\link{fixed_regex_linter}}} 16 | \item{\code{\link{regex_subset_linter}}} 17 | \item{\code{\link{string_boundary_linter}}} 18 | \item{\code{\link{which_grepl_linter}}} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat/knitr_malformed/incomplete_r_block.Rmd: -------------------------------------------------------------------------------- 1 | # Test # 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | ```{r} 9 | a = 1 10 | 11 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 12 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 13 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 14 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 15 | -------------------------------------------------------------------------------- /tests/testthat/knitr_malformed/incomplete_r_block.qmd: -------------------------------------------------------------------------------- 1 | # Test # 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | ```{r} 9 | a = 1 10 | 11 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 12 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 13 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 14 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 15 | -------------------------------------------------------------------------------- /R/actions.R: -------------------------------------------------------------------------------- 1 | in_github_actions <- function() { 2 | identical(Sys.getenv("GITHUB_ACTIONS"), "true") 3 | } 4 | 5 | in_pkgdown <- function() { 6 | identical(Sys.getenv("IN_PKGDOWN"), "true") 7 | } 8 | 9 | # Output logging commands for any lints found 10 | github_actions_log_lints <- function(lints, project_dir = "") { 11 | for (x in lints) { 12 | if (nzchar(project_dir)) { 13 | x$filename <- file.path(project_dir, x$filename) 14 | } 15 | file_line_col <- sprintf( 16 | "file=%s,line=%s,col=%s", x$filename, x$line_number, x$column_number 17 | ) 18 | cat(sprintf( 19 | "::warning %s::%s,[%s] %s\n", 20 | file_line_col, file_line_col, x$linter, x$message 21 | ), sep = "") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.devcontainer/r-devel/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/rhub/r-minimal:devel 2 | 3 | # This minimal image deletes the default repo list, so set one here 4 | # for interactive R sessions to WAI. 5 | RUN echo 'options(repos = c(CRAN = "https://cloud.r-project.org"))' \ 6 | >> /usr/local/lib/R/etc/Rprofile.site 7 | 8 | RUN apk update && \ 9 | apk add --no-cache git gcc g++ libxml2-dev linux-headers musl-dev 10 | 11 | COPY DESCRIPTION . 12 | 13 | RUN Rscript -e ' \ 14 | install.packages("remotes"); \ 15 | remotes::install_deps(dependencies = c( \ 16 | "Imports", \ 17 | "Config/needs/development" \ 18 | )) \ 19 | ' 20 | -------------------------------------------------------------------------------- /R/deprecated.R: -------------------------------------------------------------------------------- 1 | #' Deprecated functions 2 | #' 3 | #' Functions that have been deprecated and replaced by newer ones. They will be removed in an 4 | #' upcoming version of \pkg{lintr} and should thus not be used anymore. 5 | #' @noRd 6 | NULL 7 | 8 | lintr_deprecated <- function(what, alternative = NULL, version = NULL, 9 | type = "Function", signal = c("warning", "stop")) { 10 | signal <- match.arg(signal) 11 | signal <- match.fun(signal) 12 | msg <- c( 13 | c(type, " ", what, " was deprecated"), 14 | if (length(version) > 0L) c(" in lintr version ", version), 15 | ". ", 16 | if (length(alternative) > 0L) c("Use ", alternative, " instead.") 17 | ) 18 | msg <- paste(msg, collapse = "") 19 | signal(msg, call. = FALSE, domain = NA) 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat/test-length_levels_linter.R: -------------------------------------------------------------------------------- 1 | test_that("length_levels_linter skips allowed usages", { 2 | expect_lint("length(c(levels(x), 'a'))", NULL, length_levels_linter()) 3 | }) 4 | 5 | test_that("length_levels_linter blocks simple disallowed usages", { 6 | expect_lint( 7 | "2:length(levels(x))", 8 | rex::rex("nlevels(x) is better than length(levels(x))."), 9 | length_levels_linter() 10 | ) 11 | }) 12 | 13 | test_that("lints vectorize", { 14 | lint_msg <- rex::rex("nlevels(x) is better than length(levels(x)).") 15 | 16 | expect_lint( 17 | trim_some("{ 18 | length(levels(x)) 19 | length(levels(y)) 20 | }"), 21 | list( 22 | list(lint_msg, line_number = 2L), 23 | list(lint_msg, line_number = 3L) 24 | ), 25 | length_levels_linter() 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /man/repeat_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/repeat_linter.R 3 | \name{repeat_linter} 4 | \alias{repeat_linter} 5 | \title{Repeat linter} 6 | \usage{ 7 | repeat_linter() 8 | } 9 | \description{ 10 | Check that \verb{while (TRUE)} is not used for infinite loops. While this is valid 11 | R code, using \code{repeat {}} is more explicit. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "while (TRUE) { }", 17 | linters = repeat_linter() 18 | ) 19 | 20 | 21 | # okay 22 | lint( 23 | text = "repeat { }", 24 | linters = repeat_linter() 25 | ) 26 | 27 | } 28 | \seealso{ 29 | \link{linters} for a complete list of linters available in lintr. 30 | } 31 | \section{Tags}{ 32 | \link[=readability_linters]{readability}, \link[=style_linters]{style} 33 | } 34 | -------------------------------------------------------------------------------- /man/correctness_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{correctness_linters} 4 | \alias{correctness_linters} 5 | \title{Correctness linters} 6 | \description{ 7 | Linters highlighting possible programming mistakes, such as unused variables. 8 | } 9 | \seealso{ 10 | \link{linters} for a complete list of linters available in lintr. 11 | } 12 | \section{Linters}{ 13 | The following linters are tagged with 'correctness': 14 | \itemize{ 15 | \item{\code{\link{duplicate_argument_linter}}} 16 | \item{\code{\link{equals_na_linter}}} 17 | \item{\code{\link{missing_argument_linter}}} 18 | \item{\code{\link{namespace_linter}}} 19 | \item{\code{\link{object_usage_linter}}} 20 | \item{\code{\link{package_hooks_linter}}} 21 | \item{\code{\link{sprintf_linter}}} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, master] 4 | pull_request: 5 | branches: [main, master] 6 | 7 | name: lint 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v6 16 | 17 | - uses: r-lib/actions/setup-r@v2 18 | with: 19 | use-public-rspm: true 20 | 21 | - uses: r-lib/actions/setup-r-dependencies@v2 22 | with: 23 | install-quarto: false 24 | extra-packages: | 25 | any::cyclocomp 26 | r-lib/lintr 27 | local::. 28 | needs: lint 29 | 30 | - name: Lint 31 | run: lintr::lint_package() 32 | shell: Rscript {0} 33 | env: 34 | LINTR_ERROR_ON_LINT: true 35 | -------------------------------------------------------------------------------- /R/tree_utils.R: -------------------------------------------------------------------------------- 1 | generate_top_level_map <- function(pc) { 2 | if (is.null(pc)) { 3 | return(NULL) 4 | } 5 | tl_ids <- pc$id[pc$parent <= 0L] 6 | tl_parent <- pc$parent 7 | tl_parent[pc$parent <= 0L] <- tl_ids 8 | i_not_assigned <- which(!tl_parent %in% tl_ids) 9 | while ((prev_length <- length(i_not_assigned)) > 0L) { # nolint: implicit_assignment_linter. TODO(#2015): remove this. 10 | tl_parent[i_not_assigned] <- pc$parent[match(tl_parent[i_not_assigned], pc$id)] 11 | i_not_assigned <- which(!tl_parent %in% tl_ids) 12 | # nocov start 13 | if (length(i_not_assigned) >= prev_length) { 14 | cli_abort_internal("Logical error: unassigned set did not shrink. Check file syntax.") 15 | } 16 | # nocov end 17 | } 18 | tl_parent 19 | } 20 | 21 | lag <- function(x) { 22 | c(NA, x[seq_len(length(x) - 1L)]) 23 | } 24 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^\.travis\.yml$ 3 | ^[^/]+\.R$ 4 | ^travis-tool\.sh$ 5 | ^.*\.gz$ 6 | ^lintr\.Rproj$ 7 | ^\.Rproj\.user$ 8 | ^\.idea$ 9 | ^\.dev$ 10 | ^\.devcontainer$ 11 | ^\.lintr$ 12 | ^\.lintr_new$ 13 | ^wercker\.yml$ 14 | ^[^/]+.Rmd$ 15 | ^revdep$ 16 | ^COPYING$ 17 | ^bad\.R$ 18 | ^\.github$ 19 | ^codecov\.yaml$ 20 | ^\.covrignore$ 21 | ^script[.]R$ 22 | ^bench$ 23 | ^tests/testthat/_problems$ 24 | ^tests/testthat/dummy_packages/package/[.]Rbuildignore$ 25 | ^tests/testthat/dummy_packages/cp1252/[.]Rbuildignore$ 26 | testthat-problems[.]rds$ 27 | ^_pkgdown\.yaml$ 28 | ^docs$ 29 | ^pkgdown$ 30 | # large files not needed for CRAN 31 | ^man/figures/logo[.]png$ 32 | # keep '-still.gif' compact versions 33 | ^vignettes/.*([^l]|[^l]l|[^i]ll|[^t]ill|[^s]till)[.]gif$ 34 | ^CRAN-SUBMISSION$ 35 | ^CODE_OF_CONDUCT\.md$ 36 | ^paper$ 37 | ^LICENSE\.md$ 38 | ^CITATION.cff$ 39 | -------------------------------------------------------------------------------- /R/length_levels_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of nlevels over length(levels(.)) 2 | #' 3 | #' `length(levels(x))` is the same as `nlevels(x)`, but harder to read. 4 | #' 5 | #' @examples 6 | #' # will produce lints 7 | #' lint( 8 | #' text = "length(levels(x))", 9 | #' linters = length_levels_linter() 10 | #' ) 11 | #' 12 | #' # okay 13 | #' lint( 14 | #' text = "length(c(levels(x), levels(y)))", 15 | #' linters = length_levels_linter() 16 | #' ) 17 | #' 18 | #' @evalRd rd_tags("length_levels_linter") 19 | #' @seealso [linters] for a complete list of linters available in lintr. 20 | #' @export 21 | length_levels_linter <- make_linter_from_function_xpath( 22 | function_names = "levels", 23 | xpath = " 24 | parent::expr 25 | /parent::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'length']] 26 | ", 27 | lint_message = "nlevels(x) is better than length(levels(x))." 28 | ) 29 | -------------------------------------------------------------------------------- /man/missing_package_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/missing_package_linter.R 3 | \name{missing_package_linter} 4 | \alias{missing_package_linter} 5 | \title{Missing package linter} 6 | \usage{ 7 | missing_package_linter() 8 | } 9 | \description{ 10 | Check for missing packages in \code{library()}, \code{require()}, \code{loadNamespace()}, and \code{requireNamespace()} calls. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = "library(xyzxyz)", 16 | linters = missing_package_linter() 17 | ) 18 | 19 | # okay 20 | lint( 21 | text = "library(stats)", 22 | linters = missing_package_linter() 23 | ) 24 | 25 | } 26 | \seealso{ 27 | \link{linters} for a complete list of linters available in lintr. 28 | } 29 | \section{Tags}{ 30 | \link[=common_mistakes_linters]{common_mistakes}, \link[=robustness_linters]{robustness} 31 | } 32 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rrst: -------------------------------------------------------------------------------- 1 | Test 2 | ==== 3 | 4 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 5 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 6 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 7 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 8 | 9 | .. {r rst-example} 10 | a = 1 11 | .. .. 12 | 13 | Test 14 | ---- 15 | 16 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 17 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 18 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 19 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 20 | 21 | .. {r} 22 | b <- function(x) { 23 | d = 1 24 | } 25 | 26 | .. .. 27 | 28 | .. {r engine = "python"} 29 | a=[] 30 | 31 | a[0]=1 32 | .. .. 33 | -------------------------------------------------------------------------------- /R/addins.R: -------------------------------------------------------------------------------- 1 | # nocov start 2 | addin_lint <- function() { 3 | if (!requireNamespace("rstudioapi", quietly = TRUE)) { 4 | cli_abort("{.pkg rstudioapi} is required for add-ins.") 5 | } 6 | filename <- rstudioapi::getSourceEditorContext() 7 | if (filename$path == "") { 8 | cli_warn("Current source has no path. Please save before continuing.") 9 | return(flatten_lints(list())) 10 | } 11 | 12 | lint(filename$path) 13 | } 14 | 15 | addin_lint_package <- function() { 16 | if (!requireNamespace("rstudioapi", quietly = TRUE)) { 17 | cli_abort("{.pkg rstudioapi} is required for add-ins.") 18 | } 19 | project <- rstudioapi::getActiveProject() 20 | if (is.null(project)) { 21 | cli_inform("No project found, passing current directory.") 22 | project_path <- getwd() 23 | } else { 24 | project_path <- project 25 | } 26 | 27 | lint_package(project_path) 28 | } 29 | # nocov end 30 | -------------------------------------------------------------------------------- /.github/workflows/ast-fuzz.yaml: -------------------------------------------------------------------------------- 1 | # Randomly change some code & ensure lint equivalency is maintained 2 | on: 3 | push: 4 | branches: [main] 5 | # TODO before merging: remove this. Only kept to demonstrate during review. 6 | pull_request: 7 | branches: [main] 8 | 9 | name: ast-fuzz 10 | 11 | jobs: 12 | repo-meta-tests: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v6 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | r-version: "release" 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | install-quarto: false 28 | 29 | - name: Ensure equivalent code generates equivalent lints 30 | run: | 31 | callr::rscript(".dev/ast_fuzz_test.R") 32 | shell: Rscript {0} 33 | -------------------------------------------------------------------------------- /man/length_levels_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/length_levels_linter.R 3 | \name{length_levels_linter} 4 | \alias{length_levels_linter} 5 | \title{Require usage of nlevels over length(levels(.))} 6 | \usage{ 7 | length_levels_linter() 8 | } 9 | \description{ 10 | \code{length(levels(x))} is the same as \code{nlevels(x)}, but harder to read. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = "length(levels(x))", 16 | linters = length_levels_linter() 17 | ) 18 | 19 | # okay 20 | lint( 21 | text = "length(c(levels(x), levels(y)))", 22 | linters = length_levels_linter() 23 | ) 24 | 25 | } 26 | \seealso{ 27 | \link{linters} for a complete list of linters available in lintr. 28 | } 29 | \section{Tags}{ 30 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 31 | } 32 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rrst: -------------------------------------------------------------------------------- 1 | Test 2 | ==== 3 | 4 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 5 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 6 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 7 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 8 | 9 | .. {r rst-example} 10 | a = 1 11 | .. .. 12 | 13 | Test 14 | ---- 15 | 16 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 17 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 18 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 19 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 20 | 21 | .. {r} 22 | b <- function(x) { 23 | d = 1 24 | } 25 | 26 | .. .. 27 | 28 | .. {r engine = "python"} 29 | a=[] 30 | 31 | a[0]=1 32 | .. .. 33 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rtxt: -------------------------------------------------------------------------------- 1 | = Test = 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | //begin.rcode 9 | a = 1 10 | //end.rcode 11 | 12 | Test 13 | ==== 14 | 15 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 16 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 17 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 18 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 19 | 20 | //begin.rcode trailing 21 | b <- function(x) { 22 | d = 1 23 | } 24 | 25 | //end.rcode 26 | 27 | //begin.rcode engine="python" 28 | a=[] 29 | 30 | a[0]=1 31 | //end.rcode 32 | -------------------------------------------------------------------------------- /man/paren_body_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/paren_body_linter.R 3 | \name{paren_body_linter} 4 | \alias{paren_body_linter} 5 | \title{Parenthesis before body linter} 6 | \usage{ 7 | paren_body_linter() 8 | } 9 | \description{ 10 | Check that there is a space between right parenthesis and a body expression. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = "function(x)x + 1", 16 | linters = paren_body_linter() 17 | ) 18 | 19 | # okay 20 | lint( 21 | text = "function(x) x + 1", 22 | linters = paren_body_linter() 23 | ) 24 | 25 | } 26 | \seealso{ 27 | \itemize{ 28 | \item \link{linters} for a complete list of linters available in lintr. 29 | \item \url{https://style.tidyverse.org/syntax.html#parentheses} 30 | } 31 | } 32 | \section{Tags}{ 33 | \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 34 | } 35 | -------------------------------------------------------------------------------- /R/which_grepl_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of grep over which(grepl(.)) 2 | #' 3 | #' `which(grepl(pattern, x))` is the same as `grep(pattern, x)`, but harder 4 | #' to read and requires two passes over the vector. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "which(grepl('^a', x))", 10 | #' linters = which_grepl_linter() 11 | #' ) 12 | #' 13 | #' # okay 14 | #' lint( 15 | #' text = "which(grepl('^a', x) | grepl('^b', x))", 16 | #' linters = which_grepl_linter() 17 | #' ) 18 | #' 19 | #' @evalRd rd_tags("which_grepl_linter") 20 | #' @seealso [linters] for a complete list of linters available in lintr. 21 | #' @export 22 | which_grepl_linter <- make_linter_from_function_xpath( 23 | function_names = "grepl", 24 | xpath = " 25 | parent::expr 26 | /parent::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'which']] 27 | ", 28 | lint_message = "grep(pattern, x) is better than which(grepl(pattern, x))." 29 | ) 30 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rtxt: -------------------------------------------------------------------------------- 1 | = Test = 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | //begin.rcode 9 | a = 1 10 | //end.rcode 11 | 12 | Test 13 | ==== 14 | 15 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 16 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 17 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 18 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 19 | 20 | //begin.rcode trailing 21 | b <- function(x) { 22 | d = 1 23 | } 24 | 25 | //end.rcode 26 | 27 | //begin.rcode engine="python" 28 | a=[] 29 | 30 | a[0]=1 31 | //end.rcode 32 | -------------------------------------------------------------------------------- /man/pipe_call_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipe_call_linter.R 3 | \name{pipe_call_linter} 4 | \alias{pipe_call_linter} 5 | \title{Pipe call linter} 6 | \usage{ 7 | pipe_call_linter() 8 | } 9 | \description{ 10 | Force explicit calls in magrittr pipes, e.g., \code{1:3 \%>\% sum()} instead of \code{1:3 \%>\% sum}. 11 | Note that native pipe always requires a function call, i.e. \verb{1:3 |> sum} will produce an error. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "1:3 \%>\% mean \%>\% as.character", 17 | linters = pipe_call_linter() 18 | ) 19 | 20 | # okay 21 | lint( 22 | text = "1:3 \%>\% mean() \%>\% as.character()", 23 | linters = pipe_call_linter() 24 | ) 25 | 26 | } 27 | \seealso{ 28 | \link{linters} for a complete list of linters available in lintr. 29 | } 30 | \section{Tags}{ 31 | \link[=readability_linters]{readability}, \link[=style_linters]{style} 32 | } 33 | -------------------------------------------------------------------------------- /man/gitlab_output.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lint.R 3 | \name{gitlab_output} 4 | \alias{gitlab_output} 5 | \title{GitLab Report for lint results} 6 | \usage{ 7 | gitlab_output(lints, filename = "lintr_results.json") 8 | } 9 | \arguments{ 10 | \item{lints}{The linting results} 11 | 12 | \item{filename}{The file name of the output report} 13 | } 14 | \description{ 15 | Generate a report of the linting results using the 16 | \href{https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format}{GitLab} format. 17 | } 18 | \details{ 19 | lintr only supports three severity types ("style", "warning", and "error") 20 | while the GitLab format supports five ("info", "minor", "major", "critical", 21 | and "blocker"). The types "style", "warning", and "error" are mapped to 22 | the GitLab types "info", "major", and "blocker", respectively. The GitLab 23 | types "minor" and "critical" are ignored. 24 | } 25 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rnw: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{mathpazo} 3 | \begin{document} 4 | \title{Test} 5 | 6 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 7 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 8 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 9 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 10 | 11 | <>= 12 | a = 1 13 | @ 14 | 15 | \subtitle{Test} 16 | 17 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 18 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 19 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 20 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 21 | 22 | <<>>= 23 | b <- function(x) { 24 | d = 1 25 | } 26 | 27 | @ 28 | 29 | <>= 30 | a=[] 31 | 32 | a[0]=1 33 | @ 34 | -------------------------------------------------------------------------------- /tests/testthat/test-knitr_extended_formats.R: -------------------------------------------------------------------------------- 1 | test_that("marginfigure engine from tufte package doesn't cause problems", { 2 | skip_if_not_installed("tufte", minimum_version = "0.12.4") # for rstudio/tufte#117 3 | loadNamespace("tufte") # to register additional engines 4 | 5 | expect_lint( 6 | file = test_path("knitr_extended_formats", "tufte.Rmd"), 7 | checks = list(rex::rex("Use one of <-, <<- for assignment, not =."), line_number = 11L), 8 | default_linters, 9 | parse_settings = FALSE 10 | ) 11 | }) 12 | 13 | test_that("engines from bookdown package cause no problems", { 14 | skip_if_not_installed("bookdown") 15 | loadNamespace("bookdown") # to register additional engines 16 | 17 | expect_lint( 18 | file = test_path("knitr_extended_formats", "bookdown.Rmd"), 19 | checks = list(rex::rex("Use one of <-, <<- for assignment, not =."), line_number = 14L), 20 | default_linters, 21 | parse_settings = FALSE 22 | ) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/testthat/test-routine_registration_linter.R: -------------------------------------------------------------------------------- 1 | patrick::with_parameters_test_that( 2 | "lints correctly", 3 | { 4 | linter <- routine_registration_linter() 5 | expect_lint(sprintf("%s(ROUTINE, 1)", caller), NULL, linter) 6 | expect_lint( 7 | sprintf("%s('ROUTINE', PACKAGE = 'foo')", caller), 8 | "Register your native code routines with useDynLib", 9 | linter 10 | ) 11 | }, 12 | .test_name = c(".C", ".Call", ".External", ".Fortran"), 13 | caller = c(".C", ".Call", ".External", ".Fortran") 14 | ) 15 | 16 | test_that("lints vectorize", { 17 | lint_msg <- "Register your native code routines with useDynLib" 18 | 19 | expect_lint( 20 | trim_some("{ 21 | .C('ROUTINE', PACKAGE = 'foo') 22 | .External('POUTINE', PACKAGE = 'bar') 23 | }"), 24 | list( 25 | list(lint_msg, line_number = 2L), 26 | list(lint_msg, line_number = 3L) 27 | ), 28 | routine_registration_linter() 29 | ) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rnw: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{mathpazo} 3 | \begin{document} 4 | \title{Test} 5 | 6 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 7 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 8 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 9 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 10 | 11 | <>= 12 | a = 1 13 | @ 14 | 15 | \subtitle{Test} 16 | 17 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 18 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 19 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 20 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 21 | 22 | <<>>= 23 | b <- function(x) { 24 | d = 1 25 | } 26 | 27 | @ 28 | 29 | <>= 30 | a=[] 31 | 32 | a[0]=1 33 | @ 34 | -------------------------------------------------------------------------------- /man/lint-s3.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lint.R 3 | \name{lint-s3} 4 | \alias{lint-s3} 5 | \alias{Lint} 6 | \title{Create a \code{lint} object} 7 | \usage{ 8 | Lint( 9 | filename, 10 | line_number = 1L, 11 | column_number = 1L, 12 | type = c("style", "warning", "error"), 13 | message = "", 14 | line = "", 15 | ranges = NULL 16 | ) 17 | } 18 | \arguments{ 19 | \item{filename}{path to the source file that was linted.} 20 | 21 | \item{line_number}{line number where the lint occurred.} 22 | 23 | \item{column_number}{column number where the lint occurred.} 24 | 25 | \item{type}{type of lint.} 26 | 27 | \item{message}{message used to describe the lint error} 28 | 29 | \item{line}{code source where the lint occurred} 30 | 31 | \item{ranges}{a list of ranges on the line that should be emphasized.} 32 | } 33 | \value{ 34 | an object of class \code{c("lint", "list")}. 35 | } 36 | \description{ 37 | Create a \code{lint} object 38 | } 39 | -------------------------------------------------------------------------------- /man/which_grepl_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/which_grepl_linter.R 3 | \name{which_grepl_linter} 4 | \alias{which_grepl_linter} 5 | \title{Require usage of grep over which(grepl(.))} 6 | \usage{ 7 | which_grepl_linter() 8 | } 9 | \description{ 10 | \code{which(grepl(pattern, x))} is the same as \code{grep(pattern, x)}, but harder 11 | to read and requires two passes over the vector. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "which(grepl('^a', x))", 17 | linters = which_grepl_linter() 18 | ) 19 | 20 | # okay 21 | lint( 22 | text = "which(grepl('^a', x) | grepl('^b', x))", 23 | linters = which_grepl_linter() 24 | ) 25 | 26 | } 27 | \seealso{ 28 | \link{linters} for a complete list of linters available in lintr. 29 | } 30 | \section{Tags}{ 31 | \link[=consistency_linters]{consistency}, \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability}, \link[=regex_linters]{regex} 32 | } 33 | -------------------------------------------------------------------------------- /tests/testthat/test-xp_utils.R: -------------------------------------------------------------------------------- 1 | test_that("xp_call_name works", { 2 | xml_from_code <- function(str) { 3 | xml2::read_xml(xmlparsedata::xml_parse_data(parse(text = str, keep.source = TRUE))) 4 | } 5 | xml <- xml_from_code("sum(1:10)") 6 | expect_identical(xp_call_name(xml, depth = 2L), "sum") 7 | 8 | expect_identical(xp_call_name(xml2::xml_find_first(xml, "expr")), "sum") 9 | 10 | xml <- xml_from_code(c("sum(1:10)", "sd(1:10)")) 11 | expect_identical(xp_call_name(xml, depth = 2L, condition = "text() = 'sum'"), "sum") 12 | }) 13 | 14 | test_that("xp_call_name input validation works", { 15 | expect_error(xp_call_name(2L), "`expr` must be an ", fixed = TRUE) 16 | 17 | xml <- xml2::read_xml("") 18 | expect_error(xp_call_name(xml, depth = -1L), "depth >= 0", fixed = TRUE) 19 | expect_error(xp_call_name(xml, depth = "1"), "is.numeric(depth)", fixed = TRUE) 20 | expect_error(xp_call_name(xml, condition = 1L), "is.character(condition)", fixed = TRUE) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rtex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{graphicx} 3 | \title{Test} 4 | 5 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 6 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 7 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 8 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 9 | 10 | %% begin.rcode 11 | % a = 1 12 | %% end.rcode 13 | 14 | \subtitle{Test} 15 | 16 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 17 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 18 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 19 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 20 | 21 | %% begin.rcode test_chunk 22 | % b <- function(x) { 23 | % d = 1 24 | % } 25 | % 26 | %% end.rcode 27 | 28 | %% begin.rcode engine="python" 29 | % a=[] 30 | % 31 | % a[0]=1 32 | %% end.rcode 33 | -------------------------------------------------------------------------------- /man/trailing_blank_lines_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trailing_blank_lines_linter.R 3 | \name{trailing_blank_lines_linter} 4 | \alias{trailing_blank_lines_linter} 5 | \title{Trailing blank lines linter} 6 | \usage{ 7 | trailing_blank_lines_linter() 8 | } 9 | \description{ 10 | Check that there are no trailing blank lines in source code. 11 | } 12 | \examples{ 13 | # will produce lints 14 | f <- tempfile() 15 | cat("x <- 1\n\n", file = f) 16 | writeLines(readChar(f, file.size(f))) 17 | lint( 18 | filename = f, 19 | linters = trailing_blank_lines_linter() 20 | ) 21 | unlink(f) 22 | 23 | # okay 24 | cat("x <- 1\n", file = f) 25 | writeLines(readChar(f, file.size(f))) 26 | lint( 27 | filename = f, 28 | linters = trailing_blank_lines_linter() 29 | ) 30 | unlink(f) 31 | 32 | } 33 | \seealso{ 34 | \link{linters} for a complete list of linters available in lintr. 35 | } 36 | \section{Tags}{ 37 | \link[=default_linters]{default}, \link[=style_linters]{style} 38 | } 39 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/clean/R/clean_generics.R: -------------------------------------------------------------------------------- 1 | # Regression test for #737 via expecting clean to be lint-free 2 | 3 | #' drink_me 4 | #' @description empty 5 | #' 6 | #' @export 7 | drink_me <- function(x, ...) { 8 | UseMethod("drink_me") 9 | } 10 | 11 | #' drink_me for most things 12 | #' @export 13 | drink_me.default <- function(x, ...) { 14 | 1 15 | } 16 | 17 | #' drink_me for lists 18 | #' @export 19 | drink_me.list <- function(x, ...) { 20 | NULL 21 | } 22 | 23 | #' drink_me for data.frames 24 | #' @export 25 | drink_me.data.frame <- function(x, ...) { 26 | NULL 27 | } 28 | 29 | #' head on my_s3_object 30 | #' @importFrom utils head 31 | #' @export 32 | head.my_s3_object <- function(x, ...) { 33 | NULL 34 | } 35 | 36 | #' assign names for my_custom_class 37 | #' @export 38 | `names<-.my_custom_class` <- function(x, value) { 39 | NULL 40 | } 41 | 42 | #' Defined S3 generic in R/eat_me.R 43 | #' Tests #1808 44 | #' @export 45 | eat_me.liiiiiiiiiiiiiiiiiiiiiiiiiiist <- function(x, ...) { 46 | NULL 47 | } 48 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rtex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{graphicx} 3 | \title{Test} 4 | 5 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 6 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 7 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 8 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 9 | 10 | %% begin.rcode 11 | % a = 1 12 | %% end.rcode 13 | 14 | \subtitle{Test} 15 | 16 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 17 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 18 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 19 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 20 | 21 | %% begin.rcode test_chunk 22 | % b <- function(x) { 23 | % d = 1 24 | % } 25 | % 26 | %% end.rcode 27 | 28 | %% begin.rcode engine="python" 29 | % a=[] 30 | % 31 | % a[0]=1 32 | %% end.rcode 33 | -------------------------------------------------------------------------------- /tests/testthat/test-stopifnot_all_linter.R: -------------------------------------------------------------------------------- 1 | test_that("stopifnot_all_linter skips allowed usages", { 2 | expect_lint("stopifnot(all(x) || any(y))", NULL, stopifnot_all_linter()) 3 | }) 4 | 5 | test_that("stopifnot_all_linter blocks simple disallowed usages", { 6 | linter <- stopifnot_all_linter() 7 | lint_msg <- rex::rex("stopifnot(x) runs all() 'under the hood'") 8 | 9 | expect_lint("stopifnot(all(A))", list(lint_msg, column_number = 11L), linter) 10 | expect_lint("stopifnot(x, y, all(z))", list(lint_msg, column_number = 17L), linter) 11 | 12 | expect_lint( 13 | trim_some("{ 14 | stopifnot(all(x), all(y), 15 | all(z) 16 | ) 17 | stopifnot(a > 0, b < 0, all(c == 0)) 18 | }"), 19 | list( 20 | list(lint_msg, line_number = 2L, column_number = 13L), 21 | list(lint_msg, line_number = 2L, column_number = 21L), 22 | list(lint_msg, line_number = 3L, column_number = 5L), 23 | list(lint_msg, line_number = 5L, column_number = 27L) 24 | ), 25 | linter 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/test-which_grepl_linter.R: -------------------------------------------------------------------------------- 1 | test_that("which_grepl_linter skips allowed usages", { 2 | # this _could_ be combined as p1|p2, but often it's cleaner to read this way 3 | expect_no_lint("which(grepl(p1, x) | grepl(p2, x))", which_grepl_linter()) 4 | }) 5 | 6 | test_that("which_grepl_linter blocks simple disallowed usages", { 7 | linter <- which_grepl_linter() 8 | lint_msg <- rex::rex("grep(pattern, x) is better than which(grepl(pattern, x)).") 9 | 10 | expect_lint("which(grepl('^a', x))", lint_msg, linter) 11 | # options also don't matter (grep has more arguments: value, invert) 12 | expect_lint("which(grepl('^a', x, perl = TRUE, fixed = TRUE))", lint_msg, linter) 13 | 14 | expect_lint( 15 | trim_some('{ 16 | which(x) 17 | grepl(y) 18 | which(grepl("pt1", x)) 19 | which(grepl("pt2", y)) 20 | }'), 21 | list( 22 | list(lint_msg, line_number = 4L, column_number = 3L), 23 | list(lint_msg, line_number = 5L, column_number = 3L) 24 | ), 25 | linter 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /man/list_comparison_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/list_comparison_linter.R 3 | \name{list_comparison_linter} 4 | \alias{list_comparison_linter} 5 | \title{Block usage of comparison operators with known-list() functions like lapply} 6 | \usage{ 7 | list_comparison_linter() 8 | } 9 | \description{ 10 | Usage like \code{lapply(x, sum) > 10} is awkward because the list must first 11 | be coerced to a vector for comparison. A function like \code{\link[=vapply]{vapply()}} 12 | should be preferred. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "lapply(x, sum) > 10", 18 | linters = list_comparison_linter() 19 | ) 20 | 21 | # okay 22 | lint( 23 | text = "unlist(lapply(x, sum)) > 10", 24 | linters = list_comparison_linter() 25 | ) 26 | 27 | } 28 | \seealso{ 29 | \link{linters} for a complete list of linters available in lintr. 30 | } 31 | \section{Tags}{ 32 | \link[=best_practices_linters]{best_practices}, \link[=common_mistakes_linters]{common_mistakes} 33 | } 34 | -------------------------------------------------------------------------------- /tests/testthat/dummy_projects/project/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint on this file 2 | 3 | # assignment 4 | # function_left_parentheses 5 | # closed_curly 6 | # commas 7 | # paren_brace 8 | f = function (x,y = 1){} 9 | 10 | # commented_code 11 | # some <- commented("out code") 12 | 13 | # equals_na 14 | # infix_spaces 15 | # line_length 16 | # object_length 17 | # object_name 18 | # object_usage 19 | # open_curly 20 | # T_and_F_symbol 21 | someComplicatedFunctionWithALongCamelCaseName <- function(x) 22 | { 23 | y <- 1 24 | if (1 > 2 && 2 > 3 && 3 > 4 && 4 > 5 && 5*10 > 6 && 5 > 6 && 6 > 7 && x == NA) {T} else {F} 25 | } 26 | 27 | # no_tab 28 | # pipe_continuation 29 | # seq_linter 30 | # spaces_inside 31 | x <- 1:10 32 | x[ 2] 33 | 1:length(x) %>% lapply(function(x) x*2) %>% 34 | head() 35 | 36 | # single_quotes 37 | message('single_quotes') 38 | 39 | # spaces_left_parentheses 40 | # trailing_whitespace 41 | # semicolon 42 | x <- 42; y <- 2 +(1:10) 43 | 44 | # trailing_blank_lines 45 | 46 | -------------------------------------------------------------------------------- /man/rep_len_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rep_len_linter.R 3 | \name{rep_len_linter} 4 | \alias{rep_len_linter} 5 | \title{Require usage of rep_len(x, n) over rep(x, length.out = n)} 6 | \usage{ 7 | rep_len_linter() 8 | } 9 | \description{ 10 | \code{rep(x, length.out = n)} calls \code{rep_len(x, n)} "under the hood". The latter 11 | is thus more direct and equally readable. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "rep(1:3, length.out = 10)", 17 | linters = rep_len_linter() 18 | ) 19 | 20 | # okay 21 | lint( 22 | text = "rep_len(1:3, 10)", 23 | linters = rep_len_linter() 24 | ) 25 | 26 | lint( 27 | text = "rep(1:3, each = 2L, length.out = 10L)", 28 | linters = rep_len_linter() 29 | ) 30 | 31 | } 32 | \seealso{ 33 | \link{linters} for a complete list of linters available in lintr. 34 | } 35 | \section{Tags}{ 36 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 37 | } 38 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | 6 | 7 |

Test

8 | 9 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 10 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 11 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 12 | no sea takimata sanctus est Lorem ipsum dolor sit amet.

13 | 14 | 17 | 18 |

Test

19 | 20 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 21 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 22 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 23 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 24 | 25 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /R/nonportable_path_linter.R: -------------------------------------------------------------------------------- 1 | #' Non-portable path linter 2 | #' 3 | #' Check that [file.path()] is used to construct safe and portable paths. 4 | #' 5 | #' @examples 6 | #' # will produce lints 7 | #' lint( 8 | #' text = "'abcdefg/hijklmnop/qrst/uv/wxyz'", 9 | #' linters = nonportable_path_linter() 10 | #' ) 11 | #' 12 | #' # okay 13 | #' lint( 14 | #' text = "file.path('abcdefg', 'hijklmnop', 'qrst', 'uv', 'wxyz')", 15 | #' linters = nonportable_path_linter() 16 | #' ) 17 | #' 18 | #' @inheritParams absolute_path_linter 19 | #' @evalRd rd_tags("nonportable_path_linter") 20 | #' @seealso 21 | #' - [linters] for a complete list of linters available in lintr. 22 | #' - [absolute_path_linter()] 23 | #' @export 24 | nonportable_path_linter <- function(lax = TRUE) { 25 | path_linter_factory( 26 | path_function = function(path) { 27 | is_path(path) && is_valid_long_path(path, lax) && path != "/" && 28 | re_matches(path, rex(one_of("/", "\\"))) 29 | }, 30 | message = "Use file.path() to construct portable file paths." 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /tests/testthat/test-repeat_linter.R: -------------------------------------------------------------------------------- 1 | test_that("test repeat_linter", { 2 | linter <- repeat_linter() 3 | msg <- rex::rex("Use 'repeat' instead of 'while (TRUE)' for infinite loops.") 4 | 5 | expect_lint("repeat { }", NULL, linter) 6 | expect_lint("while (FALSE) { }", NULL, linter) 7 | expect_lint("while (i < 5) { }", NULL, linter) 8 | expect_lint("while (j < 5) TRUE", NULL, linter) 9 | expect_lint("while (TRUE && j < 5) { ... }", NULL, linter) 10 | 11 | expect_lint("while (TRUE) { }", msg, linter) 12 | expect_lint("for (i in 1:10) { while (TRUE) { if (i == 5) { break } } }", msg, linter) 13 | expect_lint("while (TRUE) { while (TRUE) { } }", list(msg, msg), linter) 14 | expect_lint( 15 | trim_some("{ 16 | while (TRUE) { 17 | } 18 | while (TRUE) { 19 | } 20 | }"), 21 | list( 22 | list(message = msg, line_number = 2L, column_number = 3L, ranges = list(c(3L, 14L))), 23 | list(message = msg, line_number = 4L, column_number = 3L, ranges = list(c(3L, 14L))) 24 | ), 25 | linter 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/R/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint on this file 2 | 3 | # assignment 4 | # function_left_parentheses 5 | # closed_curly 6 | # commas 7 | # paren_brace 8 | f = function (x,y = 1){} 9 | 10 | # commented_code 11 | # some <- commented("out code") 12 | 13 | # cyclocomp 14 | # equals_na 15 | # infix_spaces 16 | # line_length 17 | # object_length 18 | # object_name 19 | # object_usage 20 | # open_curly 21 | # T_and_F_symbol 22 | someComplicatedFunctionWithALongCamelCaseName <- function(x) 23 | { 24 | y <- 1 25 | if (1 > 2 && 2 > 3 && 3 > 4 && 4 > 5 && 5*10 > 6 && 5 > 6 && 6 > 7 && x == NA) {T} else {F} 26 | } 27 | 28 | # no_tab 29 | # pipe_continuation 30 | # seq_linter 31 | # spaces_inside 32 | x <- 1:10 33 | x[ 2] 34 | 1:length(x) %>% lapply(function(x) x*2) %>% 35 | head() 36 | 37 | # single_quotes 38 | message('single_quotes') 39 | 40 | # spaces_left_parentheses 41 | # trailing_whitespace 42 | # semicolon 43 | x <- 42; y <- 2 +(1:10) 44 | 45 | # trailing_blank_lines 46 | 47 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | 6 | 7 |

Test

8 | 9 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 10 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 11 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 12 | no sea takimata sanctus est Lorem ipsum dolor sit amet.

13 | 14 | 17 | 18 |

Test

19 | 20 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 21 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 22 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 23 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 24 | 25 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/data-raw/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint on this file 2 | 3 | # assignment 4 | # function_left_parentheses 5 | # closed_curly 6 | # commas 7 | # paren_brace 8 | f = function (x,y = 1){} 9 | 10 | # commented_code 11 | # some <- commented("out code") 12 | 13 | # cyclocomp 14 | # equals_na 15 | # infix_spaces 16 | # line_length 17 | # object_length 18 | # object_name 19 | # object_usage 20 | # open_curly 21 | # T_and_F_symbol 22 | someComplicatedFunctionWithALongCamelCaseName <- function(x) 23 | { 24 | y <- 1 25 | if (1 > 2 && 2 > 3 && 3 > 4 && 4 > 5 && 5*10 > 6 && 5 > 6 && 6 > 7 && x == NA) {T} else {F} 26 | } 27 | 28 | # no_tab 29 | # pipe_continuation 30 | # seq_linter 31 | # spaces_inside 32 | x <- 1:10 33 | x[ 2] 34 | 1:length(x) %>% lapply(function(x) x*2) %>% 35 | head() 36 | 37 | # single_quotes 38 | message('single_quotes') 39 | 40 | # spaces_left_parentheses 41 | # trailing_whitespace 42 | # semicolon 43 | x <- 42; y <- 2 +(1:10) 44 | 45 | # trailing_blank_lines 46 | 47 | -------------------------------------------------------------------------------- /man/Linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{Linter} 4 | \alias{Linter} 5 | \title{Create a \code{linter} closure} 6 | \usage{ 7 | Linter( 8 | fun, 9 | name = linter_auto_name(), 10 | linter_level = c(NA_character_, "file", "expression") 11 | ) 12 | } 13 | \arguments{ 14 | \item{fun}{A function that takes a source file and returns \code{lint} objects.} 15 | 16 | \item{name}{Default name of the Linter. 17 | Lints produced by the linter will be labelled with \code{name} by default.} 18 | 19 | \item{linter_level}{Which level of expression is the linter working with? 20 | \code{"expression"} means an individual expression in \code{xml_parsed_content}, while \code{"file"} means all expressions 21 | in the current file are available in \code{full_xml_parsed_content}. 22 | \code{NA} means the linter will be run with both, expression-level and file-level source expressions.} 23 | } 24 | \value{ 25 | The same function with its class set to 'linter'. 26 | } 27 | \description{ 28 | Create a \code{linter} closure 29 | } 30 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/inst/data-raw/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint on this file 2 | 3 | # assignment 4 | # function_left_parentheses 5 | # closed_curly 6 | # commas 7 | # paren_brace 8 | f = function (x,y = 1){} 9 | 10 | # commented_code 11 | # some <- commented("out code") 12 | 13 | # cyclocomp 14 | # equals_na 15 | # infix_spaces 16 | # line_length 17 | # object_length 18 | # object_name 19 | # object_usage 20 | # open_curly 21 | # T_and_F_symbol 22 | someComplicatedFunctionWithALongCamelCaseName <- function(x) 23 | { 24 | y <- 1 25 | if (1 > 2 && 2 > 3 && 3 > 4 && 4 > 5 && 5*10 > 6 && 5 > 6 && 6 > 7 && x == NA) {T} else {F} 26 | } 27 | 28 | # no_tab 29 | # pipe_continuation 30 | # seq_linter 31 | # spaces_inside 32 | x <- 1:10 33 | x[ 2] 34 | 1:length(x) %>% lapply(function(x) x*2) %>% 35 | head() 36 | 37 | # single_quotes 38 | message('single_quotes') 39 | 40 | # spaces_left_parentheses 41 | # trailing_whitespace 42 | # semicolon 43 | x <- 42; y <- 2 +(1:10) 44 | 45 | # trailing_blank_lines 46 | 47 | -------------------------------------------------------------------------------- /tests/testthat/test-checkstyle_output.R: -------------------------------------------------------------------------------- 1 | test_that("return lint report as checkstyle xml", { 2 | lints <- list( 3 | Lint( 4 | filename = "test_file", 5 | line_number = 1L, 6 | column_number = 2L, 7 | type = "error", 8 | line = "a line", 9 | message = "foo" 10 | ), 11 | Lint( 12 | filename = "test_file", 13 | line_number = 2L, 14 | column_number = 1L, 15 | type = "style", 16 | line = "another line", 17 | message = "bar" 18 | ), 19 | Lint( 20 | filename = "test_file2", 21 | line_number = 1L, 22 | column_number = 1L, 23 | type = "warning", 24 | line = "yet another line", 25 | message = "baz" 26 | ) 27 | ) 28 | class(lints) <- "lints" 29 | tmp <- withr::local_tempfile() 30 | checkstyle_output(lints, tmp) 31 | 32 | # The second line is the checkstyle version, so we ignore it during the 33 | # check, so we don't have to update the version every release. 34 | expect_identical(readLines(tmp)[-2L], readLines(test_path("checkstyle.xml"))[-2L]) 35 | }) 36 | -------------------------------------------------------------------------------- /R/whitespace_linter.R: -------------------------------------------------------------------------------- 1 | #' Whitespace linter 2 | #' 3 | #' Check that the correct character is used for indentation. 4 | #' 5 | #' Currently, only supports linting in the presence of tabs. 6 | #' 7 | #' Much ink has been spilled on this topic, and we encourage you to check 8 | #' out references for more information. 9 | #' 10 | #' @include make_linter_from_regex.R 11 | #' 12 | #' @examples 13 | #' # will produce lints 14 | #' lint( 15 | #' text = "\tx", 16 | #' linters = whitespace_linter() 17 | #' ) 18 | #' 19 | #' # okay 20 | #' lint( 21 | #' text = " x", 22 | #' linters = whitespace_linter() 23 | #' ) 24 | #' 25 | #' @evalRd rd_tags("whitespace_linter") 26 | #' @seealso [linters] for a complete list of linters available in lintr. 27 | #' 28 | #' @references 29 | #' - https://www.jwz.org/doc/tabs-vs-spaces.html 30 | #' - https://blog.codinghorror.com/death-to-the-space-infidels/ 31 | #' @export 32 | whitespace_linter <- make_linter_from_regex( 33 | regex = rex(start, zero_or_more(regex("\\s")), one_or_more("\t")), 34 | lint_type = "style", 35 | lint_msg = "Use spaces to indent, not tabs." 36 | ) 37 | -------------------------------------------------------------------------------- /R/AAA.R: -------------------------------------------------------------------------------- 1 | #' @include utils.R 2 | #' @include xp_utils.R 3 | #' @include make_linter_from_xpath.R 4 | NULL 5 | 6 | #' Available linters 7 | #' @name linters 8 | #' 9 | #' @description A variety of linters are available in \pkg{lintr}. The most popular ones are readily 10 | #' accessible through [default_linters()]. 11 | #' 12 | #' Within a [lint()] function call, the linters in use are initialized with the provided 13 | #' arguments and fed with the source file (provided by [get_source_expressions()]). 14 | #' 15 | #' A data frame of all available linters can be retrieved using [available_linters()]. 16 | #' Documentation for linters is structured into tags to allow for easier discovery; 17 | #' see also [available_tags()]. 18 | #' 19 | #' @evalRd rd_taglist() 20 | #' @evalRd rd_linterlist() 21 | NULL 22 | 23 | # need to register rex shortcuts as globals to avoid CRAN check errors 24 | rex::register_shortcuts("lintr") 25 | 26 | globalVariables( 27 | c( 28 | "line1", "col1", "line2", "col2", # columns of parsed_content 29 | "id", "parent", "token", "terminal", "text" # ditto 30 | ), 31 | "lintr" 32 | ) 33 | -------------------------------------------------------------------------------- /man/executing_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{executing_linters} 4 | \alias{executing_linters} 5 | \title{Code executing linters} 6 | \description{ 7 | Linters that evaluate parts of the linted code, such as loading referenced packages. 8 | These linters should not be used with untrusted code, and may need dependencies of the linted package or project to 9 | be available in order to function correctly. For package authors, note that this includes loading the package itself, 10 | e.g. with \code{pkgload::load_all()} or installing and attaching the package. 11 | } 12 | \seealso{ 13 | \link{linters} for a complete list of linters available in lintr. 14 | } 15 | \section{Linters}{ 16 | The following linters are tagged with 'executing': 17 | \itemize{ 18 | \item{\code{\link{namespace_linter}}} 19 | \item{\code{\link{object_length_linter}}} 20 | \item{\code{\link{object_name_linter}}} 21 | \item{\code{\link{object_overwrite_linter}}} 22 | \item{\code{\link{object_usage_linter}}} 23 | \item{\code{\link{unused_import_linter}}} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /man/common_mistakes_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{common_mistakes_linters} 4 | \alias{common_mistakes_linters} 5 | \title{Common mistake linters} 6 | \description{ 7 | Linters highlighting common mistakes, such as duplicate arguments. 8 | } 9 | \seealso{ 10 | \link{linters} for a complete list of linters available in lintr. 11 | } 12 | \section{Linters}{ 13 | The following linters are tagged with 'common_mistakes': 14 | \itemize{ 15 | \item{\code{\link{all_equal_linter}}} 16 | \item{\code{\link{download_file_linter}}} 17 | \item{\code{\link{duplicate_argument_linter}}} 18 | \item{\code{\link{equals_na_linter}}} 19 | \item{\code{\link{length_test_linter}}} 20 | \item{\code{\link{list_comparison_linter}}} 21 | \item{\code{\link{missing_argument_linter}}} 22 | \item{\code{\link{missing_package_linter}}} 23 | \item{\code{\link{pipe_return_linter}}} 24 | \item{\code{\link{redundant_equals_linter}}} 25 | \item{\code{\link{sprintf_linter}}} 26 | \item{\code{\link{unused_import_linter}}} 27 | \item{\code{\link{vector_logic_linter}}} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /man/lintr-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lintr-package.R 3 | \docType{package} 4 | \name{lintr-package} 5 | \alias{lintr} 6 | \alias{lintr-package} 7 | \title{Lintr} 8 | \description{ 9 | Checks adherence to a given style, syntax errors, and possible semantic issues. 10 | Supports on the fly checking of R code edited with Emacs, Vim, and Sublime Text. 11 | } 12 | \seealso{ 13 | \code{\link[=lint]{lint()}}, \code{\link[=lint_package]{lint_package()}}, \code{\link[=lint_dir]{lint_dir()}}, \link{linters} 14 | } 15 | \author{ 16 | \strong{Maintainer}: Michael Chirico \email{michaelchirico4@gmail.com} (\href{https://orcid.org/0000-0003-0787-087X}{ORCID}) 17 | 18 | Authors: 19 | \itemize{ 20 | \item Jim Hester 21 | \item Florent Angly (fangly) 22 | \item Russ Hyde 23 | \item Kun Ren 24 | \item Alexander Rosenstock (AshesITR) 25 | \item Indrajeet Patil \email{patilindrajeet.science@gmail.com} (\href{https://orcid.org/0000-0003-1995-6531}{ORCID}) 26 | \item Hugo Gruson (\href{https://orcid.org/0000-0002-4094-1476}{ORCID}) 27 | } 28 | 29 | } 30 | \keyword{internal} 31 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2024, James Hester 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /man/spaces_left_parentheses_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/spaces_left_parentheses_linter.R 3 | \name{spaces_left_parentheses_linter} 4 | \alias{spaces_left_parentheses_linter} 5 | \title{Spaces before parentheses linter} 6 | \usage{ 7 | spaces_left_parentheses_linter() 8 | } 9 | \description{ 10 | Check that all left parentheses have a space before them unless they are in a function call. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = "if(TRUE) x else y", 16 | linters = spaces_left_parentheses_linter() 17 | ) 18 | 19 | # okay 20 | lint( 21 | text = "if (TRUE) x else y", 22 | linters = spaces_left_parentheses_linter() 23 | ) 24 | 25 | } 26 | \seealso{ 27 | \itemize{ 28 | \item \link{linters} for a complete list of linters available in lintr. 29 | \item \url{https://style.tidyverse.org/syntax.html#parentheses} 30 | \item \code{\link[=function_left_parentheses_linter]{function_left_parentheses_linter()}} 31 | } 32 | } 33 | \section{Tags}{ 34 | \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 35 | } 36 | -------------------------------------------------------------------------------- /tests/testthat/test-system_file_linter.R: -------------------------------------------------------------------------------- 1 | test_that("system_file_linter skips allowed usages", { 2 | linter <- system_file_linter() 3 | 4 | expect_lint("system.file('a', 'b', 'c')", NULL, linter) 5 | expect_lint("file.path('a', 'b', 'c')", NULL, linter) 6 | }) 7 | 8 | test_that("system_file_linter blocks simple disallowed usages", { 9 | linter <- system_file_linter() 10 | lint_msg <- rex::rex("Use the `...` argument of system.file() to expand paths") 11 | 12 | expect_lint("system.file(file.path('path', 'to', 'data'), package = 'foo')", lint_msg, linter) 13 | expect_lint("file.path(system.file(package = 'foo'), 'path', 'to', 'data')", lint_msg, linter) 14 | }) 15 | 16 | test_that("lints vectorize", { 17 | lint_msg <- rex::rex("Use the `...` argument of system.file() to expand paths") 18 | 19 | expect_lint( 20 | trim_some("{ 21 | file.path(system.file(package = 'foo'), 'bar') 22 | system.file(file.path('bar', 'data'), package = 'foo') 23 | }"), 24 | list( 25 | list(lint_msg, line_number = 2L), 26 | list(lint_msg, line_number = 3L) 27 | ), 28 | system_file_linter() 29 | ) 30 | }) 31 | -------------------------------------------------------------------------------- /R/expect_not_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of `expect_false(x)` over `expect_true(!x)` 2 | #' 3 | #' [testthat::expect_false()] exists specifically for testing that an output is 4 | #' `FALSE`. [testthat::expect_true()] can also be used for such tests by 5 | #' negating the output, but it is better to use the tailored function instead. 6 | #' The reverse is also true -- use `expect_false(A)` instead of 7 | #' `expect_true(!A)`. 8 | #' 9 | #' @examples 10 | #' # will produce lints 11 | #' lint( 12 | #' text = "expect_true(!x)", 13 | #' linters = expect_not_linter() 14 | #' ) 15 | #' 16 | #' # okay 17 | #' lint( 18 | #' text = "expect_false(x)", 19 | #' linters = expect_not_linter() 20 | #' ) 21 | #' 22 | #' @evalRd rd_tags("expect_not_linter") 23 | #' @seealso [linters] for a complete list of linters available in lintr. 24 | #' @export 25 | expect_not_linter <- make_linter_from_function_xpath( 26 | function_names = c("expect_true", "expect_false"), 27 | xpath = " 28 | following-sibling::expr[OP-EXCLAMATION] 29 | /parent::expr 30 | ", 31 | lint_message = "expect_false(x) is better than expect_true(!x), and vice versa." 32 | ) 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 lintr authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/numeric_leading_zero_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/numeric_leading_zero_linter.R 3 | \name{numeric_leading_zero_linter} 4 | \alias{numeric_leading_zero_linter} 5 | \title{Require usage of a leading zero in all fractional numerics} 6 | \usage{ 7 | numeric_leading_zero_linter() 8 | } 9 | \description{ 10 | While .1 and 0.1 mean the same thing, the latter is easier to read due 11 | to the small size of the '.' glyph. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "x <- .1", 17 | linters = numeric_leading_zero_linter() 18 | ) 19 | 20 | lint( 21 | text = "x <- -.1", 22 | linters = numeric_leading_zero_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "x <- 0.1", 28 | linters = numeric_leading_zero_linter() 29 | ) 30 | 31 | lint( 32 | text = "x <- -0.1", 33 | linters = numeric_leading_zero_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \link{linters} for a complete list of linters available in lintr. 39 | } 40 | \section{Tags}{ 41 | \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 42 | } 43 | -------------------------------------------------------------------------------- /man/whitespace_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/whitespace_linter.R 3 | \name{whitespace_linter} 4 | \alias{whitespace_linter} 5 | \title{Whitespace linter} 6 | \usage{ 7 | whitespace_linter() 8 | } 9 | \description{ 10 | Check that the correct character is used for indentation. 11 | } 12 | \details{ 13 | Currently, only supports linting in the presence of tabs. 14 | 15 | Much ink has been spilled on this topic, and we encourage you to check 16 | out references for more information. 17 | } 18 | \examples{ 19 | # will produce lints 20 | lint( 21 | text = "\tx", 22 | linters = whitespace_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = " x", 28 | linters = whitespace_linter() 29 | ) 30 | 31 | } 32 | \references{ 33 | \itemize{ 34 | \item https://www.jwz.org/doc/tabs-vs-spaces.html 35 | \item https://blog.codinghorror.com/death-to-the-space-infidels/ 36 | } 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=consistency_linters]{consistency}, \link[=default_linters]{default}, \link[=style_linters]{style} 43 | } 44 | -------------------------------------------------------------------------------- /man/commented_code_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/commented_code_linter.R 3 | \name{commented_code_linter} 4 | \alias{commented_code_linter} 5 | \title{Commented code linter} 6 | \usage{ 7 | commented_code_linter() 8 | } 9 | \description{ 10 | Check that there is no commented code outside roxygen blocks. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = "# x <- 1", 16 | linters = commented_code_linter() 17 | ) 18 | 19 | lint( 20 | text = "x <- f() # g()", 21 | linters = commented_code_linter() 22 | ) 23 | 24 | lint( 25 | text = "x + y # + z[1, 2]", 26 | linters = commented_code_linter() 27 | ) 28 | 29 | # okay 30 | lint( 31 | text = "x <- 1; x <- f(); x + y", 32 | linters = commented_code_linter() 33 | ) 34 | 35 | lint( 36 | text = "#' x <- 1", 37 | linters = commented_code_linter() 38 | ) 39 | 40 | } 41 | \seealso{ 42 | \link{linters} for a complete list of linters available in lintr. 43 | } 44 | \section{Tags}{ 45 | \link[=best_practices_linters]{best_practices}, \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 46 | } 47 | -------------------------------------------------------------------------------- /man/stopifnot_all_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stopifnot_all_linter.R 3 | \name{stopifnot_all_linter} 4 | \alias{stopifnot_all_linter} 5 | \title{Block usage of all() within stopifnot()} 6 | \usage{ 7 | stopifnot_all_linter() 8 | } 9 | \description{ 10 | \code{stopifnot(A)} actually checks \code{all(A)} "under the hood" if \code{A} is a vector, 11 | and produces a better error message than \code{stopifnot(all(A))} does. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "stopifnot(all(x > 0))", 17 | linters = stopifnot_all_linter() 18 | ) 19 | 20 | lint( 21 | text = "stopifnot(y > 3, all(x < 0))", 22 | linters = stopifnot_all_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "stopifnot(is.null(x) || all(x > 0))", 28 | linters = stopifnot_all_linter() 29 | ) 30 | 31 | lint( 32 | text = "assert_that(all(x > 0))", 33 | linters = stopifnot_all_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \link{linters} for a complete list of linters available in lintr. 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=readability_linters]{readability} 42 | } 43 | -------------------------------------------------------------------------------- /man/pkg_testthat_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{pkg_testthat_linters} 4 | \alias{pkg_testthat_linters} 5 | \title{Testthat linters} 6 | \description{ 7 | Linters encouraging best practices within testthat suites. 8 | } 9 | \seealso{ 10 | \itemize{ 11 | \item \link{linters} for a complete list of linters available in lintr. 12 | \item \url{https://testthat.r-lib.org} 13 | \item \url{https://r-pkgs.org/testing-basics.html} 14 | } 15 | } 16 | \section{Linters}{ 17 | The following linters are tagged with 'pkg_testthat': 18 | \itemize{ 19 | \item{\code{\link{conjunct_test_linter}}} 20 | \item{\code{\link{expect_comparison_linter}}} 21 | \item{\code{\link{expect_identical_linter}}} 22 | \item{\code{\link{expect_length_linter}}} 23 | \item{\code{\link{expect_named_linter}}} 24 | \item{\code{\link{expect_not_linter}}} 25 | \item{\code{\link{expect_null_linter}}} 26 | \item{\code{\link{expect_s3_class_linter}}} 27 | \item{\code{\link{expect_s4_class_linter}}} 28 | \item{\code{\link{expect_true_false_linter}}} 29 | \item{\code{\link{expect_type_linter}}} 30 | \item{\code{\link{yoda_test_linter}}} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /R/absolute_path_linter.R: -------------------------------------------------------------------------------- 1 | #' Absolute path linter 2 | #' 3 | #' Check that no absolute paths are used (e.g. "/var", "C:\\System", "~/docs"). 4 | #' 5 | #' @param lax Less stringent linting, leading to fewer false positives. 6 | #' If `TRUE`, only lint path strings, which 7 | #' 8 | #' * contain at least two path elements, with one having at least two characters and 9 | #' * contain only alphanumeric chars (including UTF-8), spaces, and win32-allowed punctuation 10 | #' 11 | #' @examples 12 | #' # will produce lints 13 | #' lint( 14 | #' text = 'R"(/blah/file.txt)"', 15 | #' linters = absolute_path_linter() 16 | #' ) 17 | #' 18 | #' # okay 19 | #' lint( 20 | #' text = 'R"(./blah)"', 21 | #' linters = absolute_path_linter() 22 | #' ) 23 | #' 24 | #' @evalRd rd_tags("absolute_path_linter") 25 | #' @seealso 26 | #' - [linters] for a complete list of linters available in lintr. 27 | #' - [nonportable_path_linter()] 28 | #' @export 29 | absolute_path_linter <- function(lax = TRUE) { 30 | path_linter_factory( 31 | path_function = function(path) { 32 | is_absolute_path(path) && is_valid_long_path(path, lax) 33 | }, 34 | message = "Do not use absolute paths." 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /R/lengths_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of `lengths()` where possible 2 | #' 3 | #' [base::lengths()] is a function that was added to base R in version 3.2.0 to 4 | #' get the length of each element of a list. It is equivalent to 5 | #' `sapply(x, length)`, but faster and more readable. 6 | #' 7 | #' @examples 8 | #' # will produce lints 9 | #' lint( 10 | #' text = "sapply(x, length)", 11 | #' linters = lengths_linter() 12 | #' ) 13 | #' 14 | #' lint( 15 | #' text = "vapply(x, length, integer(1L))", 16 | #' linters = lengths_linter() 17 | #' ) 18 | #' 19 | #' lint( 20 | #' text = "purrr::map_int(x, length)", 21 | #' linters = lengths_linter() 22 | #' ) 23 | #' 24 | #' # okay 25 | #' lint( 26 | #' text = "lengths(x)", 27 | #' linters = lengths_linter() 28 | #' ) 29 | #' 30 | #' @evalRd rd_tags("lengths_linter") 31 | #' @seealso [linters] for a complete list of linters available in lintr. 32 | #' @export 33 | lengths_linter <- make_linter_from_function_xpath( 34 | function_names = c("sapply", "vapply", "map_int", "map_dbl"), 35 | xpath = "parent::expr[expr/SYMBOL[text() = 'length']]", 36 | lint_message = "Use lengths() to find the length of each element in a list." 37 | ) 38 | -------------------------------------------------------------------------------- /man/empty_assignment_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/empty_assignment_linter.R 3 | \name{empty_assignment_linter} 4 | \alias{empty_assignment_linter} 5 | \title{Block assignment of \code{{}}} 6 | \usage{ 7 | empty_assignment_linter() 8 | } 9 | \description{ 10 | Assignment of \code{{}} is the same as assignment of \code{NULL}; use the latter 11 | for clarity. Closely related: \code{\link[=unnecessary_concatenation_linter]{unnecessary_concatenation_linter()}}. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "x <- {}", 17 | linters = empty_assignment_linter() 18 | ) 19 | 20 | writeLines("x = {\n}") 21 | lint( 22 | text = "x = {\n}", 23 | linters = empty_assignment_linter() 24 | ) 25 | 26 | # okay 27 | lint( 28 | text = "x <- { 3 + 4 }", 29 | linters = empty_assignment_linter() 30 | ) 31 | 32 | lint( 33 | text = "x <- NULL", 34 | linters = empty_assignment_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=best_practices_linters]{best_practices}, \link[=readability_linters]{readability} 43 | } 44 | -------------------------------------------------------------------------------- /tests/testthat/test-print_linter.R: -------------------------------------------------------------------------------- 1 | test_that("print_linter skips allowed usages", { 2 | linter <- print_linter() 3 | 4 | expect_lint("print(x)", NULL, linter) 5 | expect_lint("print(foo(x))", NULL, linter) 6 | }) 7 | 8 | test_that("print_linter blocks disallowed usages", { 9 | linter <- print_linter() 10 | lint_msg <- 11 | rex::rex("Use cat() instead of print() logging messages. Use message() in cases calling for a signalled condition.") 12 | 13 | expect_lint('print("hi")', lint_msg, linter) 14 | 15 | # basic known-character functions 16 | expect_lint('print(paste(x, "b", y))', lint_msg, linter) 17 | expect_lint('print(paste0(x, "c", y))', lint_msg, linter) 18 | expect_lint('print(sprintf("a %s", x))', lint_msg, linter) 19 | 20 | # vectorization, metadata 21 | expect_lint( 22 | trim_some("{ 23 | print('a') 24 | print(paste('x', y)) 25 | print(z) 26 | print(sprintf('%s', b)) 27 | }"), 28 | list( 29 | list(lint_msg, line_number = 2L, column_number = 3L), 30 | list(lint_msg, line_number = 3L, column_number = 3L), 31 | list(lint_msg, line_number = 5L, column_number = 3L) 32 | ), 33 | linter 34 | ) 35 | }) 36 | -------------------------------------------------------------------------------- /R/repeat_linter.R: -------------------------------------------------------------------------------- 1 | #' Repeat linter 2 | #' 3 | #' Check that `while (TRUE)` is not used for infinite loops. While this is valid 4 | #' R code, using `repeat {}` is more explicit. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "while (TRUE) { }", 10 | #' linters = repeat_linter() 11 | #' ) 12 | #' 13 | #' 14 | #' # okay 15 | #' lint( 16 | #' text = "repeat { }", 17 | #' linters = repeat_linter() 18 | #' ) 19 | #' 20 | #' @evalRd rd_tags("repeat_linter") 21 | #' @seealso [linters] for a complete list of linters available in lintr. 22 | #' @export 23 | repeat_linter <- function() { 24 | xpath <- "//WHILE[following-sibling::expr[1]/NUM_CONST[text() = 'TRUE']]" 25 | 26 | Linter(linter_level = "expression", function(source_expression) { 27 | xml <- source_expression$xml_parsed_content 28 | 29 | lints <- xml_find_all(xml, xpath) 30 | 31 | xml_nodes_to_lints( 32 | lints, 33 | source_expression = source_expression, 34 | lint_message = "Use 'repeat' instead of 'while (TRUE)' for infinite loops.", 35 | range_start_xpath = "number(./@col1)", 36 | range_end_xpath = "number(./following-sibling::*[3]/@col2)" 37 | ) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /man/all_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/with.R 3 | \name{all_linters} 4 | \alias{all_linters} 5 | \title{Create a linter configuration based on all available linters} 6 | \usage{ 7 | all_linters(..., packages = "lintr") 8 | } 9 | \arguments{ 10 | \item{...}{Arguments of elements to change. If unnamed, the argument is automatically named. 11 | If the named argument already exists in the list of linters, it is replaced by the new element. 12 | If it does not exist, it is added. If the value is \code{NULL}, the linter is removed.} 13 | 14 | \item{packages}{A character vector of packages to search for linters.} 15 | } 16 | \description{ 17 | Create a linter configuration based on all available linters 18 | } 19 | \examples{ 20 | names(all_linters()) 21 | 22 | } 23 | \seealso{ 24 | \itemize{ 25 | \item \link{linters_with_defaults} for basing off lintr's set of default linters. 26 | \item \link{linters_with_tags} for basing off tags attached to linters, possibly across multiple packages. 27 | \item \link{available_linters} to get a data frame of available linters. 28 | \item \link{linters} for a complete list of linters available in lintr. 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /man/package_development_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{package_development_linters} 4 | \alias{package_development_linters} 5 | \title{Package development linters} 6 | \description{ 7 | Linters useful to package developers, for example for writing consistent tests. 8 | } 9 | \seealso{ 10 | \link{linters} for a complete list of linters available in lintr. 11 | } 12 | \section{Linters}{ 13 | The following linters are tagged with 'package_development': 14 | \itemize{ 15 | \item{\code{\link{backport_linter}}} 16 | \item{\code{\link{conjunct_test_linter}}} 17 | \item{\code{\link{expect_comparison_linter}}} 18 | \item{\code{\link{expect_identical_linter}}} 19 | \item{\code{\link{expect_length_linter}}} 20 | \item{\code{\link{expect_named_linter}}} 21 | \item{\code{\link{expect_not_linter}}} 22 | \item{\code{\link{expect_null_linter}}} 23 | \item{\code{\link{expect_s3_class_linter}}} 24 | \item{\code{\link{expect_s4_class_linter}}} 25 | \item{\code{\link{expect_true_false_linter}}} 26 | \item{\code{\link{expect_type_linter}}} 27 | \item{\code{\link{package_hooks_linter}}} 28 | \item{\code{\link{yoda_test_linter}}} 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /man/lengths_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lengths_linter.R 3 | \name{lengths_linter} 4 | \alias{lengths_linter} 5 | \title{Require usage of \code{lengths()} where possible} 6 | \usage{ 7 | lengths_linter() 8 | } 9 | \description{ 10 | \code{\link[base:lengths]{base::lengths()}} is a function that was added to base R in version 3.2.0 to 11 | get the length of each element of a list. It is equivalent to 12 | \code{sapply(x, length)}, but faster and more readable. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "sapply(x, length)", 18 | linters = lengths_linter() 19 | ) 20 | 21 | lint( 22 | text = "vapply(x, length, integer(1L))", 23 | linters = lengths_linter() 24 | ) 25 | 26 | lint( 27 | text = "purrr::map_int(x, length)", 28 | linters = lengths_linter() 29 | ) 30 | 31 | # okay 32 | lint( 33 | text = "lengths(x)", 34 | linters = lengths_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability} 43 | } 44 | -------------------------------------------------------------------------------- /man/for_loop_index_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/for_loop_index_linter.R 3 | \name{for_loop_index_linter} 4 | \alias{for_loop_index_linter} 5 | \title{Block usage of for loops directly overwriting the indexing variable} 6 | \usage{ 7 | for_loop_index_linter() 8 | } 9 | \description{ 10 | \verb{for (x in x)} is a poor choice of indexing variable. This overwrites 11 | \code{x} in the calling scope and is confusing to read. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "for (x in x) { TRUE }", 17 | linters = for_loop_index_linter() 18 | ) 19 | 20 | lint( 21 | text = "for (x in foo(x, y)) { TRUE }", 22 | linters = for_loop_index_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "for (xi in x) { TRUE }", 28 | linters = for_loop_index_linter() 29 | ) 30 | 31 | lint( 32 | text = "for (col in DF$col) { TRUE }", 33 | linters = for_loop_index_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \link{linters} for a complete list of linters available in lintr. 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=readability_linters]{readability}, \link[=robustness_linters]{robustness} 42 | } 43 | -------------------------------------------------------------------------------- /man/spaces_inside_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/spaces_inside_linter.R 3 | \name{spaces_inside_linter} 4 | \alias{spaces_inside_linter} 5 | \title{Spaces inside linter} 6 | \usage{ 7 | spaces_inside_linter() 8 | } 9 | \description{ 10 | Check that parentheses and square brackets do not have spaces directly 11 | inside them, i.e., directly following an opening delimiter or directly 12 | preceding a closing delimiter. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "c( TRUE, FALSE )", 18 | linters = spaces_inside_linter() 19 | ) 20 | 21 | lint( 22 | text = "x[ 1L ]", 23 | linters = spaces_inside_linter() 24 | ) 25 | 26 | # okay 27 | lint( 28 | text = "c(TRUE, FALSE)", 29 | linters = spaces_inside_linter() 30 | ) 31 | 32 | lint( 33 | text = "x[1L]", 34 | linters = spaces_inside_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \itemize{ 40 | \item \link{linters} for a complete list of linters available in lintr. 41 | \item \url{https://style.tidyverse.org/syntax.html#parentheses} 42 | } 43 | } 44 | \section{Tags}{ 45 | \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 46 | } 47 | -------------------------------------------------------------------------------- /tests/testthat/test-list_comparison_linter.R: -------------------------------------------------------------------------------- 1 | test_that("list_comparison_linter skips allowed usages", { 2 | expect_lint("sapply(x, sum) > 10", NULL, list_comparison_linter()) 3 | }) 4 | 5 | local({ 6 | linter <- list_comparison_linter() 7 | lint_msg <- rex::rex("a list(), is being coerced for comparison") 8 | 9 | cases <- expand.grid( 10 | list_mapper = c("lapply", "map", "Map", ".mapply"), 11 | comparator = c("==", "!=", ">=", "<=", ">", "<") 12 | ) 13 | cases$.test_name <- with(cases, paste(list_mapper, comparator)) 14 | patrick::with_parameters_test_that( 15 | "list_comparison_linter blocks simple disallowed usages", 16 | expect_lint(sprintf("%s(x, sum) %s 10", list_mapper, comparator), lint_msg, linter), 17 | .cases = cases 18 | ) 19 | }) 20 | 21 | test_that("list_comparison_linter vectorizes", { 22 | expect_lint( 23 | trim_some("{ 24 | sapply(x, sum) > 10 25 | .mapply(`+`, list(1:10, 1:10), NULL) == 2 26 | lapply(x, sum) < 5 27 | }"), 28 | list( 29 | list(rex::rex(".mapply()", anything, "`==`"), line_number = 3L), 30 | list(rex::rex("lapply()", anything, "`<`"), line_number = 4L) 31 | ), 32 | list_comparison_linter() 33 | ) 34 | }) 35 | -------------------------------------------------------------------------------- /man/boolean_arithmetic_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/boolean_arithmetic_linter.R 3 | \name{boolean_arithmetic_linter} 4 | \alias{boolean_arithmetic_linter} 5 | \title{Require usage of boolean operators over equivalent arithmetic} 6 | \usage{ 7 | boolean_arithmetic_linter() 8 | } 9 | \description{ 10 | \code{length(which(x == y)) == 0} is the same as \code{!any(x == y)}, but the latter 11 | is more readable and more efficient. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "length(which(x == y)) == 0L", 17 | linters = boolean_arithmetic_linter() 18 | ) 19 | 20 | lint( 21 | text = "sum(grepl(pattern, x)) == 0", 22 | linters = boolean_arithmetic_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "!any(x == y)", 28 | linters = boolean_arithmetic_linter() 29 | ) 30 | 31 | lint( 32 | text = "!any(grepl(pattern, x))", 33 | linters = boolean_arithmetic_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \link{linters} for a complete list of linters available in lintr. 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability} 42 | } 43 | -------------------------------------------------------------------------------- /man/system_file_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/system_file_linter.R 3 | \name{system_file_linter} 4 | \alias{system_file_linter} 5 | \title{Block usage of \code{file.path()} with \code{system.file()}} 6 | \usage{ 7 | system_file_linter() 8 | } 9 | \description{ 10 | \code{\link[=system.file]{system.file()}} has a \code{...} argument which, internally, is passed to 11 | \code{\link[=file.path]{file.path()}}, so including it in user code is repetitive. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = 'system.file(file.path("path", "to", "data"), package = "foo")', 17 | linters = system_file_linter() 18 | ) 19 | 20 | lint( 21 | text = 'file.path(system.file(package = "foo"), "path", "to", "data")', 22 | linters = system_file_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = 'system.file("path", "to", "data", package = "foo")', 28 | linters = system_file_linter() 29 | ) 30 | 31 | } 32 | \seealso{ 33 | \link{linters} for a complete list of linters available in lintr. 34 | } 35 | \section{Tags}{ 36 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 37 | } 38 | -------------------------------------------------------------------------------- /R/rep_len_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of rep_len(x, n) over rep(x, length.out = n) 2 | #' 3 | #' `rep(x, length.out = n)` calls `rep_len(x, n)` "under the hood". The latter 4 | #' is thus more direct and equally readable. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "rep(1:3, length.out = 10)", 10 | #' linters = rep_len_linter() 11 | #' ) 12 | #' 13 | #' # okay 14 | #' lint( 15 | #' text = "rep_len(1:3, 10)", 16 | #' linters = rep_len_linter() 17 | #' ) 18 | #' 19 | #' lint( 20 | #' text = "rep(1:3, each = 2L, length.out = 10L)", 21 | #' linters = rep_len_linter() 22 | #' ) 23 | #' 24 | #' @evalRd rd_tags("rep_len_linter") 25 | #' @seealso [linters] for a complete list of linters available in lintr. 26 | #' @export 27 | rep_len_linter <- make_linter_from_function_xpath( 28 | function_names = "rep", 29 | # count(expr) is for cases using positional matching; see ?rep. 30 | xpath = " 31 | parent::expr[ 32 | ( 33 | SYMBOL_SUB[text() = 'length.out'] 34 | or (not(SYMBOL_SUB) and count(expr) = 4) 35 | ) 36 | and not(SYMBOL_SUB[text() = 'each'] or count(expr) = 5) 37 | ] 38 | ", 39 | lint_message = "Use rep_len(x, n) instead of rep(x, length.out = n)." 40 | ) 41 | -------------------------------------------------------------------------------- /man/equals_na_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/equals_na_linter.R 3 | \name{equals_na_linter} 4 | \alias{equals_na_linter} 5 | \title{Equality check with NA linter} 6 | \usage{ 7 | equals_na_linter() 8 | } 9 | \description{ 10 | Check for \code{x == NA}, \code{x != NA} and \code{x \%in\% NA}. Such usage is almost surely incorrect -- 11 | checks for missing values should be done with \code{\link[=is.na]{is.na()}}. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "x == NA", 17 | linters = equals_na_linter() 18 | ) 19 | 20 | lint( 21 | text = "x != NA", 22 | linters = equals_na_linter() 23 | ) 24 | 25 | lint( 26 | text = "x \%in\% NA", 27 | linters = equals_na_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "is.na(x)", 33 | linters = equals_na_linter() 34 | ) 35 | 36 | lint( 37 | text = "!is.na(x)", 38 | linters = equals_na_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=common_mistakes_linters]{common_mistakes}, \link[=correctness_linters]{correctness}, \link[=default_linters]{default}, \link[=robustness_linters]{robustness} 47 | } 48 | -------------------------------------------------------------------------------- /man/expect_length_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_length_linter.R 3 | \name{expect_length_linter} 4 | \alias{expect_length_linter} 5 | \title{Require usage of \code{expect_length(x, n)} over \code{expect_equal(length(x), n)}} 6 | \usage{ 7 | expect_length_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:expect_length]{testthat::expect_length()}} exists specifically for testing the \code{\link[=length]{length()}} of 11 | an object. \code{\link[testthat:equality-expectations]{testthat::expect_equal()}} can also be used for such tests, 12 | but it is better to use the tailored function instead. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "expect_equal(length(x), 2L)", 18 | linters = expect_length_linter() 19 | ) 20 | 21 | # okay 22 | lint( 23 | text = "expect_length(x, 2L)", 24 | linters = expect_length_linter() 25 | ) 26 | 27 | } 28 | \seealso{ 29 | \link{linters} for a complete list of linters available in lintr. 30 | } 31 | \section{Tags}{ 32 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat}, \link[=readability_linters]{readability} 33 | } 34 | -------------------------------------------------------------------------------- /man/cyclocomp_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cyclocomp_linter.R 3 | \name{cyclocomp_linter} 4 | \alias{cyclocomp_linter} 5 | \title{Cyclomatic complexity linter} 6 | \usage{ 7 | cyclocomp_linter(complexity_limit = 15L) 8 | } 9 | \arguments{ 10 | \item{complexity_limit}{Maximum cyclomatic complexity, default \code{15}. Expressions more complex 11 | than this are linted.} 12 | } 13 | \description{ 14 | Check for overly complicated expressions. See \code{cyclocomp()} function from \code{{cyclocomp}}. 15 | } 16 | \examples{ 17 | \dontshow{if (requireNamespace("cyclocomp", quietly = TRUE)) withAutoprint(\{ # examplesIf} 18 | # will produce lints 19 | lint( 20 | text = "if (TRUE) 1 else 2", 21 | linters = cyclocomp_linter(complexity_limit = 1L) 22 | ) 23 | 24 | # okay 25 | lint( 26 | text = "if (TRUE) 1 else 2", 27 | linters = cyclocomp_linter(complexity_limit = 2L) 28 | ) 29 | \dontshow{\}) # examplesIf} 30 | } 31 | \seealso{ 32 | \link{linters} for a complete list of linters available in lintr. 33 | } 34 | \section{Tags}{ 35 | \link[=best_practices_linters]{best_practices}, \link[=configurable_linters]{configurable}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 36 | } 37 | -------------------------------------------------------------------------------- /R/lintr-package.R: -------------------------------------------------------------------------------- 1 | #' Lintr 2 | #' 3 | #' Checks adherence to a given style, syntax errors, and possible semantic issues. 4 | #' Supports on the fly checking of R code edited with Emacs, Vim, and Sublime Text. 5 | #' 6 | #' @seealso [lint()], [lint_package()], [lint_dir()], [linters] 7 | #' @keywords internal 8 | "_PACKAGE" 9 | 10 | ## lintr namespace: start 11 | #' @importFrom cli cli_inform cli_abort cli_warn qty 12 | #' @importFrom glue glue glue_collapse 13 | #' @importFrom rex rex regex re_matches re_substitutes character_class 14 | #' @importFrom stats complete.cases na.omit 15 | #' @importFrom tools R_user_dir 16 | #' @importFrom utils capture.output getParseData globalVariables head relist tail 17 | #' @importFrom xml2 as_list 18 | #' xml_attr xml_children xml_find_all xml_find_chr xml_find_lgl xml_find_num 19 | #' xml_find_first xml_name xml_parent xml_text 20 | ## lintr namespace: end 21 | NULL 22 | 23 | # make binding available for mock testing 24 | # ref: https://testthat.r-lib.org/dev/reference/local_mocked_bindings.html#base-functions 25 | # nolint start: object_name_linter. These will be copied from base style. 26 | requireNamespace <- NULL 27 | system.file <- NULL 28 | unlink <- NULL 29 | quit <- NULL 30 | # nolint end: object_name_linter. 31 | -------------------------------------------------------------------------------- /man/outer_negation_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/outer_negation_linter.R 3 | \name{outer_negation_linter} 4 | \alias{outer_negation_linter} 5 | \title{Require usage of \code{!any(x)} over \code{all(!x)}, \code{!all(x)} over \code{any(!x)}} 6 | \usage{ 7 | outer_negation_linter() 8 | } 9 | \description{ 10 | \code{any(!x)} is logically equivalent to \code{!all(x)}; ditto for the equivalence of 11 | \code{all(!x)} and \code{!any(x)}. Negating after aggregation only requires inverting 12 | one logical value, and is typically more readable. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "all(!x)", 18 | linters = outer_negation_linter() 19 | ) 20 | 21 | lint( 22 | text = "any(!x)", 23 | linters = outer_negation_linter() 24 | ) 25 | 26 | # okay 27 | lint( 28 | text = "!any(x)", 29 | linters = outer_negation_linter() 30 | ) 31 | 32 | lint( 33 | text = "!all(x)", 34 | linters = outer_negation_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability} 43 | } 44 | -------------------------------------------------------------------------------- /tests/testthat/test-list2df_linter.R: -------------------------------------------------------------------------------- 1 | test_that("list2df_linter skips allowed usages", { 2 | linter <- list2df_linter() 3 | 4 | expect_no_lint("cbind.data.frame(x, x)", linter) 5 | expect_no_lint("do.call(mean, x)", linter) 6 | expect_no_lint("do.call('c', x)", linter) 7 | 8 | # Other cbind methods 9 | expect_no_lint("do.call(cbind, x)", linter) 10 | 11 | # Anonymous function 12 | expect_no_lint("do.call(function(x) x, l)", linter) 13 | }) 14 | 15 | test_that("list2df_linter blocks simple disallowed usages", { 16 | linter <- list2df_linter() 17 | lint_message <- rex::rex("use `data.frame(lst)`") 18 | 19 | expect_lint("do.call(cbind.data.frame, x)", lint_message, linter) 20 | expect_lint("do.call('cbind.data.frame', x)", lint_message, linter) 21 | }) 22 | 23 | test_that("lints vectorize", { 24 | lint_message <- rex::rex("use `data.frame(lst)`") 25 | 26 | expect_lint( 27 | trim_some("{ 28 | cbind(a, b) 29 | do.call(cbind.data.frame, x) 30 | do.call(function(x) x, l) 31 | do.call('cbind.data.frame', y) 32 | }"), 33 | list( 34 | list(lint_message, line_number = 3L, column_number = 3L), 35 | list(lint_message, line_number = 5L, column_number = 3L) 36 | ), 37 | list2df_linter() 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /R/numeric_leading_zero_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of a leading zero in all fractional numerics 2 | #' 3 | #' While .1 and 0.1 mean the same thing, the latter is easier to read due 4 | #' to the small size of the '.' glyph. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "x <- .1", 10 | #' linters = numeric_leading_zero_linter() 11 | #' ) 12 | #' 13 | #' lint( 14 | #' text = "x <- -.1", 15 | #' linters = numeric_leading_zero_linter() 16 | #' ) 17 | #' 18 | #' # okay 19 | #' lint( 20 | #' text = "x <- 0.1", 21 | #' linters = numeric_leading_zero_linter() 22 | #' ) 23 | #' 24 | #' lint( 25 | #' text = "x <- -0.1", 26 | #' linters = numeric_leading_zero_linter() 27 | #' ) 28 | #' 29 | #' @evalRd rd_tags("numeric_leading_zero_linter") 30 | #' @seealso [linters] for a complete list of linters available in lintr. 31 | #' @export 32 | numeric_leading_zero_linter <- make_linter_from_xpath( 33 | # NB: 34 | # 1. negative constants are split to two components: 35 | # OP-MINUS, NUM_CONST 36 | # 2. complex constants are split to three components: 37 | # NUM_CONST, OP-PLUS/OP-MINUS, NUM_CONST 38 | xpath = "//NUM_CONST[starts-with(text(), '.')]", 39 | lint_message = "Include the leading zero for fractional numeric constants." 40 | ) 41 | -------------------------------------------------------------------------------- /.github/workflows/test-package-vigilant.yaml: -------------------------------------------------------------------------------- 1 | # based on test-coverage, running testthat with options(warn = 2) to fail on test warnings 2 | on: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | 8 | name: test-package 9 | 10 | jobs: 11 | test-package: 12 | runs-on: ubuntu-latest 13 | env: 14 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | steps: 17 | - uses: actions/checkout@v6 18 | 19 | - uses: r-lib/actions/setup-r@v2 20 | with: 21 | use-public-rspm: true 22 | 23 | - uses: r-lib/actions/setup-r-dependencies@v2 24 | with: 25 | install-quarto: false 26 | extra-packages: local::. 27 | 28 | - name: Run Tests 29 | run: | 30 | ## -------------------------------------------------------------------- 31 | options( 32 | crayon.enabled = TRUE, 33 | warn = 2L, 34 | warnPartialMatchArgs = TRUE, 35 | warnPartialMatchAttr = TRUE, 36 | warnPartialMatchDollar = TRUE 37 | ) 38 | if (Sys.getenv("_R_CHECK_FORCE_SUGGESTS_", "") == "") Sys.setenv("_R_CHECK_FORCE_SUGGESTS_" = "false") 39 | testthat::test_dir("tests") 40 | shell: Rscript {0} 41 | -------------------------------------------------------------------------------- /man/sprintf_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sprintf_linter.R 3 | \name{sprintf_linter} 4 | \alias{sprintf_linter} 5 | \title{Require correct \code{sprintf()} calls} 6 | \usage{ 7 | sprintf_linter() 8 | } 9 | \description{ 10 | Check for an inconsistent number of arguments or arguments with incompatible types (for literal arguments) in 11 | \code{\link[=sprintf]{sprintf()}} calls. 12 | } 13 | \details{ 14 | \code{\link[=gettextf]{gettextf()}} calls are also included, since \code{gettextf()} is a thin wrapper around \code{sprintf()}. 15 | } 16 | \examples{ 17 | # will produce lints 18 | lint( 19 | text = 'sprintf("hello \%s \%s \%d", x, y)', 20 | linters = sprintf_linter() 21 | ) 22 | 23 | lint( 24 | text = 'sprintf("hello")', 25 | linters = sprintf_linter() 26 | ) 27 | 28 | # okay 29 | lint( 30 | text = 'sprintf("hello \%s \%s \%d", x, y, z)', 31 | linters = sprintf_linter() 32 | ) 33 | 34 | lint( 35 | text = 'sprintf("hello \%s \%s \%d", x, y, ...)', 36 | linters = sprintf_linter() 37 | ) 38 | 39 | } 40 | \seealso{ 41 | \link{linters} for a complete list of linters available in lintr. 42 | } 43 | \section{Tags}{ 44 | \link[=common_mistakes_linters]{common_mistakes}, \link[=correctness_linters]{correctness} 45 | } 46 | -------------------------------------------------------------------------------- /man/comparison_negation_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/comparison_negation_linter.R 3 | \name{comparison_negation_linter} 4 | \alias{comparison_negation_linter} 5 | \title{Block usages like !(x == y) where a direct relational operator is appropriate} 6 | \usage{ 7 | comparison_negation_linter() 8 | } 9 | \description{ 10 | \code{!(x == y)} is more readably expressed as \code{x != y}. The same is true of 11 | other negations of simple comparisons like \code{!(x > y)} and \code{!(x <= y)}. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "!x == 2", 17 | linters = comparison_negation_linter() 18 | ) 19 | 20 | lint( 21 | text = "!(x > 2)", 22 | linters = comparison_negation_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "!(x == 2 & y > 2)", 28 | linters = comparison_negation_linter() 29 | ) 30 | 31 | lint( 32 | text = "!(x & y)", 33 | linters = comparison_negation_linter() 34 | ) 35 | 36 | lint( 37 | text = "x != 2", 38 | linters = comparison_negation_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 47 | } 48 | -------------------------------------------------------------------------------- /R/for_loop_index_linter.R: -------------------------------------------------------------------------------- 1 | #' Block usage of for loops directly overwriting the indexing variable 2 | #' 3 | #' `for (x in x)` is a poor choice of indexing variable. This overwrites 4 | #' `x` in the calling scope and is confusing to read. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "for (x in x) { TRUE }", 10 | #' linters = for_loop_index_linter() 11 | #' ) 12 | #' 13 | #' lint( 14 | #' text = "for (x in foo(x, y)) { TRUE }", 15 | #' linters = for_loop_index_linter() 16 | #' ) 17 | #' 18 | #' # okay 19 | #' lint( 20 | #' text = "for (xi in x) { TRUE }", 21 | #' linters = for_loop_index_linter() 22 | #' ) 23 | #' 24 | #' lint( 25 | #' text = "for (col in DF$col) { TRUE }", 26 | #' linters = for_loop_index_linter() 27 | #' ) 28 | #' 29 | #' @evalRd rd_tags("for_loop_index_linter") 30 | #' @seealso [linters] for a complete list of linters available in lintr. 31 | #' @export 32 | for_loop_index_linter <- make_linter_from_xpath( 33 | xpath = " 34 | //forcond 35 | /SYMBOL[text() = 36 | following-sibling::expr 37 | //SYMBOL[not(parent::expr[OP-DOLLAR or OP-AT or preceding-sibling::OP-LEFT-BRACKET])] 38 | /text() 39 | ] 40 | ", 41 | lint_message = "Don't re-use any sequence symbols as the index symbol in a for loop." 42 | ) 43 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | bibentry( 2 | "Article", 3 | doi = "10.21105/joss.07240", 4 | year = 2025, 5 | publisher = "{Open Journals}", 6 | volume = 10, 7 | number = 108, 8 | pages = "7240", 9 | author = c( 10 | person(given = "Jim", family = "Hester", role = c("aut"), email = NULL, comment = c(ORCID = "0000-0002-2739-7082")), 11 | person(given = "Florent", family = "Angly", role = c("aut"), email = NULL, comment = c(ORCID = "0000-0002-8999-0738")), 12 | person(given = "Michael", family = "Chirico", role = c("aut"), email = NULL, comment = c(ORCID = "0000-0003-0787-087X")), 13 | person(given = "Russ", family = "Hyde", role = c("aut")), 14 | person(given = "Ren", family = "Kun", role = c("aut")), 15 | person(given = "Indrajeet", family = "Patil", role = c("aut"), email = NULL, comment = c(ORCID = "0000-0003-1995-6531")), 16 | person(given = "Alexander", family = "Rosenstock", role = c("aut")) 17 | ), 18 | title = "Static Code Analysis for R", 19 | journal = "{Journal of Open Source Software}", 20 | textVersion = paste( 21 | "Hester, J., Angly, F., Chirico, M., Hyde, R., Kun, R., Patil, I., & Rosenstock, A.", 22 | "(2025). Static Code Analysis for R. Journal of Open Source Software, 10(108), 7240.", 23 | "https://doi.org/10.21105/joss.07240" 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /man/any_is_na_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/any_is_na_linter.R 3 | \name{any_is_na_linter} 4 | \alias{any_is_na_linter} 5 | \title{Require usage of \code{anyNA(x)} over \code{any(is.na(x))}} 6 | \usage{ 7 | any_is_na_linter() 8 | } 9 | \description{ 10 | \code{\link[base:NA]{base::anyNA()}} exists as a replacement for \code{any(is.na(x))} which is more efficient 11 | for simple objects, and is at worst equally efficient. 12 | Therefore, it should be used in all situations instead of the latter. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "any(is.na(x), na.rm = TRUE)", 18 | linters = any_is_na_linter() 19 | ) 20 | 21 | lint( 22 | text = "any(is.na(foo(x)))", 23 | linters = any_is_na_linter() 24 | ) 25 | 26 | # okay 27 | lint( 28 | text = "anyNA(x)", 29 | linters = any_is_na_linter() 30 | ) 31 | 32 | lint( 33 | text = "anyNA(foo(x))", 34 | linters = any_is_na_linter() 35 | ) 36 | 37 | lint( 38 | text = "any(!is.na(x), na.rm = TRUE)", 39 | linters = any_is_na_linter() 40 | ) 41 | 42 | } 43 | \seealso{ 44 | \link{linters} for a complete list of linters available in lintr. 45 | } 46 | \section{Tags}{ 47 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency} 48 | } 49 | -------------------------------------------------------------------------------- /man/expect_not_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_not_linter.R 3 | \name{expect_not_linter} 4 | \alias{expect_not_linter} 5 | \title{Require usage of \code{expect_false(x)} over \code{expect_true(!x)}} 6 | \usage{ 7 | expect_not_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:logical-expectations]{testthat::expect_false()}} exists specifically for testing that an output is 11 | \code{FALSE}. \code{\link[testthat:logical-expectations]{testthat::expect_true()}} can also be used for such tests by 12 | negating the output, but it is better to use the tailored function instead. 13 | The reverse is also true -- use \code{expect_false(A)} instead of 14 | \code{expect_true(!A)}. 15 | } 16 | \examples{ 17 | # will produce lints 18 | lint( 19 | text = "expect_true(!x)", 20 | linters = expect_not_linter() 21 | ) 22 | 23 | # okay 24 | lint( 25 | text = "expect_false(x)", 26 | linters = expect_not_linter() 27 | ) 28 | 29 | } 30 | \seealso{ 31 | \link{linters} for a complete list of linters available in lintr. 32 | } 33 | \section{Tags}{ 34 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat}, \link[=readability_linters]{readability} 35 | } 36 | -------------------------------------------------------------------------------- /man/sample_int_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sample_int_linter.R 3 | \name{sample_int_linter} 4 | \alias{sample_int_linter} 5 | \title{Require usage of sample.int(n, m, ...) over sample(1:n, m, ...)} 6 | \usage{ 7 | sample_int_linter() 8 | } 9 | \description{ 10 | \code{\link[=sample.int]{sample.int()}} is preferable to \code{sample()} for the case of sampling numbers 11 | between 1 and \code{n}. \code{sample} calls \code{sample.int()} "under the hood". 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "sample(1:10, 2)", 17 | linters = sample_int_linter() 18 | ) 19 | 20 | lint( 21 | text = "sample(seq(4), 2)", 22 | linters = sample_int_linter() 23 | ) 24 | 25 | lint( 26 | text = "sample(seq_len(8), 2)", 27 | linters = sample_int_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "sample(seq(1, 5, by = 2), 2)", 33 | linters = sample_int_linter() 34 | ) 35 | 36 | lint( 37 | text = "sample(letters, 2)", 38 | linters = sample_int_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability}, \link[=robustness_linters]{robustness} 47 | } 48 | -------------------------------------------------------------------------------- /man/expect_s4_class_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_s4_class_linter.R 3 | \name{expect_s4_class_linter} 4 | \alias{expect_s4_class_linter} 5 | \title{Require usage of \code{expect_s4_class(x, k)} over \code{expect_true(is(x, k))}} 6 | \usage{ 7 | expect_s4_class_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:inheritance-expectations]{testthat::expect_s4_class()}} exists specifically for testing the class 11 | of S4 objects. \code{\link[testthat:logical-expectations]{testthat::expect_true()}} can also be used for such tests, 12 | but it is better to use the tailored function instead. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = 'expect_true(is(x, "Matrix"))', 18 | linters = expect_s4_class_linter() 19 | ) 20 | 21 | # okay 22 | lint( 23 | text = 'expect_s4_class(x, "Matrix")', 24 | linters = expect_s4_class_linter() 25 | ) 26 | 27 | } 28 | \seealso{ 29 | \itemize{ 30 | \item \link{linters} for a complete list of linters available in lintr. 31 | \item \code{\link[=expect_s3_class_linter]{expect_s3_class_linter()}} 32 | } 33 | } 34 | \section{Tags}{ 35 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat} 36 | } 37 | -------------------------------------------------------------------------------- /man/routine_registration_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/routine_registration_linter.R 3 | \name{routine_registration_linter} 4 | \alias{routine_registration_linter} 5 | \title{Identify unregistered native routines} 6 | \usage{ 7 | routine_registration_linter() 8 | } 9 | \description{ 10 | It is preferable to register routines for efficiency and safety. 11 | } 12 | \examples{ 13 | # will produce lints 14 | lint( 15 | text = '.Call("cpp_routine", PACKAGE = "mypkg")', 16 | linters = routine_registration_linter() 17 | ) 18 | 19 | lint( 20 | text = '.Fortran("f_routine", PACKAGE = "mypkg")', 21 | linters = routine_registration_linter() 22 | ) 23 | 24 | # okay 25 | lint( 26 | text = ".Call(cpp_routine)", 27 | linters = routine_registration_linter() 28 | ) 29 | 30 | lint( 31 | text = ".Fortran(f_routine)", 32 | linters = routine_registration_linter() 33 | ) 34 | 35 | } 36 | \seealso{ 37 | \itemize{ 38 | \item \link{linters} for a complete list of linters available in lintr. 39 | \item \url{https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Registering-native-routines} 40 | } 41 | } 42 | \section{Tags}{ 43 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency}, \link[=robustness_linters]{robustness} 44 | } 45 | -------------------------------------------------------------------------------- /R/routine_registration_linter.R: -------------------------------------------------------------------------------- 1 | #' Identify unregistered native routines 2 | #' 3 | #' It is preferable to register routines for efficiency and safety. 4 | #' 5 | #' @examples 6 | #' # will produce lints 7 | #' lint( 8 | #' text = '.Call("cpp_routine", PACKAGE = "mypkg")', 9 | #' linters = routine_registration_linter() 10 | #' ) 11 | #' 12 | #' lint( 13 | #' text = '.Fortran("f_routine", PACKAGE = "mypkg")', 14 | #' linters = routine_registration_linter() 15 | #' ) 16 | #' 17 | #' # okay 18 | #' lint( 19 | #' text = ".Call(cpp_routine)", 20 | #' linters = routine_registration_linter() 21 | #' ) 22 | #' 23 | #' lint( 24 | #' text = ".Fortran(f_routine)", 25 | #' linters = routine_registration_linter() 26 | #' ) 27 | #' 28 | #' @evalRd rd_tags("routine_registration_linter") 29 | #' @seealso 30 | #' - [linters] for a complete list of linters available in lintr. 31 | #' - 32 | #' 33 | #' @export 34 | routine_registration_linter <- make_linter_from_function_xpath( 35 | function_names = c(".C", ".Call", ".Fortran", ".External"), 36 | xpath = " 37 | following-sibling::expr[1]/STR_CONST 38 | /parent::expr 39 | ", 40 | lint_message = "Register your native code routines with useDynLib and R_registerRoutines()." 41 | ) 42 | -------------------------------------------------------------------------------- /R/stopifnot_all_linter.R: -------------------------------------------------------------------------------- 1 | #' Block usage of all() within stopifnot() 2 | #' 3 | #' `stopifnot(A)` actually checks `all(A)` "under the hood" if `A` is a vector, 4 | #' and produces a better error message than `stopifnot(all(A))` does. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "stopifnot(all(x > 0))", 10 | #' linters = stopifnot_all_linter() 11 | #' ) 12 | #' 13 | #' lint( 14 | #' text = "stopifnot(y > 3, all(x < 0))", 15 | #' linters = stopifnot_all_linter() 16 | #' ) 17 | #' 18 | #' # okay 19 | #' lint( 20 | #' text = "stopifnot(is.null(x) || all(x > 0))", 21 | #' linters = stopifnot_all_linter() 22 | #' ) 23 | #' 24 | #' lint( 25 | #' text = "assert_that(all(x > 0))", 26 | #' linters = stopifnot_all_linter() 27 | #' ) 28 | #' 29 | #' @evalRd rd_tags("stopifnot_all_linter") 30 | #' @seealso [linters] for a complete list of linters available in lintr. 31 | #' @export 32 | stopifnot_all_linter <- make_linter_from_function_xpath( 33 | function_names = "stopifnot", 34 | xpath = " 35 | parent::expr 36 | /expr[expr/SYMBOL_FUNCTION_CALL[text() = 'all']] 37 | ", 38 | lint_message = paste( 39 | "Use stopifnot(x) instead of stopifnot(all(x)).", 40 | "stopifnot(x) runs all() 'under the hood' and provides a better error message in case of failure." 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /tests/testthat/default_linter_testcode.R: -------------------------------------------------------------------------------- 1 | # Each of the default linters should throw at least one lint on this file 2 | 3 | # assignment 4 | # function_left_parentheses 5 | # brace_linter 6 | # commas 7 | # paren_brace 8 | f = function (x,y = 1){} 9 | 10 | # return_linter 11 | g <- function(x) { 12 | return(x + 1) 13 | } 14 | 15 | # commented_code 16 | # some <- commented("out code") 17 | 18 | # equals_na 19 | # brace_linter 20 | # indentation 21 | # infix_spaces 22 | # line_length 23 | # object_length 24 | # object_name 25 | # object_usage 26 | # open_curly 27 | # T_and_F_symbol 28 | someComplicatedFunctionWithALongCamelCaseName <- function(x) 29 | { 30 | y <- 1 31 | if (1 > 2 && 2 > 3 && 3 > 4 && 4 > 5 && 5*10 > 6 && 5 > 6 && 6 > 7 && x == NA) {T} else F 32 | } 33 | 34 | # vector_logic 35 | if (1 & 2) FALSE else TRUE 36 | 37 | # function_brace 38 | my_metric <- function(x) 39 | sum(x) + prod(x) 40 | 41 | # no_tab 42 | # pipe_consistency 43 | # pipe_continuation 44 | # seq_linter 45 | # spaces_inside 46 | # indentation 47 | x <- 1:10 48 | x[ 2] 49 | 1:length(x) %>% lapply(function(x) x*2) %>% 50 | head() 51 | 52 | # single_quotes 53 | message('single_quotes') 54 | 55 | # spaces_left_parentheses 56 | # trailing_whitespace 57 | # semicolon 58 | x <- 42; y <- 2 +(1:10) 59 | 60 | # trailing_blank_lines 61 | 62 | -------------------------------------------------------------------------------- /man/is_lint_level.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/is_lint_level.R 3 | \name{is_lint_level} 4 | \alias{is_lint_level} 5 | \title{Is this an expression- or a file-level source object?} 6 | \usage{ 7 | is_lint_level(source_expression, level = c("expression", "file")) 8 | } 9 | \arguments{ 10 | \item{source_expression}{A parsed expression object, i.e., an element 11 | of the object returned by \code{\link[=get_source_expressions]{get_source_expressions()}}.} 12 | 13 | \item{level}{Which level of expression is being tested? \code{"expression"} 14 | means an individual expression, while \code{"file"} means all expressions 15 | in the current file are available.} 16 | } 17 | \description{ 18 | Helper for determining whether the current \code{source_expression} contains 19 | all expressions in the current file, or just a single expression. 20 | } 21 | \examples{ 22 | tmp <- tempfile() 23 | writeLines(c("x <- 1", "y <- x + 1"), tmp) 24 | source_exprs <- get_source_expressions(tmp) 25 | is_lint_level(source_exprs$expressions[[1L]], level = "expression") 26 | is_lint_level(source_exprs$expressions[[1L]], level = "file") 27 | is_lint_level(source_exprs$expressions[[3L]], level = "expression") 28 | is_lint_level(source_exprs$expressions[[3L]], level = "file") 29 | unlink(tmp) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /man/list2df_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/list2df_linter.R 3 | \name{list2df_linter} 4 | \alias{list2df_linter} 5 | \title{Recommend direct usage of \code{data.frame()} to create a data.frame from a list} 6 | \usage{ 7 | list2df_linter() 8 | } 9 | \description{ 10 | \code{\link[base:list2DF]{base::list2DF()}} is the preferred way to turn a list of columns into a data.frame. 11 | Note that it doesn't support recycling; if that's required, use \code{\link[=data.frame]{data.frame()}}. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "do.call(cbind.data.frame, x)", 17 | linters = list2df_linter() 18 | ) 19 | 20 | lint( 21 | text = "do.call('cbind.data.frame', x)", 22 | linters = list2df_linter() 23 | ) 24 | 25 | lint( 26 | text = "do.call(cbind.data.frame, list(a = 1, b = 1:10))", 27 | linters = list2df_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "list2df(x)", 33 | linters = list2df_linter() 34 | ) 35 | 36 | lint( 37 | text = "data.frame(list(a = 1, b = 1:10))", 38 | linters = list2df_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=efficiency_linters]{efficiency}, \link[=readability_linters]{readability} 47 | } 48 | -------------------------------------------------------------------------------- /man/terminal_close_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/terminal_close_linter.R 3 | \name{terminal_close_linter} 4 | \alias{terminal_close_linter} 5 | \title{Prohibit close() from terminating a function definition} 6 | \usage{ 7 | terminal_close_linter() 8 | } 9 | \description{ 10 | Functions that end in \code{close(x)} are almost always better written by using 11 | \code{on.exit(close(x))} close to where \code{x} is defined and/or opened. 12 | } 13 | \examples{ 14 | # will produce lints 15 | code <- paste( 16 | "f <- function(fl) {", 17 | " conn <- file(fl, open = 'r')", 18 | " readLines(conn)", 19 | " close(conn)", 20 | "}", 21 | sep = "\n" 22 | ) 23 | writeLines(code) 24 | lint( 25 | text = code, 26 | linters = terminal_close_linter() 27 | ) 28 | 29 | # okay 30 | code <- paste( 31 | "f <- function(fl) {", 32 | " conn <- file(fl, open = 'r')", 33 | " on.exit(close(conn))", 34 | " readLines(conn)", 35 | "}", 36 | sep = "\n" 37 | ) 38 | writeLines(code) 39 | lint( 40 | text = code, 41 | linters = terminal_close_linter() 42 | ) 43 | 44 | } 45 | \seealso{ 46 | \link{linters} for a complete list of linters available in lintr. 47 | } 48 | \section{Tags}{ 49 | \link[=best_practices_linters]{best_practices}, \link[=robustness_linters]{robustness} 50 | } 51 | -------------------------------------------------------------------------------- /man/absolute_path_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/absolute_path_linter.R 3 | \name{absolute_path_linter} 4 | \alias{absolute_path_linter} 5 | \title{Absolute path linter} 6 | \usage{ 7 | absolute_path_linter(lax = TRUE) 8 | } 9 | \arguments{ 10 | \item{lax}{Less stringent linting, leading to fewer false positives. 11 | If \code{TRUE}, only lint path strings, which 12 | \itemize{ 13 | \item contain at least two path elements, with one having at least two characters and 14 | \item contain only alphanumeric chars (including UTF-8), spaces, and win32-allowed punctuation 15 | }} 16 | } 17 | \description{ 18 | Check that no absolute paths are used (e.g. "/var", "C:\\System", "~/docs"). 19 | } 20 | \examples{ 21 | # will produce lints 22 | lint( 23 | text = 'R"(/blah/file.txt)"', 24 | linters = absolute_path_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = 'R"(./blah)"', 30 | linters = absolute_path_linter() 31 | ) 32 | 33 | } 34 | \seealso{ 35 | \itemize{ 36 | \item \link{linters} for a complete list of linters available in lintr. 37 | \item \code{\link[=nonportable_path_linter]{nonportable_path_linter()}} 38 | } 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=configurable_linters]{configurable}, \link[=robustness_linters]{robustness} 42 | } 43 | -------------------------------------------------------------------------------- /tests/testthat/dummy_packages/package/vignettes/test.Rmd: -------------------------------------------------------------------------------- 1 | # Test # 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | ```{r} 9 | a = 1 10 | ``` 11 | 12 | Test 13 | ==== 14 | 15 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 16 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 17 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 18 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 19 | 20 | ```{r} 21 | b <- function(x) { 22 | d = 1 23 | } 24 | 25 | ``` 26 | 27 | ```{r engine="python"} 28 | a=[] 29 | 30 | a[0]=1 31 | ``` 32 | 33 | ``` 34 | Plain code blocks can be written after three or more backticks 35 | - R Markdown: The Definitive Guide. Xie, Allaire and Grolemund (2.5.2) 36 | ``` 37 | 38 | Calls to a non-R knitr-engine using {engine_name} syntax. 39 | 40 | ```{python} 41 | # Python that looks like R 42 | a = list() 43 | b = {2} 44 | print(a) 45 | ``` 46 | 47 | ```{python} 48 | # Python that's definitely not R 49 | a = [] 50 | a.append(2) 51 | print(a) 52 | ``` 53 | -------------------------------------------------------------------------------- /man/consecutive_assertion_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/consecutive_assertion_linter.R 3 | \name{consecutive_assertion_linter} 4 | \alias{consecutive_assertion_linter} 5 | \title{Force consecutive calls to assertions into just one when possible} 6 | \usage{ 7 | consecutive_assertion_linter() 8 | } 9 | \description{ 10 | \code{\link[base:stopifnot]{base::stopifnot()}} accepts any number of tests, so sequences like 11 | \verb{stopifnot(x); stopifnot(y)} are redundant. Ditto for tests using 12 | \code{assertthat::assert_that()} without specifying \verb{msg=}. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = "stopifnot(x); stopifnot(y)", 18 | linters = consecutive_assertion_linter() 19 | ) 20 | 21 | lint( 22 | text = "assert_that(x); assert_that(y)", 23 | linters = consecutive_assertion_linter() 24 | ) 25 | 26 | # okay 27 | lint( 28 | text = "stopifnot(x, y)", 29 | linters = consecutive_assertion_linter() 30 | ) 31 | 32 | lint( 33 | text = 'assert_that(x, msg = "Bad x!"); assert_that(y)', 34 | linters = consecutive_assertion_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 43 | } 44 | -------------------------------------------------------------------------------- /man/missing_argument_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/missing_argument_linter.R 3 | \name{missing_argument_linter} 4 | \alias{missing_argument_linter} 5 | \title{Missing argument linter} 6 | \usage{ 7 | missing_argument_linter( 8 | except = c("alist", "quote", "switch"), 9 | allow_trailing = FALSE 10 | ) 11 | } 12 | \arguments{ 13 | \item{except}{a character vector of function names as exceptions.} 14 | 15 | \item{allow_trailing}{always allow trailing empty arguments?} 16 | } 17 | \description{ 18 | Check for missing arguments in function calls (e.g. \code{stats::median(1:10, )}). 19 | } 20 | \examples{ 21 | # will produce lints 22 | lint( 23 | text = 'tibble(x = "a", )', 24 | linters = missing_argument_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = 'tibble(x = "a")', 30 | linters = missing_argument_linter() 31 | ) 32 | 33 | lint( 34 | text = 'tibble(x = "a", )', 35 | linters = missing_argument_linter(except = "tibble") 36 | ) 37 | 38 | lint( 39 | text = 'tibble(x = "a", )', 40 | linters = missing_argument_linter(allow_trailing = TRUE) 41 | ) 42 | 43 | } 44 | \seealso{ 45 | \link{linters} for a complete list of linters available in lintr. 46 | } 47 | \section{Tags}{ 48 | \link[=common_mistakes_linters]{common_mistakes}, \link[=configurable_linters]{configurable}, \link[=correctness_linters]{correctness} 49 | } 50 | -------------------------------------------------------------------------------- /man/use_lintr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/use_lintr.R 3 | \name{use_lintr} 4 | \alias{use_lintr} 5 | \title{Use lintr in your project} 6 | \usage{ 7 | use_lintr(path = ".", type = c("tidyverse", "full")) 8 | } 9 | \arguments{ 10 | \item{path}{Path to project root, where a \code{.lintr} file should be created. 11 | If the \code{.lintr} file already exists, an error will be thrown.} 12 | 13 | \item{type}{What kind of configuration to create? 14 | \itemize{ 15 | \item \code{tidyverse} creates a minimal lintr config, based on the default linters (\code{\link[=linters_with_defaults]{linters_with_defaults()}}). 16 | These are suitable for following \href{https://style.tidyverse.org/}{the tidyverse style guide}. 17 | \item \code{full} creates a lintr config using all available linters via \code{\link[=all_linters]{all_linters()}}. 18 | }} 19 | } 20 | \value{ 21 | Path to the generated configuration, invisibly. 22 | } 23 | \description{ 24 | Create a minimal lintr config file as a starting point for customization 25 | } 26 | \examples{ 27 | if (FALSE) { 28 | # use the default set of linters 29 | lintr::use_lintr() 30 | # or try all linters 31 | lintr::use_lintr(type = "full") 32 | 33 | # then 34 | lintr::lint_dir() 35 | } 36 | } 37 | \seealso{ 38 | \code{vignette("lintr")} for detailed introduction to using and configuring lintr. 39 | } 40 | -------------------------------------------------------------------------------- /man/ifelse_censor_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ifelse_censor_linter.R 3 | \name{ifelse_censor_linter} 4 | \alias{ifelse_censor_linter} 5 | \title{Block usage of \code{ifelse()} where \code{pmin()} or \code{pmax()} is more appropriate} 6 | \usage{ 7 | ifelse_censor_linter() 8 | } 9 | \description{ 10 | \code{ifelse(x > M, M, x)} is the same as \code{pmin(x, M)}, but harder 11 | to read and requires several passes over the vector. 12 | } 13 | \details{ 14 | The same goes for other similar ways to censor a vector, e.g. 15 | \code{ifelse(x <= M, x, M)} is \code{pmin(x, M)}, 16 | \code{ifelse(x < m, m, x)} is \code{pmax(x, m)}, and 17 | \code{ifelse(x >= m, x, m)} is \code{pmax(x, m)}. 18 | } 19 | \examples{ 20 | # will produce lints 21 | lint( 22 | text = "ifelse(5:1 < pi, 5:1, pi)", 23 | linters = ifelse_censor_linter() 24 | ) 25 | 26 | lint( 27 | text = "ifelse(x > 0, x, 0)", 28 | linters = ifelse_censor_linter() 29 | ) 30 | 31 | # okay 32 | lint( 33 | text = "pmin(5:1, pi)", 34 | linters = ifelse_censor_linter() 35 | ) 36 | 37 | lint( 38 | text = "pmax(x, 0)", 39 | linters = ifelse_censor_linter() 40 | ) 41 | 42 | } 43 | \seealso{ 44 | \link{linters} for a complete list of linters available in lintr. 45 | } 46 | \section{Tags}{ 47 | \link[=best_practices_linters]{best_practices}, \link[=efficiency_linters]{efficiency} 48 | } 49 | -------------------------------------------------------------------------------- /man/yoda_test_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/yoda_test_linter.R 3 | \name{yoda_test_linter} 4 | \alias{yoda_test_linter} 5 | \title{Block obvious "yoda tests"} 6 | \usage{ 7 | yoda_test_linter() 8 | } 9 | \description{ 10 | Yoda tests use \verb{(expected, actual)} instead of the more common \verb{(actual, expected)}. 11 | This is not always possible to detect statically; this linter focuses on 12 | the simple case of testing an expression against a literal value, e.g. 13 | \verb{(1L, foo(x))} should be \verb{(foo(x), 1L)}. 14 | } 15 | \examples{ 16 | # will produce lints 17 | lint( 18 | text = "expect_equal(2, x)", 19 | linters = yoda_test_linter() 20 | ) 21 | 22 | lint( 23 | text = 'expect_identical("a", x)', 24 | linters = yoda_test_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = "expect_equal(x, 2)", 30 | linters = yoda_test_linter() 31 | ) 32 | 33 | lint( 34 | text = 'expect_identical(x, "a")', 35 | linters = yoda_test_linter() 36 | ) 37 | 38 | } 39 | \seealso{ 40 | \link{linters} for a complete list of linters available in lintr. 41 | \url{https://en.wikipedia.org/wiki/Yoda_conditions} 42 | } 43 | \section{Tags}{ 44 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat}, \link[=readability_linters]{readability} 45 | } 46 | -------------------------------------------------------------------------------- /man/robustness_linters.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/linter_tag_docs.R 3 | \name{robustness_linters} 4 | \alias{robustness_linters} 5 | \title{Robustness linters} 6 | \description{ 7 | Linters highlighting code robustness issues, such as possibly wrong edge case behavior. 8 | } 9 | \seealso{ 10 | \link{linters} for a complete list of linters available in lintr. 11 | } 12 | \section{Linters}{ 13 | The following linters are tagged with 'robustness': 14 | \itemize{ 15 | \item{\code{\link{absolute_path_linter}}} 16 | \item{\code{\link{all_equal_linter}}} 17 | \item{\code{\link{backport_linter}}} 18 | \item{\code{\link{class_equals_linter}}} 19 | \item{\code{\link{download_file_linter}}} 20 | \item{\code{\link{equals_na_linter}}} 21 | \item{\code{\link{for_loop_index_linter}}} 22 | \item{\code{\link{missing_package_linter}}} 23 | \item{\code{\link{namespace_linter}}} 24 | \item{\code{\link{nonportable_path_linter}}} 25 | \item{\code{\link{object_overwrite_linter}}} 26 | \item{\code{\link{routine_registration_linter}}} 27 | \item{\code{\link{sample_int_linter}}} 28 | \item{\code{\link{seq_linter}}} 29 | \item{\code{\link{strings_as_factors_linter}}} 30 | \item{\code{\link{T_and_F_symbol_linter}}} 31 | \item{\code{\link{terminal_close_linter}}} 32 | \item{\code{\link{undesirable_function_linter}}} 33 | \item{\code{\link{undesirable_operator_linter}}} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /man/unnecessary_placeholder_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/unnecessary_placeholder_linter.R 3 | \name{unnecessary_placeholder_linter} 4 | \alias{unnecessary_placeholder_linter} 5 | \title{Block usage of pipeline placeholders if unnecessary} 6 | \usage{ 7 | unnecessary_placeholder_linter() 8 | } 9 | \description{ 10 | The argument placeholder \code{.} in magrittr pipelines is unnecessary if 11 | passed as the first positional argument; using it can cause confusion 12 | and impacts readability. 13 | } 14 | \details{ 15 | This is true for forward (\verb{\%>\%}), assignment (\verb{\%<>\%}), and tee (\verb{\%T>\%}) operators. 16 | } 17 | \examples{ 18 | # will produce lints 19 | lint( 20 | text = "x \%>\% sum(., na.rm = TRUE)", 21 | linters = unnecessary_placeholder_linter() 22 | ) 23 | 24 | # okay 25 | lint( 26 | text = "x \%>\% sum(na.rm = TRUE)", 27 | linters = unnecessary_placeholder_linter() 28 | ) 29 | 30 | lint( 31 | text = "x \%>\% lm(data = ., y ~ z)", 32 | linters = unnecessary_placeholder_linter() 33 | ) 34 | 35 | lint( 36 | text = "x \%>\% outer(., .)", 37 | linters = unnecessary_placeholder_linter() 38 | ) 39 | 40 | } 41 | \seealso{ 42 | \link{linters} for a complete list of linters available in lintr. 43 | } 44 | \section{Tags}{ 45 | \link[=best_practices_linters]{best_practices}, \link[=readability_linters]{readability} 46 | } 47 | -------------------------------------------------------------------------------- /man/pipe_return_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipe_return_linter.R 3 | \name{pipe_return_linter} 4 | \alias{pipe_return_linter} 5 | \title{Block usage of return() in magrittr pipelines} 6 | \usage{ 7 | pipe_return_linter() 8 | } 9 | \description{ 10 | \code{\link[=return]{return()}} inside a magrittr pipeline does not actually execute \code{return()} 11 | like you'd expect: 12 | } 13 | \details{ 14 | \if{html}{\out{
}}\preformatted{bad_usage <- function(x) \{ 15 | x \%>\% 16 | return() 17 | FALSE 18 | \} 19 | }\if{html}{\out{
}} 20 | 21 | \code{bad_usage(TRUE)} will return \code{FALSE}! It will technically work "as expected" 22 | if this is the final statement in the function body, but such usage is misleading. 23 | Instead, assign the pipe outcome to a variable and return that. 24 | } 25 | \examples{ 26 | # will produce lints 27 | lint( 28 | text = "function(x) x \%>\% return()", 29 | linters = pipe_return_linter() 30 | ) 31 | 32 | # okay 33 | code <- "function(x) {\n y <- sum(x)\n return(y)\n}" 34 | writeLines(code) 35 | lint( 36 | text = code, 37 | linters = pipe_return_linter() 38 | ) 39 | 40 | } 41 | \seealso{ 42 | \link{linters} for a complete list of linters available in lintr. 43 | } 44 | \section{Tags}{ 45 | \link[=best_practices_linters]{best_practices}, \link[=common_mistakes_linters]{common_mistakes} 46 | } 47 | -------------------------------------------------------------------------------- /man/quotes_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/quotes_linter.R 3 | \name{quotes_linter} 4 | \alias{quotes_linter} 5 | \title{Character string quote linter} 6 | \usage{ 7 | quotes_linter(delimiter = c("\\"", "'")) 8 | } 9 | \arguments{ 10 | \item{delimiter}{Which quote delimiter to accept. Defaults to the tidyverse 11 | default of \verb{"} (double-quoted strings).} 12 | } 13 | \description{ 14 | Check that the desired quote delimiter is used for string constants. 15 | } 16 | \examples{ 17 | # will produce lints 18 | lint( 19 | text = "c('a', 'b')", 20 | linters = quotes_linter() 21 | ) 22 | 23 | # okay 24 | lint( 25 | text = 'c("a", "b")', 26 | linters = quotes_linter() 27 | ) 28 | 29 | code_lines <- "paste0(x, '\"this is fine\"')" 30 | writeLines(code_lines) 31 | lint( 32 | text = code_lines, 33 | linters = quotes_linter() 34 | ) 35 | 36 | # okay 37 | lint( 38 | text = "c('a', 'b')", 39 | linters = quotes_linter(delimiter = "'") 40 | ) 41 | 42 | } 43 | \seealso{ 44 | \itemize{ 45 | \item \link{linters} for a complete list of linters available in lintr. 46 | \item \url{https://style.tidyverse.org/syntax.html#character-vectors} 47 | } 48 | } 49 | \section{Tags}{ 50 | \link[=configurable_linters]{configurable}, \link[=consistency_linters]{consistency}, \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 51 | } 52 | -------------------------------------------------------------------------------- /man/nonportable_path_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nonportable_path_linter.R 3 | \name{nonportable_path_linter} 4 | \alias{nonportable_path_linter} 5 | \title{Non-portable path linter} 6 | \usage{ 7 | nonportable_path_linter(lax = TRUE) 8 | } 9 | \arguments{ 10 | \item{lax}{Less stringent linting, leading to fewer false positives. 11 | If \code{TRUE}, only lint path strings, which 12 | \itemize{ 13 | \item contain at least two path elements, with one having at least two characters and 14 | \item contain only alphanumeric chars (including UTF-8), spaces, and win32-allowed punctuation 15 | }} 16 | } 17 | \description{ 18 | Check that \code{\link[=file.path]{file.path()}} is used to construct safe and portable paths. 19 | } 20 | \examples{ 21 | # will produce lints 22 | lint( 23 | text = "'abcdefg/hijklmnop/qrst/uv/wxyz'", 24 | linters = nonportable_path_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = "file.path('abcdefg', 'hijklmnop', 'qrst', 'uv', 'wxyz')", 30 | linters = nonportable_path_linter() 31 | ) 32 | 33 | } 34 | \seealso{ 35 | \itemize{ 36 | \item \link{linters} for a complete list of linters available in lintr. 37 | \item \code{\link[=absolute_path_linter]{absolute_path_linter()}} 38 | } 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=configurable_linters]{configurable}, \link[=robustness_linters]{robustness} 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/check-all-examples.yaml: -------------------------------------------------------------------------------- 1 | # Make sure all examples run successfully, even the ones that are not supposed 2 | # to be run or tested on CRAN machines by default. 3 | # 4 | # The examples that fail should use 5 | # - `if (FALSE) { ... }` (if example is included only for illustrative purposes) 6 | # - `try({ ... })` (if the intent is to show the error) 7 | # 8 | # This workflow helps find such failing examples that need to be modified. 9 | 10 | on: 11 | push: 12 | branches: main 13 | pull_request: 14 | branches: main 15 | 16 | name: check-all-examples 17 | 18 | jobs: 19 | check-all-examples: 20 | runs-on: ubuntu-latest 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | steps: 25 | - uses: actions/checkout@v6 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | install-quarto: false 34 | pak-version: devel 35 | extra-packages: | 36 | any::pkgload 37 | local::. 38 | 39 | - name: Run examples 40 | run: | 41 | options(crayon.enabled = TRUE) 42 | pkgload::load_all() 43 | setwd("man") 44 | for (rd in list.files(pattern = "\\.Rd")) pkgload::run_example(rd, run_dontrun = TRUE, run_donttest = TRUE, quiet = TRUE) 45 | shell: Rscript {0} 46 | -------------------------------------------------------------------------------- /man/T_and_F_symbol_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/T_and_F_symbol_linter.R 3 | \name{T_and_F_symbol_linter} 4 | \alias{T_and_F_symbol_linter} 5 | \title{\code{T} and \code{F} symbol linter} 6 | \usage{ 7 | T_and_F_symbol_linter() 8 | } 9 | \description{ 10 | Although they can be synonyms, avoid the symbols \code{T} and \code{F}, and use \code{TRUE} and \code{FALSE}, respectively, instead. 11 | \code{T} and \code{F} are not reserved keywords and can be assigned to any other values. 12 | } 13 | \examples{ 14 | # will produce lints 15 | lint( 16 | text = "x <- T; y <- F", 17 | linters = T_and_F_symbol_linter() 18 | ) 19 | 20 | lint( 21 | text = "T = 1.2; F = 2.4", 22 | linters = T_and_F_symbol_linter() 23 | ) 24 | 25 | # okay 26 | lint( 27 | text = "x <- c(TRUE, FALSE)", 28 | linters = T_and_F_symbol_linter() 29 | ) 30 | 31 | lint( 32 | text = "t = 1.2; f = 2.4", 33 | linters = T_and_F_symbol_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \itemize{ 39 | \item \link{linters} for a complete list of linters available in lintr. 40 | \item \url{https://style.tidyverse.org/syntax.html#logical-vectors} 41 | } 42 | } 43 | \section{Tags}{ 44 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=robustness_linters]{robustness}, \link[=style_linters]{style} 45 | } 46 | -------------------------------------------------------------------------------- /man/coalesce_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/coalesce_linter.R 3 | \name{coalesce_linter} 4 | \alias{coalesce_linter} 5 | \title{Encourage usage of the null coalescing operator \verb{\%||\%}} 6 | \usage{ 7 | coalesce_linter() 8 | } 9 | \description{ 10 | The \code{x \%||\% y} is equivalent to 11 | \code{if (is.null(x)) y else x}, but more expressive. 12 | It is exported by R since 4.4.0, and equivalents 13 | have been available in other tidyverse packages 14 | for much longer, e.g. 2008 for ggplot2. 15 | } 16 | \examples{ 17 | # will produce lints 18 | lint( 19 | text = "if (is.null(x)) y else x", 20 | linters = coalesce_linter() 21 | ) 22 | 23 | lint( 24 | text = "if (!is.null(x)) x else y", 25 | linters = coalesce_linter() 26 | ) 27 | 28 | lint( 29 | text = "if (is.null(x[1])) x[2] else x[1]", 30 | linters = coalesce_linter() 31 | ) 32 | 33 | # okay 34 | lint( 35 | text = "x \%||\% y", 36 | linters = coalesce_linter() 37 | ) 38 | 39 | lint( 40 | text = "x \%||\% y", 41 | linters = coalesce_linter() 42 | ) 43 | 44 | lint( 45 | text = "x[1] \%||\% x[2]", 46 | linters = coalesce_linter() 47 | ) 48 | 49 | 50 | } 51 | \seealso{ 52 | \link{linters} for a complete list of linters available in lintr. 53 | } 54 | \section{Tags}{ 55 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 56 | } 57 | -------------------------------------------------------------------------------- /man/expect_named_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_named_linter.R 3 | \name{expect_named_linter} 4 | \alias{expect_named_linter} 5 | \title{Require usage of \code{expect_named(x, n)} over \code{expect_equal(names(x), n)}} 6 | \usage{ 7 | expect_named_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:expect_named]{testthat::expect_named()}} exists specifically for testing the \code{\link[=names]{names()}} of 11 | an object. \code{\link[testthat:equality-expectations]{testthat::expect_equal()}} can also be used for such tests, 12 | but it is better to use the tailored function instead. 13 | } 14 | \examples{ 15 | # will produce lints 16 | lint( 17 | text = 'expect_equal(names(x), "a")', 18 | linters = expect_named_linter() 19 | ) 20 | 21 | # okay 22 | lint( 23 | text = 'expect_named(x, "a")', 24 | linters = expect_named_linter() 25 | ) 26 | 27 | lint( 28 | text = 'expect_equal(colnames(x), "a")', 29 | linters = expect_named_linter() 30 | ) 31 | 32 | lint( 33 | text = 'expect_equal(dimnames(x), "a")', 34 | linters = expect_named_linter() 35 | ) 36 | 37 | } 38 | \seealso{ 39 | \link{linters} for a complete list of linters available in lintr. 40 | } 41 | \section{Tags}{ 42 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat}, \link[=readability_linters]{readability} 43 | } 44 | -------------------------------------------------------------------------------- /R/empty_assignment_linter.R: -------------------------------------------------------------------------------- 1 | #' Block assignment of `{}` 2 | #' 3 | #' Assignment of `{}` is the same as assignment of `NULL`; use the latter 4 | #' for clarity. Closely related: [unnecessary_concatenation_linter()]. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' lint( 9 | #' text = "x <- {}", 10 | #' linters = empty_assignment_linter() 11 | #' ) 12 | #' 13 | #' writeLines("x = {\n}") 14 | #' lint( 15 | #' text = "x = {\n}", 16 | #' linters = empty_assignment_linter() 17 | #' ) 18 | #' 19 | #' # okay 20 | #' lint( 21 | #' text = "x <- { 3 + 4 }", 22 | #' linters = empty_assignment_linter() 23 | #' ) 24 | #' 25 | #' lint( 26 | #' text = "x <- NULL", 27 | #' linters = empty_assignment_linter() 28 | #' ) 29 | #' 30 | #' @evalRd rd_tags("empty_assignment_linter") 31 | #' @seealso [linters] for a complete list of linters available in lintr. 32 | #' @export 33 | empty_assignment_linter <- make_linter_from_xpath( 34 | # for some reason, the parent in the `=` case is , not , hence parent::expr 35 | xpath = " 36 | //OP-LEFT-BRACE[following-sibling::*[not(self::COMMENT)][1][self::OP-RIGHT-BRACE]] 37 | /parent::expr[ 38 | preceding-sibling::LEFT_ASSIGN 39 | or preceding-sibling::EQ_ASSIGN 40 | or following-sibling::RIGHT_ASSIGN 41 | ] 42 | /parent::* 43 | ", 44 | lint_message = "Assign NULL explicitly or, whenever possible, allocate the empty object with the right type and size." 45 | ) 46 | -------------------------------------------------------------------------------- /man/expect_type_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_type_linter.R 3 | \name{expect_type_linter} 4 | \alias{expect_type_linter} 5 | \title{Require usage of \code{expect_type(x, type)} over \code{expect_equal(typeof(x), type)}} 6 | \usage{ 7 | expect_type_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:inheritance-expectations]{testthat::expect_type()}} exists specifically for testing the storage type 11 | of objects. \code{\link[testthat:equality-expectations]{testthat::expect_equal()}}, \code{\link[testthat:equality-expectations]{testthat::expect_identical()}}, and 12 | \code{\link[testthat:logical-expectations]{testthat::expect_true()}} can also be used for such tests, 13 | but it is better to use the tailored function instead. 14 | } 15 | \examples{ 16 | # will produce lints 17 | lint( 18 | text = 'expect_equal(typeof(x), "double")', 19 | linters = expect_type_linter() 20 | ) 21 | 22 | lint( 23 | text = 'expect_identical(typeof(x), "double")', 24 | linters = expect_type_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = 'expect_type(x, "double")', 30 | linters = expect_type_linter() 31 | ) 32 | 33 | } 34 | \seealso{ 35 | \link{linters} for a complete list of linters available in lintr. 36 | } 37 | \section{Tags}{ 38 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat} 39 | } 40 | -------------------------------------------------------------------------------- /tests/testthat/test-sarif_output.R: -------------------------------------------------------------------------------- 1 | test_that("`sarif_output` produces expected error", { 2 | skip_if_not_installed("jsonlite") 3 | 4 | l <- lint(text = "x = 1", linters = assignment_linter()) 5 | expect_error(sarif_output(l), "Package path needs to be a relative path", fixed = TRUE) 6 | }) 7 | 8 | test_that("`sarif_output` writes expected files", { 9 | skip_if_not_installed("jsonlite") 10 | 11 | l <- lint_package( 12 | test_path("dummy_packages", "missing_dep"), 13 | linters = object_length_linter(), 14 | parse_settings = FALSE 15 | ) 16 | 17 | withr::with_tempdir({ 18 | sarif_output(l) 19 | expect_true(file.exists("lintr_results.sarif")) 20 | }) 21 | 22 | withr::with_tempdir({ 23 | sarif_output(l, filename = "myfile.sarif") 24 | expect_true(file.exists("myfile.sarif")) 25 | }) 26 | }) 27 | 28 | test_that("`sarif_output` produces valid files", { 29 | skip_if_not_installed("jsonlite") 30 | 31 | l <- lint_package( 32 | test_path("dummy_packages", "clean"), 33 | linters = default_linters, 34 | parse_settings = FALSE 35 | ) 36 | 37 | withr::with_tempdir({ 38 | sarif <- sarif_output(l) 39 | sarif <- jsonlite::fromJSON( 40 | "lintr_results.sarif", 41 | simplifyVector = TRUE, 42 | simplifyDataFrame = FALSE, 43 | simplifyMatrix = FALSE 44 | ) 45 | 46 | expect_false(is.null(sarif$runs)) 47 | expect_false(is.null(sarif$runs[[1L]]$results)) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /man/nrow_subset_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nrow_subset_linter.R 3 | \name{nrow_subset_linter} 4 | \alias{nrow_subset_linter} 5 | \title{Block usage of \code{nrow(subset(x, .))}} 6 | \usage{ 7 | nrow_subset_linter() 8 | } 9 | \description{ 10 | Using \code{nrow(subset(x, condition))} to count the instances where \code{condition} 11 | applies inefficiently requires doing a full subset of \code{x} just to 12 | count the number of rows in the resulting subset. 13 | There are a number of equivalent expressions that don't require the full 14 | subset, e.g. \code{with(x, sum(condition))} (or, more generically, 15 | \code{with(x, sum(condition, na.rm = TRUE))}). 16 | } 17 | \examples{ 18 | # will produce lints 19 | lint( 20 | text = "nrow(subset(x, is_treatment))", 21 | linters = nrow_subset_linter() 22 | ) 23 | 24 | lint( 25 | text = "nrow(filter(x, is_treatment))", 26 | linters = nrow_subset_linter() 27 | ) 28 | 29 | lint( 30 | text = "x \%>\% filter(x, is_treatment) \%>\% nrow()", 31 | linters = nrow_subset_linter() 32 | ) 33 | 34 | # okay 35 | lint( 36 | text = "with(x, sum(is_treatment, na.rm = TRUE))", 37 | linters = nrow_subset_linter() 38 | ) 39 | 40 | } 41 | \seealso{ 42 | \link{linters} for a complete list of linters available in lintr. 43 | } 44 | \section{Tags}{ 45 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=efficiency_linters]{efficiency} 46 | } 47 | -------------------------------------------------------------------------------- /R/ids_with_token.R: -------------------------------------------------------------------------------- 1 | #' Get parsed IDs by token 2 | #' 3 | #' Gets the source IDs (row indices) corresponding to given token. 4 | #' 5 | #' @param source_expression A list of source expressions, the result of a call to [get_source_expressions()], 6 | #' for the desired filename. 7 | #' @param value Character. String corresponding to the token to search for. 8 | #' For example: 9 | #' 10 | #' * "SYMBOL" 11 | #' * "FUNCTION" 12 | #' * "EQ_FORMALS" 13 | #' * "$" 14 | #' * "(" 15 | #' 16 | #' @param fun For additional flexibility, a function to search for in 17 | #' the `token` column of `parsed_content`. Typically `==` or `%in%`. 18 | #' 19 | #' @examples 20 | #' tmp <- tempfile() 21 | #' writeLines(c("x <- 1", "y <- x + 1"), tmp) 22 | #' source_exprs <- get_source_expressions(tmp) 23 | #' ids_with_token(source_exprs$expressions[[1L]], value = "SYMBOL") 24 | #' with_id(source_exprs$expressions[[1L]], 2L) 25 | #' unlink(tmp) 26 | #' 27 | #' @return `ids_with_token`: The indices of the `parsed_content` data frame 28 | #' entry of the list of source expressions. Indices correspond to the 29 | #' *rows* where `fun` evaluates to `TRUE` for the `value` in the *token* column. 30 | #' @export 31 | ids_with_token <- function(source_expression, value, fun = `==`) { 32 | if (!is_lint_level(source_expression, "expression")) { 33 | return(integer()) 34 | } 35 | loc <- which(fun(source_expression$parsed_content$token, value)) 36 | if (loc %==% integer()) { 37 | return(integer()) 38 | } 39 | loc 40 | } 41 | -------------------------------------------------------------------------------- /tests/testthat/test-expect_not_linter.R: -------------------------------------------------------------------------------- 1 | test_that("expect_not_linter skips allowed usages", { 2 | expect_lint("expect_true(x)", NULL, expect_not_linter()) 3 | # NB: also applies to tinytest, but it's sufficient to test testthat 4 | expect_lint("testthat::expect_true(x)", NULL, expect_not_linter()) 5 | expect_lint("expect_false(x)", NULL, expect_not_linter()) 6 | expect_lint("testthat::expect_false(x)", NULL, expect_not_linter()) 7 | 8 | # not a strict ban on ! 9 | ## (expect_false(x && y) is the same, but it's not clear which to prefer) 10 | expect_lint("expect_true(!x || !y)", NULL, expect_not_linter()) 11 | }) 12 | 13 | test_that("expect_not_linter blocks simple disallowed usages", { 14 | linter <- expect_not_linter() 15 | lint_msg <- rex::rex("expect_false(x) is better than expect_true(!x), and vice versa.") 16 | 17 | expect_lint("expect_true(!x)", lint_msg, linter) 18 | expect_lint("testthat::expect_true(!x)", lint_msg, linter) 19 | expect_lint("expect_false(!foo(x))", lint_msg, linter) 20 | expect_lint("testthat::expect_true(!(x && y))", lint_msg, linter) 21 | }) 22 | 23 | test_that("lints vectorize", { 24 | lint_msg <- rex::rex("expect_false(x) is better than expect_true(!x), and vice versa.") 25 | 26 | expect_lint( 27 | trim_some("{ 28 | expect_true(!x) 29 | expect_false(!y) 30 | }"), 31 | list( 32 | list(lint_msg, line_number = 2L), 33 | list(lint_msg, line_number = 3L) 34 | ), 35 | expect_not_linter() 36 | ) 37 | }) 38 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v6 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | install-quarto: false 34 | extra-packages: any::pkgdown, local::. 35 | needs: website 36 | 37 | - name: Build site 38 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 39 | shell: Rscript {0} 40 | 41 | - name: Deploy to GitHub pages 🚀 42 | if: github.event_name != 'pull_request' 43 | uses: JamesIves/github-pages-deploy-action@v4.7.6 44 | with: 45 | clean: false 46 | branch: gh-pages 47 | folder: docs 48 | -------------------------------------------------------------------------------- /man/is_numeric_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/is_numeric_linter.R 3 | \name{is_numeric_linter} 4 | \alias{is_numeric_linter} 5 | \title{Redirect \code{is.numeric(x) || is.integer(x)} to just use \code{is.numeric(x)}} 6 | \usage{ 7 | is_numeric_linter() 8 | } 9 | \description{ 10 | \code{\link[=is.numeric]{is.numeric()}} returns \code{TRUE} when \code{typeof(x)} is \code{double} or \code{integer} -- 11 | testing \code{is.numeric(x) || is.integer(x)} is thus redundant. 12 | } 13 | \details{ 14 | NB: This linter plays well with \code{\link[=class_equals_linter]{class_equals_linter()}}, which can help 15 | avoid further \code{is.numeric()} equivalents like 16 | \code{any(class(x) == c("numeric", "integer"))}. 17 | } 18 | \examples{ 19 | # will produce lints 20 | lint( 21 | text = "is.numeric(y) || is.integer(y)", 22 | linters = is_numeric_linter() 23 | ) 24 | 25 | lint( 26 | text = 'class(z) \%in\% c("numeric", "integer")', 27 | linters = is_numeric_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "is.numeric(y) || is.factor(y)", 33 | linters = is_numeric_linter() 34 | ) 35 | 36 | lint( 37 | text = 'class(z) \%in\% c("numeric", "integer", "factor")', 38 | linters = is_numeric_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=readability_linters]{readability} 47 | } 48 | -------------------------------------------------------------------------------- /man/class_equals_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/class_equals_linter.R 3 | \name{class_equals_linter} 4 | \alias{class_equals_linter} 5 | \title{Block comparison of class with \code{==}} 6 | \usage{ 7 | class_equals_linter() 8 | } 9 | \description{ 10 | Usage like \code{class(x) == "character"} is prone to error since class in R 11 | is in general a vector. The correct version for S3 classes is \code{\link[=inherits]{inherits()}}: 12 | \code{inherits(x, "character")}. Often, class \code{k} will have an \code{is.} equivalent, 13 | for example \code{\link[=is.character]{is.character()}} or \code{\link[=is.data.frame]{is.data.frame()}}. 14 | } 15 | \details{ 16 | Similar reasoning applies for \code{class(x) \%in\% "character"}. 17 | } 18 | \examples{ 19 | # will produce lints 20 | lint( 21 | text = 'is_lm <- class(x) == "lm"', 22 | linters = class_equals_linter() 23 | ) 24 | 25 | lint( 26 | text = 'if ("lm" \%in\% class(x)) is_lm <- TRUE', 27 | linters = class_equals_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = 'is_lm <- inherits(x, "lm")', 33 | linters = class_equals_linter() 34 | ) 35 | 36 | lint( 37 | text = 'if (inherits(x, "lm")) is_lm <- TRUE', 38 | linters = class_equals_linter() 39 | ) 40 | 41 | } 42 | \seealso{ 43 | \link{linters} for a complete list of linters available in lintr. 44 | } 45 | \section{Tags}{ 46 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency}, \link[=robustness_linters]{robustness} 47 | } 48 | -------------------------------------------------------------------------------- /tests/testthat/knitr_formats/test.Rmd: -------------------------------------------------------------------------------- 1 | # Test # 2 | 3 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 4 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 5 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 6 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 7 | 8 | ```{r} 9 | a = 1 10 | ``` 11 | 12 | Test 13 | ==== 14 | 15 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod 16 | tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At 17 | vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, 18 | no sea takimata sanctus est Lorem ipsum dolor sit amet. 19 | 20 | ```{r} 21 | b <- function(x) { 22 | d = 1 23 | } 24 | 25 | ``` 26 | 27 | ```{r engine="python"} 28 | a=[] 29 | 30 | a[0]=1 31 | ``` 32 | 33 | ``` 34 | Plain code blocks can be written after three or more backticks 35 | - R Markdown: The Definitive Guide. Xie, Allaire and Grolemund (2.5.2) 36 | ``` 37 | 38 | ```r 39 | # This is a non-evaluated block of R code for formatting in markdown. 40 | # It should not be linted 41 | abc = 123 42 | ``` 43 | 44 | ```cpp 45 | // Some C++ code for formatting by markdown 46 | 47 | ``` 48 | 49 | Calls to a non-R knitr-engine using {engine_name} syntax. 50 | 51 | ```{python} 52 | # Python that looks like R 53 | a = list() 54 | b = {2} 55 | print(a) 56 | ``` 57 | 58 | ```{python} 59 | # Python that's definitely not R 60 | a = [] 61 | a.append(2) 62 | print(a) 63 | ``` 64 | -------------------------------------------------------------------------------- /man/print_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print_linter.R 3 | \name{print_linter} 4 | \alias{print_linter} 5 | \title{Block usage of print() for logging} 6 | \usage{ 7 | print_linter() 8 | } 9 | \description{ 10 | The default print method for character vectors is appropriate for interactively inspecting objects, 11 | not for logging messages. Thus checked-in usage like \code{print(paste('Data has', nrow(DF), 'rows.'))} 12 | is better served by using \code{\link[=cat]{cat()}}, e.g. \code{cat(sprintf('Data has \%d rows.\\n', nrow(DF)))} (noting that 13 | using \code{cat()} entails supplying your own line returns, and that \code{\link[glue:glue]{glue::glue()}} might be preferable 14 | to \code{\link[=sprintf]{sprintf()}} for constructing templated strings). Lastly, note that \code{\link[=message]{message()}} differs slightly 15 | from \code{cat()} in that it prints to \code{stderr} by default, not \code{stdout}, but is still a good option 16 | to consider for logging purposes. 17 | } 18 | \examples{ 19 | # will produce lints 20 | lint( 21 | text = "print('a')", 22 | linters = print_linter() 23 | ) 24 | 25 | lint( 26 | text = "print(paste(x, 'y'))", 27 | linters = print_linter() 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "print(x)", 33 | linters = print_linter() 34 | ) 35 | 36 | } 37 | \seealso{ 38 | \link{linters} for a complete list of linters available in lintr. 39 | } 40 | \section{Tags}{ 41 | \link[=best_practices_linters]{best_practices}, \link[=consistency_linters]{consistency} 42 | } 43 | -------------------------------------------------------------------------------- /tests/testthat/test-expect_s4_class_linter.R: -------------------------------------------------------------------------------- 1 | test_that("expect_s4_class_linter skips allowed usages", { 2 | linter <- expect_s4_class_linter() 3 | 4 | # expect_s4_class doesn't have an inverted version 5 | expect_lint("expect_true(!is(x, 'class'))", NULL, linter) 6 | # NB: also applies to tinytest, but it's sufficient to test testthat 7 | expect_lint("testthat::expect_s3_class(!is(x, 'class'))", NULL, linter) 8 | 9 | # expect_s4_class() doesn't have info= or label= arguments 10 | expect_lint("expect_true(is(x, 'SpatialPoly'), info = 'x should be SpatialPoly')", NULL, linter) 11 | expect_lint("expect_true(is(x, 'SpatialPoly'), label = 'x inheritance')", NULL, linter) 12 | }) 13 | 14 | test_that("expect_s4_class blocks simple disallowed usages", { 15 | linter <- expect_s4_class_linter() 16 | lint_msg <- rex::rex("expect_s4_class(x, k) is better than expect_true(is(x, k))") 17 | 18 | expect_lint("expect_true(is(x, 'data.frame'))", lint_msg, linter) 19 | # namespace qualification is irrelevant 20 | expect_lint("testthat::expect_true(methods::is(x, 'SpatialPolygonsDataFrame'))", lint_msg, linter) 21 | }) 22 | 23 | test_that("lints vectorize", { 24 | lint_msg <- rex::rex("expect_s4_class(x, k) is better than expect_true(is(x, k))") 25 | 26 | expect_lint( 27 | trim_some("{ 28 | expect_true(is(x, 'data.frame')) 29 | expect_true(is(x, 'SpatialPolygonsDataFrame')) 30 | }"), 31 | list( 32 | list(lint_msg, line_number = 2L), 33 | list(lint_msg, line_number = 3L) 34 | ), 35 | expect_s4_class_linter() 36 | ) 37 | }) 38 | -------------------------------------------------------------------------------- /.github/workflows/repo-meta-tests.yaml: -------------------------------------------------------------------------------- 1 | # Various repo-level tests for code quality 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | name: repo-meta-tests 9 | 10 | jobs: 11 | repo-meta-tests: 12 | runs-on: ubuntu-latest 13 | env: 14 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | steps: 17 | - uses: actions/checkout@v6 18 | 19 | - uses: r-lib/actions/setup-r@v2 20 | with: 21 | r-version: "release" 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | install-quarto: false 27 | extra-packages: | 28 | any::roxygen2 29 | 30 | - name: Ensure lint metadata is tested 31 | run: | 32 | options(crayon.enabled = TRUE) 33 | callr::rscript(".dev/lint_metadata_test.R") 34 | shell: Rscript {0} 35 | 36 | - name: Generate some foreign locales 37 | run: | 38 | sudo locale-gen en_US.utf8 hu_HU.utf8 ja_JP.utf8 39 | 40 | - name: Ensure roxygen content matches man directory 41 | run: | 42 | callr::rscript(".dev/roxygen_test.R") 43 | shell: Rscript {0} 44 | 45 | - name: Ensure defunct linters exist 46 | run: | 47 | callr::rscript(".dev/defunct_linters_test.R") 48 | shell: Rscript {0} 49 | 50 | - name: Ensure any objects in the namespace are actually needed 51 | run: | 52 | callr::rscript(".dev/unused_helpers_test.R") 53 | shell: Rscript {0} 54 | -------------------------------------------------------------------------------- /tests/testthat/test-for_loop_index_linter.R: -------------------------------------------------------------------------------- 1 | test_that("for_loop_index_linter skips allowed usages", { 2 | linter <- for_loop_index_linter() 3 | 4 | expect_lint("for (xi in x) {}", NULL, linter) 5 | 6 | # this is OK, so not every symbol is problematic 7 | expect_lint("for (col in DF$col) {}", NULL, linter) 8 | expect_lint("for (col in S4@col) {}", NULL, linter) 9 | expect_lint("for (col in DT[, col]) {}", NULL, linter) 10 | 11 | # make sure symbol check is scoped 12 | expect_lint( 13 | trim_some(" 14 | { 15 | for (i in 1:10) { 16 | 42L 17 | } 18 | i <- 7L 19 | } 20 | "), 21 | NULL, 22 | linter 23 | ) 24 | }) 25 | 26 | test_that("for_loop_index_linter blocks simple disallowed usages", { 27 | linter <- for_loop_index_linter() 28 | lint_msg <- "Don't re-use any sequence symbols as the index symbol in a for loop" 29 | 30 | expect_lint("for (x in x) {}", lint_msg, linter) 31 | # these also overwrite a variable in calling scope 32 | expect_lint("for (x in foo(x)) {}", lint_msg, linter) 33 | # arbitrary nesting 34 | expect_lint("for (x in foo(bar(y, baz(2, x)))) {}", lint_msg, linter) 35 | }) 36 | 37 | test_that("lints vectorize", { 38 | lint_msg <- "Don't re-use any sequence symbols as the index symbol in a for loop" 39 | 40 | expect_lint( 41 | trim_some("{ 42 | for (x in x) {} 43 | for (y in y) {} 44 | }"), 45 | list( 46 | list(lint_msg, line_number = 2L), 47 | list(lint_msg, line_number = 3L) 48 | ), 49 | for_loop_index_linter() 50 | ) 51 | }) 52 | -------------------------------------------------------------------------------- /R/pipe_return_linter.R: -------------------------------------------------------------------------------- 1 | #' Block usage of return() in magrittr pipelines 2 | #' 3 | #' [return()] inside a magrittr pipeline does not actually execute `return()` 4 | #' like you'd expect: 5 | #' 6 | #' ```r 7 | #' bad_usage <- function(x) { 8 | #' x %>% 9 | #' return() 10 | #' FALSE 11 | #' } 12 | #' ``` 13 | #' 14 | #' `bad_usage(TRUE)` will return `FALSE`! It will technically work "as expected" 15 | #' if this is the final statement in the function body, but such usage is misleading. 16 | #' Instead, assign the pipe outcome to a variable and return that. 17 | #' 18 | #' @examples 19 | #' # will produce lints 20 | #' lint( 21 | #' text = "function(x) x %>% return()", 22 | #' linters = pipe_return_linter() 23 | #' ) 24 | #' 25 | #' # okay 26 | #' code <- "function(x) {\n y <- sum(x)\n return(y)\n}" 27 | #' writeLines(code) 28 | #' lint( 29 | #' text = code, 30 | #' linters = pipe_return_linter() 31 | #' ) 32 | #' 33 | #' @evalRd rd_tags("pipe_return_linter") 34 | #' @seealso [linters] for a complete list of linters available in lintr. 35 | #' @export 36 | pipe_return_linter <- make_linter_from_xpath( 37 | # NB: Native pipe disallows this at the parser level, so there's no need 38 | # to lint in valid R code. 39 | xpath = " 40 | //SPECIAL[text() = '%>%'] 41 | /following-sibling::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'return']] 42 | ", 43 | lint_message = paste( 44 | "Avoid return() as the final step of a magrittr pipeline. ", 45 | "Instead, assign the output of the pipeline to a well-named object and return that." 46 | ) 47 | ) 48 | -------------------------------------------------------------------------------- /tests/testthat/test-numeric_leading_zero_linter.R: -------------------------------------------------------------------------------- 1 | test_that("numeric_leading_zero_linter skips allowed usages", { 2 | linter <- numeric_leading_zero_linter() 3 | 4 | expect_lint("a <- 0.1", NULL, linter) 5 | expect_lint("b <- -0.2", NULL, linter) 6 | expect_lint("c <- 3.0", NULL, linter) 7 | expect_lint("d <- 4L", NULL, linter) 8 | expect_lint("e <- TRUE", NULL, linter) 9 | expect_lint("f <- 0.5e6", NULL, linter) 10 | expect_lint("g <- 0x78", NULL, linter) 11 | expect_lint("h <- 0.9 + 0.1i", NULL, linter) 12 | expect_lint("h <- 0.9+0.1i", NULL, linter) 13 | expect_lint("h <- 0.9 - 0.1i", NULL, linter) 14 | expect_lint("i <- 2L + 3.4i", NULL, linter) 15 | }) 16 | 17 | test_that("numeric_leading_zero_linter blocks simple disallowed usages", { 18 | linter <- numeric_leading_zero_linter() 19 | lint_msg <- rex::rex("Include the leading zero for fractional numeric constants.") 20 | 21 | expect_lint("a <- .1", lint_msg, linter) 22 | expect_lint("b <- -.2", lint_msg, linter) 23 | expect_lint("c <- .3 + 4.5i", lint_msg, linter) 24 | expect_lint("d <- 6.7 + .8i", lint_msg, linter) 25 | expect_lint("d <- 6.7+.8i", lint_msg, linter) 26 | expect_lint("e <- .9e10", lint_msg, linter) 27 | }) 28 | 29 | test_that("lints vectorize", { 30 | lint_msg <- rex::rex("Include the leading zero for fractional numeric constants.") 31 | 32 | expect_lint( 33 | trim_some("{ 34 | .1 35 | -.2 36 | }"), 37 | list( 38 | list(lint_msg, line_number = 2L), 39 | list(lint_msg, line_number = 3L) 40 | ), 41 | numeric_leading_zero_linter() 42 | ) 43 | }) 44 | -------------------------------------------------------------------------------- /man/implicit_integer_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/implicit_integer_linter.R 3 | \name{implicit_integer_linter} 4 | \alias{implicit_integer_linter} 5 | \title{Implicit integer linter} 6 | \usage{ 7 | implicit_integer_linter(allow_colon = FALSE) 8 | } 9 | \arguments{ 10 | \item{allow_colon}{Logical, default \code{FALSE}. If \code{TRUE}, expressions involving \code{:} 11 | won't throw a lint regardless of whether the inputs are implicitly integers.} 12 | } 13 | \description{ 14 | Check that integers are explicitly typed using the form \code{1L} instead of \code{1}. 15 | } 16 | \examples{ 17 | # will produce lints 18 | lint( 19 | text = "x <- 1", 20 | linters = implicit_integer_linter() 21 | ) 22 | 23 | lint( 24 | text = "x[2]", 25 | linters = implicit_integer_linter() 26 | ) 27 | 28 | lint( 29 | text = "1:10", 30 | linters = implicit_integer_linter() 31 | ) 32 | 33 | # okay 34 | lint( 35 | text = "x <- 1.0", 36 | linters = implicit_integer_linter() 37 | ) 38 | 39 | lint( 40 | text = "x <- 1L", 41 | linters = implicit_integer_linter() 42 | ) 43 | 44 | lint( 45 | text = "x[2L]", 46 | linters = implicit_integer_linter() 47 | ) 48 | 49 | lint( 50 | text = "1:10", 51 | linters = implicit_integer_linter(allow_colon = TRUE) 52 | ) 53 | 54 | } 55 | \seealso{ 56 | \link{linters} for a complete list of linters available in lintr. 57 | } 58 | \section{Tags}{ 59 | \link[=best_practices_linters]{best_practices}, \link[=configurable_linters]{configurable}, \link[=consistency_linters]{consistency}, \link[=style_linters]{style} 60 | } 61 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yaml: -------------------------------------------------------------------------------- 1 | url: https://lintr.r-lib.org 2 | 3 | template: 4 | bootstrap: 5 5 | light-switch: true 6 | includes: 7 | in_header: | 8 | 9 | 10 | development: 11 | mode: "auto" 12 | 13 | reference: 14 | - title: Linting 15 | contents: 16 | - lint 17 | - linters_with_defaults 18 | 19 | - title: Configuration 20 | contents: 21 | - read_settings 22 | - exclude 23 | - linters_with_tags 24 | - modify_defaults 25 | 26 | - title: Individual linters 27 | contents: 28 | - ends_with("linter") 29 | 30 | - title: Groups of linters 31 | contents: 32 | - ends_with("linters") 33 | 34 | - title: Common default configurations 35 | contents: 36 | - all_undesirable_functions 37 | - starts_with("default_") 38 | 39 | - title: Utilities 40 | contents: 41 | - Linter 42 | - expect_lint 43 | - expect_lint_free 44 | - ids_with_token 45 | - is_lint_level 46 | - get_r_string 47 | - use_lintr 48 | - make_linter_from_xpath 49 | - xml_nodes_to_lints 50 | - xp_call_name 51 | 52 | - title: Meta-tooling 53 | contents: 54 | - Lint 55 | - checkstyle_output 56 | - sarif_output 57 | - gitlab_output 58 | - clear_cache 59 | - get_source_expressions 60 | - parse_exclusions 61 | 62 | news: 63 | releases: 64 | - text: "Version 3.0.0" 65 | href: https://www.tidyverse.org/blog/2022/07/lintr-3-0-0/ 66 | -------------------------------------------------------------------------------- /man/expect_null_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_null_linter.R 3 | \name{expect_null_linter} 4 | \alias{expect_null_linter} 5 | \title{Require usage of \code{expect_null} for checking \code{NULL}} 6 | \usage{ 7 | expect_null_linter() 8 | } 9 | \description{ 10 | Require usage of \code{expect_null(x)} over \code{expect_equal(x, NULL)} and similar 11 | usages. 12 | } 13 | \details{ 14 | \code{\link[testthat:expect_null]{testthat::expect_null()}} exists specifically for testing for \code{NULL} objects. 15 | \code{\link[testthat:equality-expectations]{testthat::expect_equal()}}, \code{\link[testthat:equality-expectations]{testthat::expect_identical()}}, and 16 | \code{\link[testthat:logical-expectations]{testthat::expect_true()}} can also be used for such tests, 17 | but it is better to use the tailored function instead. 18 | } 19 | \examples{ 20 | # will produce lints 21 | lint( 22 | text = "expect_equal(x, NULL)", 23 | linters = expect_null_linter() 24 | ) 25 | 26 | lint( 27 | text = "expect_identical(x, NULL)", 28 | linters = expect_null_linter() 29 | ) 30 | 31 | lint( 32 | text = "expect_true(is.null(x))", 33 | linters = expect_null_linter() 34 | ) 35 | 36 | 37 | # okay 38 | lint( 39 | text = "expect_null(x)", 40 | linters = expect_null_linter() 41 | ) 42 | 43 | } 44 | \seealso{ 45 | \link{linters} for a complete list of linters available in lintr. 46 | } 47 | \section{Tags}{ 48 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat} 49 | } 50 | -------------------------------------------------------------------------------- /man/expect_true_false_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expect_true_false_linter.R 3 | \name{expect_true_false_linter} 4 | \alias{expect_true_false_linter} 5 | \title{Require usage of \code{expect_true(x)} over \code{expect_equal(x, TRUE)}} 6 | \usage{ 7 | expect_true_false_linter() 8 | } 9 | \description{ 10 | \code{\link[testthat:logical-expectations]{testthat::expect_true()}} and \code{\link[testthat:logical-expectations]{testthat::expect_false()}} exist specifically 11 | for testing the \code{TRUE}/\code{FALSE} value of an object. 12 | \code{\link[testthat:equality-expectations]{testthat::expect_equal()}} and \code{\link[testthat:equality-expectations]{testthat::expect_identical()}} can also be 13 | used for such tests, but it is better to use the tailored function instead. 14 | } 15 | \examples{ 16 | # will produce lints 17 | lint( 18 | text = "expect_equal(x, TRUE)", 19 | linters = expect_true_false_linter() 20 | ) 21 | 22 | lint( 23 | text = "expect_equal(x, FALSE)", 24 | linters = expect_true_false_linter() 25 | ) 26 | 27 | # okay 28 | lint( 29 | text = "expect_true(x)", 30 | linters = expect_true_false_linter() 31 | ) 32 | 33 | lint( 34 | text = "expect_false(x)", 35 | linters = expect_true_false_linter() 36 | ) 37 | 38 | } 39 | \seealso{ 40 | \link{linters} for a complete list of linters available in lintr. 41 | } 42 | \section{Tags}{ 43 | \link[=best_practices_linters]{best_practices}, \link[=package_development_linters]{package_development}, \link[=pkg_testthat_linters]{pkg_testthat}, \link[=readability_linters]{readability} 44 | } 45 | -------------------------------------------------------------------------------- /man/trailing_whitespace_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trailing_whitespace_linter.R 3 | \name{trailing_whitespace_linter} 4 | \alias{trailing_whitespace_linter} 5 | \title{Trailing whitespace linter} 6 | \usage{ 7 | trailing_whitespace_linter(allow_empty_lines = FALSE, allow_in_strings = TRUE) 8 | } 9 | \arguments{ 10 | \item{allow_empty_lines}{Suppress lints for lines that contain only whitespace.} 11 | 12 | \item{allow_in_strings}{Suppress lints for trailing whitespace in string constants.} 13 | } 14 | \description{ 15 | Check that there are no space characters at the end of source lines. 16 | } 17 | \examples{ 18 | # will produce lints 19 | lint( 20 | text = "x <- 1.2 ", 21 | linters = trailing_whitespace_linter() 22 | ) 23 | 24 | code_lines <- "a <- TRUE\n \nb <- FALSE" 25 | writeLines(code_lines) 26 | lint( 27 | text = code_lines, 28 | linters = trailing_whitespace_linter() 29 | ) 30 | 31 | # okay 32 | lint( 33 | text = "x <- 1.2", 34 | linters = trailing_whitespace_linter() 35 | ) 36 | 37 | lint( 38 | text = "x <- 1.2 # comment about this assignment", 39 | linters = trailing_whitespace_linter() 40 | ) 41 | 42 | code_lines <- "a <- TRUE\n \nb <- FALSE" 43 | writeLines(code_lines) 44 | lint( 45 | text = code_lines, 46 | linters = trailing_whitespace_linter(allow_empty_lines = TRUE) 47 | ) 48 | 49 | } 50 | \seealso{ 51 | \link{linters} for a complete list of linters available in lintr. 52 | } 53 | \section{Tags}{ 54 | \link[=configurable_linters]{configurable}, \link[=default_linters]{default}, \link[=style_linters]{style} 55 | } 56 | -------------------------------------------------------------------------------- /man/pipe_consistency_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipe_consistency_linter.R 3 | \name{pipe_consistency_linter} 4 | \alias{pipe_consistency_linter} 5 | \title{Pipe consistency linter} 6 | \usage{ 7 | pipe_consistency_linter(pipe = c("|>", "\%>\%", "auto")) 8 | } 9 | \arguments{ 10 | \item{pipe}{Which pipe operator is valid (either \code{"\%>\%"} or \code{"|>"}). The default 11 | is the native pipe (\verb{|>}). \code{"auto"} will instead 12 | only enforce consistency, i.e., that in any given file there is only one pipe.} 13 | } 14 | \description{ 15 | Check that the recommended pipe operator is used, or more conservatively that 16 | pipes are consistent by file. 17 | } 18 | \examples{ 19 | # will produce lints 20 | lint( 21 | text = "1:3 |> mean() \%>\% as.character()", 22 | linters = pipe_consistency_linter() 23 | ) 24 | 25 | lint( 26 | text = "1:3 \%>\% mean() \%>\% as.character()", 27 | linters = pipe_consistency_linter("|>") 28 | ) 29 | 30 | # okay 31 | lint( 32 | text = "1:3 |> mean() |> as.character()", 33 | linters = pipe_consistency_linter() 34 | ) 35 | 36 | lint( 37 | text = "1:3 \%>\% mean() \%>\% as.character()", 38 | linters = pipe_consistency_linter("\%>\%") 39 | ) 40 | } 41 | \seealso{ 42 | \itemize{ 43 | \item \link{linters} for a complete list of linters available in lintr. 44 | \item \url{https://style.tidyverse.org/pipes.html#magrittr} 45 | } 46 | } 47 | \section{Tags}{ 48 | \link[=configurable_linters]{configurable}, \link[=default_linters]{default}, \link[=readability_linters]{readability}, \link[=style_linters]{style} 49 | } 50 | -------------------------------------------------------------------------------- /R/terminal_close_linter.R: -------------------------------------------------------------------------------- 1 | #' Prohibit close() from terminating a function definition 2 | #' 3 | #' Functions that end in `close(x)` are almost always better written by using 4 | #' `on.exit(close(x))` close to where `x` is defined and/or opened. 5 | #' 6 | #' @examples 7 | #' # will produce lints 8 | #' code <- paste( 9 | #' "f <- function(fl) {", 10 | #' " conn <- file(fl, open = 'r')", 11 | #' " readLines(conn)", 12 | #' " close(conn)", 13 | #' "}", 14 | #' sep = "\n" 15 | #' ) 16 | #' writeLines(code) 17 | #' lint( 18 | #' text = code, 19 | #' linters = terminal_close_linter() 20 | #' ) 21 | #' 22 | #' # okay 23 | #' code <- paste( 24 | #' "f <- function(fl) {", 25 | #' " conn <- file(fl, open = 'r')", 26 | #' " on.exit(close(conn))", 27 | #' " readLines(conn)", 28 | #' "}", 29 | #' sep = "\n" 30 | #' ) 31 | #' writeLines(code) 32 | #' lint( 33 | #' text = code, 34 | #' linters = terminal_close_linter() 35 | #' ) 36 | #' 37 | #' @evalRd rd_tags("terminal_close_linter") 38 | #' @seealso [linters] for a complete list of linters available in lintr. 39 | #' @export 40 | terminal_close_linter <- make_linter_from_xpath( 41 | xpath = " 42 | (//FUNCTION | //OP-LAMBDA) 43 | /following-sibling::expr 44 | /expr[last()][ 45 | expr/SYMBOL_FUNCTION_CALL[text() = 'close'] 46 | or expr[ 47 | SYMBOL_FUNCTION_CALL[text() = 'return'] 48 | and following-sibling::expr/expr/SYMBOL_FUNCTION_CALL[text() = 'close'] 49 | ] 50 | ] 51 | ", 52 | lint_message = "Use on.exit(close(x)) to close connections instead of running it as the last call in a function." 53 | ) 54 | -------------------------------------------------------------------------------- /man/download_file_linter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/download_file_linter.R 3 | \name{download_file_linter} 4 | \alias{download_file_linter} 5 | \title{Recommend usage of a portable \code{mode} value for downloading files} 6 | \usage{ 7 | download_file_linter() 8 | } 9 | \description{ 10 | \code{mode = "w"} (the default) or \code{mode = "a"} in \code{download.file()} can generate broken files 11 | on Windows. Instead, \code{\link[utils:download.file]{utils::download.file()}} recommends the usage of \code{mode = "wb"} 12 | and \code{mode = "ab"}. 13 | If \code{method = "curl"} or \code{method = "wget"}, no \code{mode} should be provided as it will be ignored. 14 | } 15 | \examples{ 16 | # will produce lints 17 | lint( 18 | text = "download.file(x = my_url)", 19 | linters = download_file_linter() 20 | ) 21 | 22 | lint( 23 | text = "download.file(x = my_url, mode = 'w')", 24 | linters = download_file_linter() 25 | ) 26 | 27 | lint( 28 | text = "download.file(x = my_url, method = 'curl', mode = 'wb')", 29 | linters = download_file_linter() 30 | ) 31 | 32 | # okay 33 | lint( 34 | text = "download.file(x = my_url, mode = 'wb')", 35 | linters = download_file_linter() 36 | ) 37 | 38 | lint( 39 | text = "download.file(x = my_url, method = 'curl')", 40 | linters = download_file_linter() 41 | ) 42 | 43 | } 44 | \seealso{ 45 | \link{linters} for a complete list of linters available in lintr. 46 | } 47 | \section{Tags}{ 48 | \link[=best_practices_linters]{best_practices}, \link[=common_mistakes_linters]{common_mistakes}, \link[=robustness_linters]{robustness} 49 | } 50 | -------------------------------------------------------------------------------- /R/expect_length_linter.R: -------------------------------------------------------------------------------- 1 | #' Require usage of `expect_length(x, n)` over `expect_equal(length(x), n)` 2 | #' 3 | #' [testthat::expect_length()] exists specifically for testing the [length()] of 4 | #' an object. [testthat::expect_equal()] can also be used for such tests, 5 | #' but it is better to use the tailored function instead. 6 | #' 7 | #' @examples 8 | #' # will produce lints 9 | #' lint( 10 | #' text = "expect_equal(length(x), 2L)", 11 | #' linters = expect_length_linter() 12 | #' ) 13 | #' 14 | #' # okay 15 | #' lint( 16 | #' text = "expect_length(x, 2L)", 17 | #' linters = expect_length_linter() 18 | #' ) 19 | #' 20 | #' @evalRd rd_tags("expect_length_linter") 21 | #' @seealso [linters] for a complete list of linters available in lintr. 22 | #' @export 23 | expect_length_linter <- function() { 24 | # TODO(#2465): also catch expect_true(length(x) == 1) 25 | xpath <- " 26 | following-sibling::expr[ 27 | expr[1][SYMBOL_FUNCTION_CALL[text() = 'length']] 28 | and (position() = 1 or preceding-sibling::expr[NUM_CONST]) 29 | ] 30 | /parent::expr[not(SYMBOL_SUB[text() = 'info' or contains(text(), 'label')])] 31 | " 32 | 33 | Linter(linter_level = "expression", function(source_expression) { 34 | xml_calls <- source_expression$xml_find_function_calls(c("expect_equal", "expect_identical")) 35 | bad_expr <- xml_find_all(xml_calls, xpath) 36 | 37 | matched_function <- xp_call_name(bad_expr) 38 | lint_message <- sprintf("expect_length(x, n) is better than %s(length(x), n)", matched_function) 39 | xml_nodes_to_lints(bad_expr, source_expression, lint_message, type = "warning") 40 | }) 41 | } 42 | --------------------------------------------------------------------------------