Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tag based documentation #908

Merged
merged 16 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
^tests/testthat/dummy_packages/package/[.]Rbuildignore$
^tests/testthat/dummy_packages/package/[.]lintr$
^tests/testthat/dummy_packages/clean/[.]lintr$
^tests/testthat/dummy_packages/clean_subdir/[.]lintr$
^tests/testthat/dummy_packages/cp1252/[.]Rbuildignore$
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ Encoding: UTF-8
VignetteBuilder: knitr
RoxygenNote: 7.1.2
Collate:
'T_and_F_symbol_linter.R'
'utils.R'
'aaa.R'
'T_and_F_symbol_linter.R'
'actions.R'
'addins.R'
'assignment_linter.R'
Expand All @@ -70,6 +70,8 @@ Collate:
'infix_spaces_linter.R'
'line_length_linter.R'
'lint.R'
'linter_tag_docs.R'
'linter_tags.R'
'make_linter_from_regex.R'
'methods.R'
'missing_argument_linter.R'
Expand Down Expand Up @@ -101,3 +103,4 @@ Collate:
'unneeded_concatenation_linter.R'
'with_id.R'
'zzz.R'
Roxygen: list(markdown = TRUE)
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export(all_undesirable_functions)
export(all_undesirable_operators)
export(assignment_linter)
export(assignment_spaces_linter)
export(available_linters)
export(backport_linter)
export(checkstyle_output)
export(clear_cache)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ function calls. (#850, #851, @renkun-ken)
* Debugging functions (`browser()`, `debug()`, `debugcall()`, `debugonce()`, `trace()`, `undebug()`, `untrace()`) are now part of the default set of undesirable functions to help prevent them from being committed by mistake. (#876, @michaelchirico)
* New linter `package_hooks_linter()` runs a series of checks also done by `R CMD check` on the `.onLoad()`, `.onAttach()`, `.Last.lib()` and `.onDetach()` hooks (#882, @MichaelChirico)
* Improved location information for R parse errors (#894, #892, @renkun-ken and @AshesITR)
* New tag based documentation pages for linters (#888, @AshesITR)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would help to motivate this (why as lintr user do I care about this update?). maybe a quick blurb like

Add new tag-based documentation pages for linters to help organize information surrounding the growing number of linters available

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refining:

To help organize information surrounding the growing number of linters available, we've added metadata "tags" for each linter (#888).

bullet: each tag gets a help page, e.g. ?x

WDYT

* Each linter has its own help page
Copy link
Collaborator

@MichaelChirico MichaelChirico Mar 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe add an example ", e.g. ?seq_linter"

* `?linters` also links to tag help pages, collecting linters with a similar goal
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idea (you may have mentioned this/similar before): it may be nice to have an API to set linters by tag in .lintr, WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idea might be to export alll those linter lists like default_linters and think of a smart way of deduplication?

e.g. make something like linters: with_defaults(best_practices_linters, readability_linters) work, returning all linters that are tagged any of default, best_practices or readability work?

* Each linter can have multiple tags
* New function `available_linters()` to list available linters and their tags
This feature is extensible by package authors providing add-on linters for {lintr}.
Comment on lines +82 to +87
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could add the bullets on the top level and duplicate the issue number?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this bullet is pretty vague maybe a See ?available_linters reference?


# lintr 2.0.1

Expand Down
7 changes: 5 additions & 2 deletions R/T_and_F_symbol_linter.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#' @include aaa.R
#' `T` and `F` symbol linter
#'
#' @describeIn linters Avoid the symbols \code{T} and \code{F} (for \code{TRUE} and \code{FALSE}).
#' Avoid the symbols `T` and `F` (for `TRUE` and `FALSE`).
#'
#' @evalRd rd_tags("T_and_F_symbol_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
T_and_F_symbol_linter <- function() { # nolint: object_name_linter.
Linter(function(source_file) {
Expand Down
19 changes: 9 additions & 10 deletions R/aaa.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
NULL

#' Available linters
#'
#' @rdname linters
#' @name linters
#' @title linters
#'
#' @description A variety of linters is available in \pkg{lintr}. The most popular ones are readily
#' accessible through \code{\link{default_linters}}, though there are additional ones you may want
#' @description A variety of linters is available in \pkg{lintr}. The most popular ones are readily
#' accessible through [default_linters()], though there are additional ones you may want
#' to use.
#'
#' All the functions listed below are \bold{getters} that return a closure of class 'linter'.
#' Within a \code{\link{lint}} function call, the linters in use are initialized with the provided
#' arguments and fed with the source file (provided by \code{\link{get_source_expressions}}).
#' Within a [lint()] function call, the linters in use are initialized with the provided
#' arguments and fed with the source file (provided by [get_source_expressions()]).
#'
#' A data frame of all available linters can be retrieved using [available_linters()].
#' Documentation for linters is structured into tags to allow for easier discovery.
#'
#' @param length the length cutoff to use for the given linter.
#' @return A closure of class 'linter'.
#' @evalRd rd_taglist()
#' @evalRd rd_linterlist()
NULL

# need to register rex shortcuts as globals to avoid CRAN check errors
Expand Down
7 changes: 6 additions & 1 deletion R/assignment_linter.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#' @describeIn linters Check that \code{<-} is always used for assignment.
#' Assignment linter
#'
#' Check that `<-` is always used for assignment.
#'
#' @evalRd rd_tags("assignment_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
assignment_linter <- function() {
Linter(function(source_file) {
Expand Down
7 changes: 6 additions & 1 deletion R/assignment_spaces_linter.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#' @describeIn linters checks that assignments only have one space before and after
#' Assignment spaces linter
#'
#' Check that assignments only have one space before and after.
#'
#' @evalRd rd_tags("assignment_spaces_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
assignment_spaces_linter <- function() {
Linter(function(source_file) {
Expand Down
7 changes: 6 additions & 1 deletion R/backport_linter.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#' @describeIn linters that checks for usage of unavailable functions. Not reliable for testing r-devel dependencies.
#' Backport linter
#'
#' Check for usage of unavailable functions. Not reliable for testing r-devel dependencies.
#'
#' @param r_version Minimum R version to test for compatibility
#' @evalRd rd_tags("backport_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
backport_linter <- function(r_version = getRversion()) {
Linter(function(source_file) {
Expand Down
6 changes: 2 additions & 4 deletions R/cache.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
#' Clear the lintr cache
#'
#' @param file filename whose cache to clear. If you pass \code{NULL}, it will
#' delete all of the caches.
#' @param path directory to store caches. Reads option 'lintr.cache_directory'
#' as the default.
#' @param file filename whose cache to clear. If you pass `NULL`, it will delete all of the caches.
#' @param path directory to store caches. Reads option 'lintr.cache_directory' as the default.
#' @return 0 for success, 1 for failure, invisibly.
#' @export
clear_cache <- function(file = NULL, path = NULL) {
Expand Down
11 changes: 7 additions & 4 deletions R/closed_curly_linter.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#' @describeIn linters Check that closed curly braces are on their own line
#' unless they follow an else.
#' @param allow_single_line if \code{TRUE}, allow an open and closed curly pair
#' on the same line.
#' Closed curly linter
#'
#' Check that closed curly braces are on their own line unless they follow an else.
#'
#' @param allow_single_line if `TRUE`, allow an open and closed curly pair on the same line.
#' @evalRd rd_tags("closed_curly_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
closed_curly_linter <- function(allow_single_line = FALSE) {
Linter(function(source_file) {
Expand Down
8 changes: 6 additions & 2 deletions R/commas_linter.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#' @describeIn linters check that all commas are followed by spaces, but do not
#' have spaces before them.
#' Commas linter
#'
#' Check that all commas are followed by spaces, but do not have spaces before them.
#'
#' @evalRd rd_tags("commas_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @importFrom utils head
#' @export
commas_linter <- function() {
Expand Down
18 changes: 14 additions & 4 deletions R/comment_linters.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ ops <- list(
"&&",
rex("%", except_any_of("%"), "%"))

#' @describeIn linters Check that there is no commented code outside roxygen
#' blocks.
#' Commented code linter
#'
#' Check that there is no commented code outside roxygen blocks.
#'
#' @evalRd rd_tags("commented_code_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
commented_code_linter <- function() {
Linter(function(source_file) {
Expand Down Expand Up @@ -76,8 +80,14 @@ parsable <- function(x) {
}


#' @describeIn linters Check that the source contains no TODO comments (case-insensitive).
#' @param todo Vector of strings that identify TODO comments.
#' TODO comment linter
#'
#' Check that the source contains no TODO comments (case-insensitive).
#'
#' @param todo Vector of strings that identify TODO comments.
#'
#' @evalRd rd_tags("todo_comment_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
todo_comment_linter <- function(todo = c("todo", "fixme")) {
Linter(function(source_file) {
Expand Down
12 changes: 8 additions & 4 deletions R/cyclocomp_linter.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#' @describeIn linters Check for overly complicated expressions. See
#' \code{\link[cyclocomp]{cyclocomp}}.
#' @param complexity_limit expressions with a cyclomatic complexity higher than
#' this are linted, defaults to 15. See \code{\link[cyclocomp]{cyclocomp}}.
#' Cyclomatic complexity linter
#'
#' Check for overly complicated expressions. See [cyclocomp::cyclocomp()].
#'
#' @param complexity_limit expressions with a cyclomatic complexity higher than this are linted, defaults to 15.
#' See [cyclocomp::cyclocomp()].
#' @evalRd rd_tags("cyclocomp_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @importFrom cyclocomp cyclocomp
#' @export
cyclocomp_linter <- function(complexity_limit = 15L) {
Expand Down
7 changes: 6 additions & 1 deletion R/duplicate_argument_linter.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#' @describeIn linters checks for duplicate arguments in function calls.
#' Duplicate argument linter
#'
#' Check for duplicate arguments in function calls.
#'
#' @param except a character vector of function names as exceptions.
#' @evalRd rd_tags("duplicate_argument_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
duplicate_argument_linter <- function(except = character()) {
Linter(function(source_file) {
Expand Down
7 changes: 6 additions & 1 deletion R/equals_na_linter.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#' @describeIn linters that checks for x == NA and x != NA
#' Equality check with NA linter
#'
#' Check for `x == NA` and `x != NA`
#'
#' @evalRd rd_tags("equals_na_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
equals_na_linter <- function() {
Linter(function(source_file) {
Expand Down
39 changes: 19 additions & 20 deletions R/exclude.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
#'
#' @param lints that need to be filtered.
#' @param exclusions manually specified exclusions
#' @param ... additional arguments passed to \code{\link{parse_exclusions}}
#' @param ... additional arguments passed to [parse_exclusions()]
#' @details
#' Exclusions can be specified in three different ways.
#' \enumerate{
#' \item{single line in the source file. default: \code{# nolint}, possibly followed by a listing of linters to exclude.
#' If the listing is missing, all linters are excluded on that line. The default listing format is
#' \code{# nolint: linter_name, linter2_name.}. There may not be anything between the colon and the line exclusion tag
#' and the listing must be terminated with a full stop (\code{.}) for the linter list to be respected.}
#' \item{line range in the source file. default: \code{# nolint start}, \code{# nolint end}.
#' \code{#}\code{ nolint start} accepts linter lists in the same form as code{# nolint}.}
#' \item{exclusions parameter, a named list of files with named lists of linters and lines to exclude them on,
#' a named list of the files and lines to exclude, or just the filenames if you want to exclude the entire file,
#' or the directory names if you want to exclude all files in a directory.}
#' }
#'
#' 1. single line in the source file. default: `# nolint`, possibly followed by a listing of linters to exclude.
#' If the listing is missing, all linters are excluded on that line. The default listing format is
#' `# nolint: linter_name, linter2_name.`. There may not be anything between the colon and the line exclusion tag
#' and the listing must be terminated with a full stop (`.`) for the linter list to be respected.
#' 2. line range in the source file. default: `# nolint start`, `# nolint end`. `# nolint start` accepts linter
#' lists in the same form as `# nolint`.
#' 3. exclusions parameter, a named list of files with named lists of linters and lines to exclude them on, a named
#' list of the files and lines to exclude, or just the filenames if you want to exclude the entire file, or the
#' directory names if you want to exclude all files in a directory.
exclude <- function(lints, exclusions = settings$exclusions, ...) {
if (length(lints) <= 0) {
return(lints)
Expand Down Expand Up @@ -75,9 +74,9 @@ line_info <- function(line_numbers, type = c("start", "end")) {
#' @param exclude_start regular expression used to mark the start of an excluded range
#' @param exclude_end regular expression used to mark the end of an excluded range
#' @param exclude_linter regular expression used to capture a list of to-be-excluded linters immediately following a
#' \code{exclude} or \code{exclude_start} marker.
#' `exclude` or `exclude_start` marker.
#' @param exclude_linter_sep regular expression used to split a linter list into indivdual linter names for exclusion.
#' @param lines a character vector of the content lines of \code{file}
#' @param lines a character vector of the content lines of `file`
#'
#' @return A possibly named list of excluded lines, possibly for specific linters.
parse_exclusions <- function(file, exclude = settings$exclude,
Expand Down Expand Up @@ -170,24 +169,24 @@ add_exclusions <- function(exclusions, lines, linters_string, exclude_linter_sep
#' Normalize lint exclusions
#'
#' @param x Exclusion specification
#' - A character vector of filenames or directories relative to \code{root}
#' - A character vector of filenames or directories relative to `root`
#' - A named list of integers specifying lines to be excluded per file
#' - A named list of named lists specifying linters and lines to be excluded for the linters per file.
#' @param normalize_path Should the names of the returned exclusion list be normalized paths?
#' If no, they will be relative to \code{root}.
#' If no, they will be relative to `root`.
#' @param root Base directory for relative filename resolution.
#' @param pattern If non-NULL, only exclude files in excluded directories if they match
#' \code{pattern}. Passed to \link[base]{list.files} if a directory is excluded.
#' `pattern`. Passed to [list.files][base::list.files] if a directory is excluded.
#'
#' @return A named list of file exclusions.
#' The names of the list specify the filenames to be excluded.
#'
#' Each file exclusion is a possibly named list containing line numbers to exclude, or the sentinel \code{Inf} for
#' Each file exclusion is a possibly named list containing line numbers to exclude, or the sentinel `Inf` for
#' completely excluded files. If the an entry is named, the exclusions only take effect for the linter with the same
#' name.
#'
#' If \code{normalize_path} is \code{TRUE}, file names will be normalized relative to \code{root}.
#' Otherwise the paths are left as provided (relative to \code{root} or absolute).
#' If `normalize_path` is `TRUE`, file names will be normalized relative to `root`.
#' Otherwise the paths are left as provided (relative to `root` or absolute).
#'
#' @keywords internal
normalize_exclusions <- function(x, normalize_path = TRUE,
Expand Down
31 changes: 14 additions & 17 deletions R/expect_lint.R
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
#' Lint expectation
#'
#' This is an expectation function to test that the lints produced by \code{lint} satisfy a number
#' of checks.
#' This is an expectation function to test that the lints produced by `lint` satisfy a number of checks.
#'
#' @param content a character vector for the file content to be linted, each vector element
#' representing a line of text.
#' @param content a character vector for the file content to be linted, each vector element representing a line of
#' text.
#' @param checks checks to be performed:
#' \describe{
#' \item{NULL}{check that no lints are returned.}
#' \item{single string or regex object}{check that the single lint returned has a matching
#' message.}
#' \item{named list}{check that the single lint returned has fields that match. Accepted fields
#' are the same as those taken by \code{\link{Lint}}.}
#' \item{list of named lists}{for each of the multiple lints returned, check that it matches
#' the checks in the corresponding named list (as described in the point above).}
#' \item{single string or regex object}{check that the single lint returned has a matching message.}
#' \item{named list}{check that the single lint returned has fields that match. Accepted fields are the same as those
#' taken by [Lint()].}
#' \item{list of named lists}{for each of the multiple lints returned, check that it matches the checks in the
#' corresponding named list (as described in the point above).}
#' }
#' Named vectors are also accepted instead of named lists, but this is a compatibility feature that
#' is not recommended for new code.
#' @param ... arguments passed to \code{\link{lint}}, e.g. the linters or cache to use.
#' @param file if not \code{NULL}, read content from the specified file rather than from \code{content}.
#' @param language temporarily override Rs \code{LANGUAGE} envvar, controlling localisation of base
#' R error messages. This makes testing them reproducible on all systems irrespective of their
#' native R language setting.
#' @return \code{NULL}, invisibly.
#' @param ... arguments passed to [lint()], e.g. the linters or cache to use.
#' @param file if not `NULL`, read content from the specified file rather than from `content`.
#' @param language temporarily override Rs `LANGUAGE` envvar, controlling localisation of base R error messages.
#' This makes testing them reproducible on all systems irrespective of their native R language setting.
#' @return `NULL`, invisibly.
#' @examples
#' # no expected lint
#' expect_lint("a", NULL, trailing_blank_lines_linter)
Expand Down Expand Up @@ -122,7 +119,7 @@ expect_lint <- function(content, checks, ..., file = NULL, language = "en") {
#' lints in the package. It can be used to ensure that your tests fail if the package
#' contains lints.
#'
#' @param ... arguments passed to \code{\link{lint_package}}
#' @param ... arguments passed to [lint_package()]
#' @export
expect_lint_free <- function(...) {
testthat::skip_on_cran()
Expand Down
9 changes: 7 additions & 2 deletions R/extraction_operator_linter.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#' @describeIn linters Check that the `[[` operator is used when extracting a single element from
#' an object, not `[` (subsetting) nor `$` (interactive use).
#' Extraction operator linter
#'
#' Check that the `[[` operator is used when extracting a single element from an object, not `[` (subsetting) nor `$`
#' (interactive use).
#'
#' @evalRd rd_tags("extraction_operator_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
extraction_operator_linter <- function() {
Linter(function(source_file) {
Expand Down
8 changes: 6 additions & 2 deletions R/function_left_parentheses.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#' @describeIn linters check that all left parentheses in a function call
#' do not have spaces before them.
#' Function left parentheses linter
#'
#' Check that all left parentheses in a function call do not have spaces before them.
#'
#' @evalRd rd_tags("function_left_parentheses_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
function_left_parentheses_linter <- function() { # nolint: object_length_linter.
Linter(function(source_file) {
Expand Down
Loading