├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── conflicted.R ├── conflicts.R ├── disambiguate.R ├── favor.R ├── find.R ├── package.R ├── prefer.R ├── shim.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── conflicted.Rproj ├── cran-comments.md ├── man ├── conflict_prefer.Rd ├── conflict_scout.Rd ├── conflicted-package.Rd └── conflicts_prefer.Rd ├── revdep ├── .gitignore ├── README.md ├── cran.md ├── email.yml ├── failures.md └── problems.md └── tests ├── testthat.R └── testthat ├── _snaps ├── disambiguate.md ├── favor.md ├── find.md ├── prefer.md └── shim.md ├── data ├── DESCRIPTION ├── NAMESPACE └── data │ └── mtcars.rda ├── funmatch ├── DESCRIPTION ├── NAMESPACE └── R │ └── test.R ├── pipe ├── DESCRIPTION ├── NAMESPACE └── R │ └── test.R ├── prefs ├── DESCRIPTION ├── NAMESPACE └── R │ └── test.R ├── primitive ├── DESCRIPTION ├── NAMESPACE └── R │ └── sum.R ├── test-disambiguate.R ├── test-favor.R ├── test-find.R ├── test-package.R ├── test-prefer.R ├── test-shim.R ├── test-utils.R └── test-zzz.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^revdep$ 2 | ^CRAN-RELEASE$ 3 | ^docs$ 4 | ^_pkgdown\.yml$ 5 | ^.*\.Rproj$ 6 | ^\.Rproj\.user$ 7 | ^\.travis\.yml$ 8 | ^README\.Rmd$ 9 | ^README-.*\.png$ 10 | ^codecov\.yml$ 11 | ^CODE_OF_CONDUCT\.md$ 12 | ^cran-comments\.md$ 13 | ^LICENSE\.md$ 14 | ^\.github$ 15 | ^pkgdown$ 16 | ^CRAN-SUBMISSION$ 17 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.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 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | 19 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | config: 25 | - {os: macos-latest, r: 'release'} 26 | 27 | - {os: windows-latest, r: 'release'} 28 | # Use 3.6 to trigger usage of RTools35 29 | - {os: windows-latest, r: '3.6'} 30 | # use 4.1 to check with rtools40's older compiler 31 | - {os: windows-latest, r: '4.1'} 32 | 33 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 34 | - {os: ubuntu-latest, r: 'release'} 35 | - {os: ubuntu-latest, r: 'oldrel-1'} 36 | - {os: ubuntu-latest, r: 'oldrel-2'} 37 | - {os: ubuntu-latest, r: 'oldrel-3'} 38 | - {os: ubuntu-latest, r: 'oldrel-4'} 39 | 40 | env: 41 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 42 | R_KEEP_PKG_SOURCE: yes 43 | 44 | steps: 45 | - uses: actions/checkout@v3 46 | 47 | - uses: r-lib/actions/setup-pandoc@v2 48 | 49 | - uses: r-lib/actions/setup-r@v2 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | http-user-agent: ${{ matrix.config.http-user-agent }} 53 | use-public-rspm: true 54 | 55 | - uses: r-lib/actions/setup-r-dependencies@v2 56 | with: 57 | extra-packages: any::rcmdcheck 58 | needs: check 59 | 60 | - uses: r-lib/actions/check-r-package@v2 61 | with: 62 | upload-snapshots: true 63 | -------------------------------------------------------------------------------- /.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 | permissions: 23 | contents: write 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - uses: r-lib/actions/setup-pandoc@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | use-public-rspm: true 32 | 33 | - uses: r-lib/actions/setup-r-dependencies@v2 34 | with: 35 | extra-packages: any::pkgdown, local::. 36 | needs: website 37 | 38 | - name: Build site 39 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 40 | shell: Rscript {0} 41 | 42 | - name: Deploy to GitHub pages 🚀 43 | if: github.event_name != 'pull_request' 44 | uses: JamesIves/github-pages-deploy-action@v4.4.1 45 | with: 46 | clean: false 47 | branch: gh-pages 48 | folder: docs 49 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.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 | issue_comment: 5 | types: [created] 6 | 7 | name: Commands 8 | 9 | jobs: 10 | document: 11 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 12 | name: document 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: r-lib/actions/pr-fetch@v2 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - uses: r-lib/actions/setup-r@v2 24 | with: 25 | use-public-rspm: true 26 | 27 | - uses: r-lib/actions/setup-r-dependencies@v2 28 | with: 29 | extra-packages: any::roxygen2 30 | needs: pr-document 31 | 32 | - name: Document 33 | run: roxygen2::roxygenise() 34 | shell: Rscript {0} 35 | 36 | - name: commit 37 | run: | 38 | git config --local user.name "$GITHUB_ACTOR" 39 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 40 | git add man/\* NAMESPACE 41 | git commit -m 'Document' 42 | 43 | - uses: r-lib/actions/pr-push@v2 44 | with: 45 | repo-token: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | style: 48 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 49 | name: style 50 | runs-on: ubuntu-latest 51 | env: 52 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 53 | steps: 54 | - uses: actions/checkout@v3 55 | 56 | - uses: r-lib/actions/pr-fetch@v2 57 | with: 58 | repo-token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - uses: r-lib/actions/setup-r@v2 61 | 62 | - name: Install dependencies 63 | run: install.packages("styler") 64 | shell: Rscript {0} 65 | 66 | - name: Style 67 | run: styler::style_pkg() 68 | shell: Rscript {0} 69 | 70 | - name: commit 71 | run: | 72 | git config --local user.name "$GITHUB_ACTOR" 73 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 74 | git add \*.R 75 | git commit -m 'Style' 76 | 77 | - uses: r-lib/actions/pr-push@v2 78 | with: 79 | repo-token: ${{ secrets.GITHUB_TOKEN }} 80 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.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 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: | 31 | covr::codecov( 32 | quiet = FALSE, 33 | clean = FALSE, 34 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 35 | ) 36 | shell: Rscript {0} 37 | 38 | - name: Show testthat output 39 | if: always() 40 | run: | 41 | ## -------------------------------------------------------------------- 42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 43 | shell: bash 44 | 45 | - name: Upload test results 46 | if: failure() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: coverage-test-failures 50 | path: ${{ runner.temp }}/package 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | docs 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: conflicted 2 | Title: An Alternative Conflict Resolution Strategy 3 | Version: 1.2.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@posit.co", role = c("aut", "cre")), 6 | person("Posit Software, PBC", role = c("cph", "fnd")) 7 | ) 8 | Description: R's default conflict management system gives the most 9 | recently loaded package precedence. This can make it hard to detect 10 | conflicts, particularly when they arise because a package update 11 | creates ambiguity that did not previously exist. 'conflicted' takes a 12 | different approach, making every conflict an error and forcing you to 13 | choose which function to use. 14 | License: MIT + file LICENSE 15 | URL: https://conflicted.r-lib.org/, https://github.com/r-lib/conflicted 16 | BugReports: https://github.com/r-lib/conflicted/issues 17 | Depends: 18 | R (>= 3.6) 19 | Imports: 20 | cli (>= 3.4.0), 21 | memoise, 22 | rlang (>= 1.0.0) 23 | Suggests: 24 | callr, 25 | covr, 26 | dplyr, 27 | Matrix, 28 | methods, 29 | pkgload, 30 | testthat (>= 3.0.0), 31 | withr 32 | Config/Needs/website: tidyverse/tidytemplate 33 | Config/testthat/edition: 3 34 | Encoding: UTF-8 35 | Roxygen: list(markdown = TRUE) 36 | RoxygenNote: 7.2.3 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: conflicted authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 conflicted 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 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("[",conflict_report) 4 | S3method(print,conflict_report) 5 | export(conflict_prefer) 6 | export(conflict_prefer_all) 7 | export(conflict_prefer_matching) 8 | export(conflict_scout) 9 | export(conflicts_prefer) 10 | import(rlang) 11 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # conflicted (development version) 2 | 3 | * `conflict_scout()` refers to the package where the function was defined, 4 | for reexported functions (#93). 5 | 6 | * `conflict_scout()` no longer returns functions whose conflicts have been 7 | resolved manually or automatically (#95). 8 | 9 | # conflicted 1.2.0 10 | 11 | * New `conflicts_prefer()` to easily declare multiple preferences at once: 12 | `conflicts_prefer(dplyr::filter, lubridate::week, ...)` (#82). 13 | 14 | * Disambiguation message now provides clickable preferences (#74). 15 | 16 | * Conflicts now take into account the `include.only` and `exclude` arguments 17 | that you might have specified in `library()` (#84). 18 | 19 | * `conflict_prefer_all()` and `conflict_prefer_matching()` are now much faster. 20 | And when `losers` is supplied, they only register the minimal necessary 21 | number of conflicts. 22 | 23 | # conflicted 1.1.0 24 | 25 | * New `conflicted_prefer_all()` and `conflicted_prefer_matching()` to 26 | prefer functions en masse (#51). 27 | 28 | * Improvements to conflict detection and resolution: 29 | 30 | * Reports conflicts involving lazy loaded datasets (#54). 31 | 32 | * Don't report conflicts involving a `standardGeneric` (#47). 33 | 34 | * Better handling of conflicts cleared by superset principle: if there is 35 | a conflict all functions (including any base functions) are reported, and 36 | if there isn't a conflict, no packages are reported (instead of 1) (#47). 37 | 38 | * Don't report conflict between a function and a non-function (#30). 39 | 40 | * Conflicts involving a primitive function no longer error 41 | (@nerskin, #46, #48). 42 | 43 | # conflicted 1.0.4 44 | 45 | * Fixes for dev rlang 46 | 47 | # conflicted 1.0.3 48 | 49 | * Fix > vs >= mistake 50 | 51 | # conflicted 1.0.2 52 | 53 | * Align with changes to R 3.6 54 | 55 | # conflicted 1.0.1 56 | 57 | * Internal `has_moved()` function no longer fails when it encounters a 58 | call to `.Deprecated()` with no arguments (#29). 59 | 60 | * `.conflicts` environment is correctly removed and replaced each time 61 | a new package is loaded (#28). 62 | 63 | # conflicted 1.0.0 64 | 65 | ### New functions 66 | 67 | * `conflict_scout()` reports all conflicts found with a set of packages. 68 | 69 | * `conflict_prefer()` allows you to declare a persistent preference 70 | (within a session) for one function over another (#4). 71 | 72 | ### Improve conflict resolution 73 | 74 | * conflicts now generally expects packages that override functions in base 75 | packages to obey the "superset principle", i.e. that `foo::bar(...)` must 76 | return the same value of `base::bar(...)` whenever the input is not an 77 | error. In other words, if you override a base function you can only extend 78 | the API, not change or reduce it. 79 | 80 | There are two exceptions. If the arguments of the two functions are not 81 | compatible (i.e. the function in the package doesn't include all 82 | arguments of the base package), conflicts can tell it doesn't follow 83 | the superset principle. Additionally, `dplyr::lag()` fails to follow 84 | the superset principle, and is marked as a special case (#2). 85 | 86 | * Functions that have moved between packages (i.e. functions with a call to 87 | `.Deprecated("pkg::foo")`) as the first element of the function body) will 88 | never generate conflicts. 89 | 90 | * conflicted now listens for `detach()` events and removes conflicts that 91 | are removed by detaching a package (#5) 92 | 93 | ## Minor improvements and bug fixes 94 | 95 | * Error messages for infix functions and non-syntactic function names are 96 | improved (#14) 97 | -------------------------------------------------------------------------------- /R/conflicted.R: -------------------------------------------------------------------------------- 1 | #' @section Resolving conflicts: 2 | #' 3 | #' To permanently resolve a conflict within a session, use assignment: 4 | #' 5 | #' \preformatted{ 6 | #' library(conflicted) 7 | #' library(dplyr) 8 | #' 9 | #' filter <- dplyr::filter 10 | #' } 11 | #' 12 | #' @keywords internal 13 | #' @import rlang 14 | "_PACKAGE" 15 | -------------------------------------------------------------------------------- /R/conflicts.R: -------------------------------------------------------------------------------- 1 | conflicts_register <- function(pkgs = pkgs_attached()) { 2 | conflicts <- conflict_scout(pkgs) 3 | 4 | env <- conflicts_init() 5 | map2(names(conflicts), conflicts, conflict_disambiguate, env = env) 6 | 7 | # Shim library() and require() so we can rebuild 8 | shims_bind(env) 9 | 10 | env 11 | } 12 | 13 | conflicts_register_if_needed <- function(package) { 14 | if (pkg_attached(package)) { 15 | conflicts_register() 16 | } 17 | invisible() 18 | } 19 | 20 | conflicts_remove <- function(pkg) { 21 | # The detach hook is called before the package is removed from the search path 22 | conflicts_register(setdiff(pkgs_attached(), pkg)) 23 | } 24 | 25 | # Environment management ------------------------------------------------- 26 | 27 | conflicts_init <- function() { 28 | conflicts_reset() 29 | get("attach")(env(), name = ".conflicts") 30 | } 31 | 32 | conflicts_attached <- function() { 33 | ".conflicts" %in% search() 34 | } 35 | 36 | conflicts_reset <- function() { 37 | if (conflicts_attached()) { 38 | detach(".conflicts", character.only = TRUE) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /R/disambiguate.R: -------------------------------------------------------------------------------- 1 | conflict_disambiguate <- function(fun, pkgs, env) { 2 | if (length(pkgs) == 0) { 3 | # no conflict, so no action needed 4 | } else if (length(pkgs) == 1) { 5 | # No ambiguity, but need to make sure this choice wins, not version 6 | # from search path (which might be in wrong order) 7 | env_bind(env, !!fun := getExportedValue(pkgs, fun)) 8 | } else { 9 | if (is_infix_fun(fun)) { 10 | env_bind_active(env, !!fun := disambiguate_infix(fun, pkgs)) 11 | } else { 12 | env_bind_active(env, !!fun := disambiguate_prefix(fun, pkgs)) 13 | } 14 | } 15 | } 16 | 17 | disambiguate_infix <- function(name, pkgs) { 18 | force(name) 19 | force(pkgs) 20 | 21 | function(value) { 22 | if (from_save(sys.calls())) 23 | return(NULL) 24 | 25 | prefer <- prefer_bullets(pkgs, name) 26 | 27 | cli::cli_abort(c( 28 | "{label_conflicted()} {.strong {name}} found in {length(pkgs)} packages.", 29 | "Declare a preference with {.fn {add_ns('conflicts_prefer')}}:", 30 | prefer 31 | )) 32 | } 33 | } 34 | 35 | disambiguate_prefix <- function(name, pkgs) { 36 | force(name) 37 | force(pkgs) 38 | 39 | function(value) { 40 | if (from_save(sys.calls())) 41 | return(NULL) 42 | 43 | bt_name <- backtick(name) 44 | 45 | namespace <- map_chr(pkgs, function(pkg) { 46 | style_object(pkg, name) 47 | }) 48 | names(namespace) <- rep("*", length(namespace)) 49 | prefer <- prefer_bullets(pkgs, name) 50 | 51 | cli::cli_abort(c( 52 | "{label_conflicted()} {.strong {name}} found in {length(pkgs)} packages.", 53 | "Either pick the one you want with {.code ::}:", 54 | namespace, 55 | "Or declare a preference with {.fn {add_ns('conflicts_prefer')}}:", 56 | prefer 57 | )) 58 | } 59 | } 60 | 61 | add_ns <- function(fun = "") { 62 | paste0(if (!pkg_attached("conflicted")) "conflicted::", fun) 63 | } 64 | 65 | # Helpers ----------------------------------------------------------------- 66 | 67 | prefer_bullets <- function(pkgs, name) { 68 | if (make.names(name) != name) { 69 | name <- backtick(name) 70 | } 71 | 72 | ns <- add_ns() 73 | prefer <- map_chr(pkgs, function(pkg) { 74 | sprintf( 75 | "{.run [%sconflicts_prefer(%s::%s)](conflicted::conflicts_prefer(%s::%s))}", 76 | ns, 77 | pkg, 78 | name, 79 | pkg, 80 | name 81 | ) 82 | }) 83 | 84 | names(prefer) <- rep("*", length(prefer)) 85 | prefer 86 | } 87 | 88 | is_infix_fun <- function(name) { 89 | base <- c( 90 | ":", "::", ":::", "$", "@", "^", "*", "/", "+", "-", ">", ">=", 91 | "<", "<=", "==", "!=", "!", "&", "&&", "|", "||", "~", "<-", "<<-" 92 | ) 93 | name %in% base || grepl("^%.*%$", name) 94 | } 95 | 96 | # This is a hack needed for RStudio, which saves and restores environments 97 | # when you "install and restart", because save() evaluates active bindings. 98 | from_save <- function(calls) { 99 | is_save <- function(call) { 100 | is_call(call) && length(call) > 1 && is_symbol(call[[1]], "save") 101 | } 102 | 103 | for (call in calls) { 104 | if (is_save(call)) 105 | return(TRUE) 106 | } 107 | 108 | FALSE 109 | } 110 | -------------------------------------------------------------------------------- /R/favor.R: -------------------------------------------------------------------------------- 1 | #' Declare many preferences at once 2 | #' 3 | #' @description 4 | #' `conflicts_prefer()` allows you to declare "winners" of conflicts, 5 | #' declaring one or many winners at once. 6 | #' 7 | #' See [conflict_prefer()] for more precise control. 8 | #' 9 | #' @section Best practices: 10 | #' I recommend placing a single call to `conflicts_prefer()` at the top of 11 | #' your script, immediately after loading all needed packages with calls to 12 | #' `library()`. 13 | #' 14 | #' @export 15 | #' @param ... Functions to prefer in form `pkg::fun` or `pkg::fun()`. 16 | #' @inheritParams conflict_prefer 17 | #' @examples 18 | #' conflicts_prefer( 19 | #' dplyr::filter(), 20 | #' dplyr::lag(), 21 | #' ) 22 | #' 23 | #' # or 24 | #' conflicts_prefer( 25 | #' dplyr::filter, 26 | #' dplyr::lag, 27 | #' ) 28 | conflicts_prefer <- function(..., .quiet = FALSE) { 29 | calls <- exprs(...) 30 | # accept pkg::fun() or pkg::fun 31 | calls <- lapply(calls, standardise_call) 32 | 33 | is_ok <- vapply(calls, is_ns_call, logical(1)) 34 | if (any(!is_ok)) { 35 | cli::cli_abort( 36 | "All arguments must be in form {.code pkg::fun} or {.fn pkg::fun}." 37 | ) 38 | } 39 | 40 | pkgs <- vapply(calls, function(x) as.character(x[[2]]), character(1)) 41 | funs <- vapply(calls, function(x) as.character(x[[3]]), character(1)) 42 | 43 | for (i in seq_along(calls)) { 44 | conflict_preference_register(funs[[i]], pkgs[[i]], quiet = .quiet) 45 | } 46 | 47 | conflicts_register() 48 | 49 | invisible() 50 | } 51 | 52 | standardise_call <- function(x) { 53 | if (is_call(x, n = 0)) { 54 | x[[1]] 55 | } else { 56 | x 57 | } 58 | } 59 | 60 | is_ns_call <- function(x) { 61 | is_call(x, "::", n = 2) 62 | } 63 | -------------------------------------------------------------------------------- /R/find.R: -------------------------------------------------------------------------------- 1 | #' Find conflicts amongst a set of packages 2 | #' 3 | #' `conflict_scout()` is the workhorse behind the conflicted package. You can 4 | #' call it directly yourself if you want to see all conflicts before hitting 5 | #' them in practice. 6 | #' 7 | #' For a reexported function, this function will report the package where the 8 | #' function is defined. If this package is not attached, it will report 9 | #' the first such package on the search path or in the `pkgs` argument. 10 | # 11 | #' @param pkgs Set of packages for which to report conflicts. If `NULL`, 12 | #' the default, will report conflicts for all loaded packages 13 | #' @return A named list of character vectors. The names are functions and 14 | #' the values are the packages where they appear. Disambiguated functions 15 | #' are removed from that list. 16 | #' 17 | #' A user friendly print method displays the result as bulleted list. 18 | #' @export 19 | #' @examples 20 | #' conflict_scout() 21 | conflict_scout <- function(pkgs = NULL) { 22 | pkgs <- pkgs %||% pkgs_attached() 23 | objs <- lapply(pkgs, pkg_ls) 24 | names(objs) <- pkgs 25 | 26 | index <- invert(objs) 27 | potential <- index[lengths(index) > 1] 28 | 29 | # Only consider it a conflict if the objects are actually different 30 | unique <- Map(unique_obj, names(potential), potential) 31 | conflicts <- unique[lengths(unique) > 1] 32 | 33 | # superset principle: ignore single conflict with base packages 34 | # unless attr(f, "conflicted_superset") is FALSE 35 | conflicts <- map2(names(conflicts), conflicts, superset_principle) 36 | 37 | # a function that has moved packages should never conflict 38 | conflicts <- map2(names(conflicts), conflicts, drop_moved) 39 | 40 | # a function doesn't conflict with a non-dataset 41 | conflicts <- map2(names(conflicts), conflicts, function_lookup) 42 | 43 | # apply declared user preferences 44 | for (fun in ls(prefs)) { 45 | if (!has_name(conflicts, fun)) 46 | next 47 | 48 | conflicts[[fun]] <- prefs_resolve(fun, conflicts[[fun]]) 49 | } 50 | 51 | # remove all non-conflicts from the list 52 | conflicts <- conflicts[lengths(conflicts) > 1] 53 | 54 | new_conflict_report(conflicts) 55 | } 56 | 57 | new_conflict_report <- function(conflicts, n = length(conflicts)) { 58 | structure(conflicts, n = n, class = "conflict_report") 59 | } 60 | 61 | #' @export 62 | `[.conflict_report` <- function(x, ...) { 63 | new_conflict_report(NextMethod(), n = attr(x, "n")) 64 | } 65 | 66 | #' @export 67 | print.conflict_report <- function(x, ...) { 68 | n <- attr(x, "n") 69 | cli::cli_text("{n} conflict{?s}") 70 | 71 | if (length(x) == 0) { 72 | return(invisible(x)) 73 | } 74 | 75 | x <- x[order(names(x))] 76 | fun <- map_chr(names(x), function(x) sprintf("{.fn %s}", x)) 77 | pkgs <- map_chr(x, function(x) cli::format_inline("{.pkg {x}}")) 78 | bullets <- paste0(fun, ": ", pkgs) 79 | names(bullets) <- rep("*", length(bullets)) 80 | 81 | cli::cli_bullets(bullets) 82 | if (n > length(x)) { 83 | cat_line("...") 84 | } 85 | invisible(x) 86 | } 87 | 88 | superset_principle <- function(fun, pkgs) { 89 | base <- intersect(pkgs, base_packages) 90 | non_base <- setdiff(pkgs, base_packages) 91 | 92 | if (length(non_base) == 0) { 93 | # all base, so no conflicts 94 | character() 95 | } else if (length(non_base) == 1) { 96 | # only one so see if it assumes superset principle 97 | if (is_superset(fun, non_base, base = base)) { 98 | character() 99 | } else { 100 | pkgs 101 | } 102 | } else { 103 | pkgs 104 | } 105 | } 106 | 107 | function_lookup <- function(fun, pkgs) { 108 | # Only apply this rule if exaclty two conflicts 109 | if (length(pkgs) != 2) { 110 | return(pkgs) 111 | } 112 | 113 | pkg1 <- getExportedValue(pkgs[[1]], fun) 114 | pkg2 <- getExportedValue(pkgs[[2]], fun) 115 | 116 | if (is.function(pkg1) != is.function(pkg2)) { 117 | character() 118 | } else { 119 | pkgs 120 | } 121 | 122 | } 123 | 124 | is_superset <- function(fun, pkg, base) { 125 | # Special case dplyr::lag() which looks like it should agree 126 | if (pkg == "dplyr" && fun == "lag") 127 | return(FALSE) 128 | 129 | pkg_obj <- getExportedValue(pkg, fun) 130 | # If it's a standardGeneric, assume the author know's what they're doing 131 | if (methods::is(pkg_obj, "standardGeneric")) { 132 | return(TRUE) 133 | } 134 | 135 | base_obj <- getExportedValue(base, fun) 136 | if (!is.function(pkg_obj) || !is.function(base_obj)) 137 | return(FALSE) 138 | 139 | # Assume any function that just takes ... passes them on appropriately, 140 | # like BiocGenerics::table() 141 | args_pkg <- names(fn_fmls(pkg_obj)) 142 | if (identical(args_pkg, "...")) 143 | return(TRUE) 144 | 145 | if (is.primitive(base_obj)){ 146 | return(FALSE) 147 | } 148 | 149 | args_base <- names(fn_fmls(base_obj)) 150 | 151 | # To be a superset, all base arguments must be included in the pkg funtion 152 | all(args_base %in% args_pkg) 153 | } 154 | 155 | drop_moved <- function(fun, pkgs) { 156 | is_dep <- vapply(pkgs, has_moved, fun = fun, FUN.VALUE = logical(1)) 157 | pkgs[!is_dep] 158 | } 159 | 160 | 161 | # memoised onLoad 162 | has_moved <- function(pkg, fun, obj = NULL) { 163 | if (is.null(obj)) { 164 | obj <- getExportedValue(pkg, fun) 165 | } 166 | 167 | if (!is.function(obj)) { 168 | return(FALSE) 169 | } 170 | 171 | body <- body(obj) 172 | if (length(body) < 2 || !is_call(body, "{")) 173 | return(FALSE) 174 | 175 | if (!is_call(body[[2]], ".Deprecated")) 176 | return(FALSE) 177 | 178 | if (length(body[[2]]) < 2) 179 | return(FALSE) 180 | 181 | new <- body[[2]][[2]] 182 | if (!is.character(new)) 183 | return(FALSE) 184 | 185 | grepl(paste0("::", fun), new) 186 | } 187 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | pkgs_attached <- function() { 2 | pkgs <- gsub("package:", "", grep("package:", search(), value = TRUE)) 3 | 4 | # Ignore any packages loaded by devtools since these contain 5 | # export all imported functions by default 6 | is_dev <- vapply(pkgs, pkg_devtools, logical(1)) 7 | pkgs[!is_dev] 8 | } 9 | 10 | pkg_attached <- function(x) { 11 | paste0("package:", x) %in% search() 12 | } 13 | 14 | # Use attached package if possible, because that is affected by the 15 | # `exclude` and `include.only` arguments to library(). Otherwise fall back 16 | # to a NAMESPACE based approach 17 | pkg_ls <- function(pkg) { 18 | if (pkg_attached(pkg)) { 19 | ls(pkg_env_name(pkg)) 20 | } else { 21 | ns <- getNamespace(pkg) 22 | exports <- getNamespaceExports(ns) 23 | 24 | names <- intersect(exports, env_names(ns)) 25 | int <- grepl("^.__", names) 26 | c(names[!int], pkg_data(pkg)) 27 | } 28 | } 29 | 30 | pkg_data <- function(x) { 31 | ns <- ns_env(x) 32 | lazy_data <- .getNamespaceInfo(ns, "lazydata") 33 | 34 | if (is.null(lazy_data)) 35 | return(character()) 36 | 37 | env_names(lazy_data) 38 | } 39 | 40 | base_packages <- c( 41 | "base", "datasets", "grDevices", "graphics", "methods", "stats", "utils" 42 | ) 43 | 44 | pkgs_base <- function(x) { 45 | all(x %in% base_packages) 46 | } 47 | 48 | -------------------------------------------------------------------------------- /R/prefer.R: -------------------------------------------------------------------------------- 1 | #' Persistently prefer one function over another 2 | #' 3 | #' @description 4 | #' `conflict_prefer()` allows you to declare "winners" of conflicts. 5 | #' You can either declare a specific pairing (i.e. `dplyr::filter()` beats 6 | #' `base::filter()`), or an overall winner (i.e. `dplyr::filter()` beats 7 | #' all comers). As of conflicted 1.2.0, in most case you should use 8 | #' [conflicts_prefer()] instead as it's both faster and easier to use. 9 | #' 10 | #' Use `conflicted_prefer_all()` to prefer all functions in a package, or 11 | #' `conflicted_prefer_matching()` to prefer functions that match a regular 12 | #' expression. 13 | #' 14 | #' @param name Name of function. 15 | #' @param winner Name of package that should win the conflict. 16 | #' @param losers Optional vector of packages that should lose the conflict. 17 | #' If omitted, `winner` will beat all comers. 18 | #' @param quiet If `TRUE`, all output will be suppressed 19 | #' @export 20 | #' @examples 21 | #' # Prefer over all other packages 22 | #' conflict_prefer("filter", "dplyr") 23 | #' 24 | #' # Prefer over specified package or packages 25 | #' conflict_prefer("filter", "dplyr", "base") 26 | #' conflict_prefer("filter", "dplyr", c("base", "filtration")) 27 | #' 28 | #' # Prefer many functions that match a pattern 29 | #' \dontrun{ 30 | #' # Prefer col_* from vroom 31 | #' conflict_prefer_matching("^col_", "vroom") 32 | #' } 33 | #' # Or all functions from a package: 34 | #' \dontrun{ 35 | #' # Prefer all tidylog functions over dtplyr functions 36 | #' conflict_prefer_all("tidylog", "dtplyr") 37 | #' } 38 | conflict_prefer <- function(name, winner, losers = NULL, quiet = FALSE) { 39 | conflict_preference_register(name, winner, losers = losers, quiet = quiet) 40 | conflicts_register_if_needed(winner) 41 | } 42 | 43 | conflict_preference_register <- function(name, winner, losers = NULL, quiet = FALSE) { 44 | stopifnot(is.character(name), length(name) == 1) 45 | stopifnot(is.character(winner), length(winner) == 1) 46 | stopifnot(is.null(losers) || is.character(losers)) 47 | 48 | if (env_has(prefs, name)) { 49 | if (!quiet) { 50 | cli::cli_inform( 51 | "{label_conflicted()} Removing existing preference." 52 | ) 53 | } 54 | } 55 | 56 | if (!quiet) { 57 | full <- style_object(winner, name, winner = TRUE) 58 | if (is.null(losers)) { 59 | cli::cli_inform( 60 | "{label_conflicted()} Will prefer {full} over any other package." 61 | ) 62 | } else { 63 | alt <- style_object(losers, name) 64 | cli::cli_inform( 65 | "{label_conflicted()} Will prefer {full} over {alt}." 66 | ) 67 | } 68 | } 69 | 70 | env_bind(prefs, !!name := c(winner, losers)) 71 | 72 | invisible() 73 | } 74 | 75 | 76 | #' @export 77 | #' @param pattern Regular expression used to select objects from the `winner` 78 | #' package. 79 | #' @rdname conflict_prefer 80 | conflict_prefer_matching <- function(pattern, winner, losers = NULL, quiet = FALSE) { 81 | names <- grep(pattern, sort(pkg_ls(winner)), value = TRUE) 82 | names <- losers_intersect(names, losers) 83 | 84 | for (name in names) { 85 | conflict_preference_register(name, winner, losers = losers, quiet = quiet) 86 | } 87 | 88 | conflicts_register_if_needed(winner) 89 | } 90 | 91 | #' @export 92 | #' @rdname conflict_prefer 93 | conflict_prefer_all <- function(winner, losers = NULL, quiet = FALSE) { 94 | names <- sort(pkg_ls(winner)) 95 | names <- losers_intersect(names, losers) 96 | 97 | for (name in names) { 98 | conflict_preference_register(name, winner, losers = losers, quiet = quiet) 99 | } 100 | conflicts_register_if_needed(winner) 101 | } 102 | 103 | losers_intersect <- function(names, losers) { 104 | if (is.null(losers)) { 105 | names 106 | } else { 107 | loser_names <- unlist(lapply(losers, pkg_ls)) 108 | names <- intersect(names, loser_names) 109 | } 110 | } 111 | 112 | prefs_resolve <- function(fun, conflicts) { 113 | pkgs <- prefs[[fun]] 114 | 115 | if (length(pkgs) == 1) { 116 | pkgs[[1]] 117 | } else { 118 | c(pkgs[[1]], setdiff(conflicts, pkgs)) 119 | } 120 | } 121 | 122 | # Environment management -------------------------------------------------- 123 | 124 | # contains character vectors - first element gives winner, 125 | # subsequent elements (if present) gives losers 126 | prefs <- env() 127 | 128 | prefs_ls <- function() { 129 | env_names(prefs) 130 | } 131 | 132 | prefs_reset <- function() { 133 | env_unbind(prefs, env_names(prefs)) 134 | } 135 | -------------------------------------------------------------------------------- /R/shim.R: -------------------------------------------------------------------------------- 1 | shims_bind <- function(env = caller_env()) { 2 | if (getRversion() >= "3.6.0") { 3 | env_bind(env, 4 | library = shim_library_3_6, 5 | require = shim_require_3_6 6 | ) 7 | } else { 8 | env_bind(env, 9 | library = shim_library_3_1, 10 | require = shim_require_3_1 11 | ) 12 | } 13 | 14 | } 15 | 16 | # library ----------------------------------------------------------------- 17 | 18 | shim_library_3_1 <- function(package, 19 | help, 20 | pos = 2, 21 | lib.loc = NULL, 22 | character.only = FALSE, 23 | logical.return = FALSE, 24 | warn.conflicts = TRUE, 25 | quietly = FALSE, 26 | verbose = getOption("verbose") 27 | ) { 28 | 29 | if (!missing(package)) { 30 | package <- package_name(enquo(package), character.only = character.only) 31 | 32 | conflicts_reset() 33 | on.exit(conflicts_register()) 34 | on_detach(package, function() conflicts_remove(package)) 35 | 36 | library( 37 | package, 38 | pos = pos, 39 | lib.loc = lib.loc, 40 | character.only = TRUE, 41 | logical.return = logical.return, 42 | warn.conflicts = FALSE, 43 | quietly = quietly, 44 | verbose = verbose 45 | ) 46 | } else if (!missing(help)) { 47 | help <- package_name(enquo(help), character.only = character.only) 48 | library( 49 | help = help, 50 | character.only = TRUE 51 | ) 52 | } else { 53 | library( 54 | lib.loc = lib.loc, 55 | logical.return = logical.return 56 | ) 57 | } 58 | } 59 | 60 | if (getRversion() >= "3.6.0") { 61 | shim_library_3_6 <- function(package, 62 | help, 63 | pos = 2, 64 | lib.loc = NULL, 65 | character.only = FALSE, 66 | logical.return = FALSE, 67 | warn.conflicts, 68 | quietly = FALSE, 69 | verbose = getOption("verbose"), 70 | mask.ok, 71 | exclude, 72 | include.only, 73 | attach.required = missing(include.only) 74 | ) { 75 | 76 | if (!missing(package)) { 77 | package <- package_name(enquo(package), character.only = character.only) 78 | 79 | conflicts_reset() 80 | on.exit(conflicts_register()) 81 | on_detach(package, function() conflicts_remove(package)) 82 | 83 | library( 84 | package, 85 | pos = pos, 86 | lib.loc = lib.loc, 87 | character.only = TRUE, 88 | logical.return = logical.return, 89 | warn.conflicts = FALSE, 90 | quietly = quietly, 91 | verbose = verbose, 92 | mask.ok = mask.ok, 93 | exclude = exclude, 94 | include.only = include.only, 95 | attach.required = attach.required 96 | ) 97 | } else if (!missing(help)) { 98 | help <- package_name(enquo(help), character.only = character.only) 99 | library( 100 | help = help, 101 | character.only = TRUE 102 | ) 103 | } else { 104 | library( 105 | lib.loc = lib.loc, 106 | logical.return = logical.return 107 | ) 108 | } 109 | } 110 | } else { 111 | shim_library_3_6 <- function(...) {} 112 | } 113 | 114 | # require ----------------------------------------------------------------- 115 | 116 | shim_require_3_1 <- function(package, 117 | lib.loc = NULL, 118 | quietly = FALSE, 119 | warn.conflicts = TRUE, 120 | character.only = FALSE) { 121 | 122 | package <- package_name(enquo(package), character.only = character.only) 123 | 124 | conflicts_reset() 125 | on.exit(conflicts_register()) 126 | on_detach(package, function() conflicts_remove(package)) 127 | 128 | require( 129 | package, 130 | lib.loc = lib.loc, 131 | quietly = quietly, 132 | warn.conflicts = FALSE, 133 | character.only = TRUE 134 | ) 135 | } 136 | 137 | if (getRversion() >= "3.6.0") { 138 | shim_require_3_6 <- function(package, 139 | lib.loc = NULL, 140 | quietly = FALSE, 141 | warn.conflicts, 142 | character.only = FALSE, 143 | mask.ok, 144 | exclude, 145 | include.only, 146 | attach.required = missing(include.only) 147 | ) { 148 | 149 | package <- package_name(enquo(package), character.only = character.only) 150 | 151 | conflicts_reset() 152 | on.exit(conflicts_register()) 153 | on_detach(package, function() conflicts_remove(package)) 154 | 155 | require( 156 | package, 157 | lib.loc = lib.loc, 158 | quietly = quietly, 159 | warn.conflicts = FALSE, 160 | character.only = TRUE, 161 | mask.ok = mask.ok, 162 | exclude = exclude, 163 | include.only = include.only, 164 | attach.required = attach.required 165 | ) 166 | } 167 | } else { 168 | shim_require_3_6 <- function(...) {} 169 | } 170 | 171 | # Helpers ----------------------------------------------------------------- 172 | 173 | package_name <- function(package, character.only = FALSE) { 174 | if (!character.only) { 175 | package <- as.character(quo_squash(package)) 176 | } else { 177 | package <- eval_tidy(package) 178 | } 179 | 180 | if (!is.character(package) || length(package) != 1L) { 181 | cli::cli_abort("{.arg package} must be character vector of length 1.") 182 | } 183 | if (is.na(package) || (package == "")) { 184 | cli::cli_abort("{.arg package} must not be NA or ''.") 185 | } 186 | 187 | package 188 | } 189 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | invert <- function(x) { 2 | if (length(x) == 0) return() 3 | stacked <- utils::stack(x) 4 | tapply(as.character(stacked$ind), stacked$values, list) 5 | } 6 | 7 | pkg_devtools <- function(name) { 8 | ns <- .getNamespace(name) 9 | if (is.null(ns)) { 10 | return(FALSE) 11 | } 12 | 13 | !is.null(ns$.__DEVTOOLS__) 14 | } 15 | 16 | on_detach <- function(pkg, fun) { 17 | force(fun) 18 | 19 | done <- FALSE 20 | call_once <- function(...) { 21 | if (done) return() 22 | done <<- TRUE 23 | fun() 24 | } 25 | 26 | setHook(packageEvent(pkg, "detach"), call_once) 27 | } 28 | 29 | map2 <- function(.x, .y, .f, ...) { 30 | mapply(.f, .x, .y, MoreArgs = list(...), SIMPLIFY = FALSE) 31 | } 32 | 33 | map_chr <- function(.x, .f, ...) { 34 | vapply(.x, .f, ..., FUN.VALUE = character(1)) 35 | } 36 | 37 | imap_chr <- function(.x, .f, ...) { 38 | out <- character(length(.x)) 39 | for (i in seq_along(.x)) { 40 | out[[i]] <- .f(.x[[i]], names(.x)[[i]], ...) 41 | } 42 | 43 | set_names(out, names(.x)) 44 | } 45 | 46 | unique_obj <- function(name, pkgs) { 47 | objs <- lapply(pkgs, getExportedValue, name) 48 | names(objs) <- pkgs 49 | 50 | canonical <- canonical_objs(objs, name) 51 | 52 | canonical_objs <- c(canonical, objs) 53 | canonical_pkgs <- c(names(canonical), pkgs) 54 | 55 | canonical_pkgs[!duplicated(canonical_objs)] 56 | } 57 | 58 | canonical_objs <- function(objs, name) { 59 | seen <- list() 60 | 61 | # Finding the namespace where a function is really defined 62 | env_names <- imap_chr(objs, function(obj, pkg) { 63 | canonical_obj <- tryCatch( 64 | { 65 | canonical_pkg <- getNamespaceName(environment(obj)) 66 | # Double-check that this is actually the correct object, 67 | # e.g., devtools does interesting things here 68 | canonical_obj <- getExportedValue(canonical_pkg, name) 69 | set_names(list(canonical_obj), canonical_pkg) 70 | }, 71 | error = function(e) NULL 72 | ) 73 | 74 | 75 | # Error getting name or exported value? 76 | if (is.null(canonical_obj)) { 77 | return("") 78 | } 79 | 80 | # Roundtrip failed? 81 | if (!identical(unname(canonical_obj)[[1]], obj)) { 82 | return("") 83 | } 84 | 85 | # Happy path: canonical package is attached? 86 | canonical <- names(canonical_obj) 87 | if (canonical %in% names(objs)) { 88 | return(canonical) 89 | } 90 | 91 | # More work needed: we pick the first package 92 | if (canonical %in% names(seen)) { 93 | # Second pass, we had found a package with that function before 94 | return(seen[[canonical]]) 95 | } 96 | 97 | # We are first, recording 98 | seen[[canonical]] <<- pkg 99 | pkg 100 | }) 101 | 102 | canonical_names <- unique(env_names[env_names != ""]) 103 | canonical_pos <- set_names(match(canonical_names, env_names), canonical_names) 104 | 105 | set_names(objs[canonical_pos], canonical_names) 106 | } 107 | 108 | style_object <- function(pkg, name, winner = FALSE) { 109 | paste0( 110 | if (winner) cli::style_bold(cli::col_blue(pkg)) else cli::col_blue(pkg), 111 | "::", 112 | backtick(name) 113 | ) 114 | } 115 | 116 | label_conflicted <- function() { 117 | cli::col_grey("[conflicted]") 118 | } 119 | 120 | backtick <- function(x) { 121 | ifelse(x == make.names(x), x, paste0("`", x, "`")) 122 | } 123 | 124 | 125 | compact <- function(x) { 126 | empty <- vapply(x, is_empty, logical(1)) 127 | x[!empty] 128 | } 129 | 130 | cat_line <- function(...) { 131 | cat(paste0(..., "\n", collapse = "")) 132 | } 133 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(...) { 2 | has_moved <<- memoise::memoise(has_moved) 3 | } 4 | 5 | .onAttach <- function(...) { 6 | conflicts_register() 7 | } 8 | 9 | # This should really be done on .onDetach(), but because it's called inside of 10 | # detach() it messes up the computation of `pos` inside of `detach()` and it 11 | # detaches the wrong environment 12 | .onUnload <- function(...) { 13 | conflicts_reset() 14 | } 15 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, echo = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "README-" 12 | ) 13 | set.seed(1014) 14 | ``` 15 | 16 | # conflicted 17 | 18 | 19 | [![R-CMD-check](https://github.com/r-lib/conflicted/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/conflicted/actions/workflows/R-CMD-check.yaml) 20 | [![Codecov test coverage](https://codecov.io/gh/r-lib/conflicted/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/conflicted?branch=main) 21 | 22 | 23 | The goal of conflicted is to provide an alternative conflict resolution strategy. R's default conflict resolution system gives precedence to the most recently loaded package. This can make it hard to detect conflicts, particularly when introduced by an update to an existing package. conflicted takes a different approach, making every conflict an error and forcing you to choose which function to use. 24 | 25 | Thanks to [\@krlmlr](https://github.com/krlmlr) for this neat idea! This code was previously part of the experimental [strict](https://github.com/hadley/strict) package, but I decided improved conflict resolution is useful by itself and worth its own package. 26 | 27 | ## Installation 28 | 29 | ```{r, eval = FALSE} 30 | # install.packages("pak") 31 | pak::pak("r-lib/conflicted") 32 | ``` 33 | 34 | ## Usage 35 | 36 | To use conflicted, all you need to do is load it: 37 | 38 | ```{r, error = TRUE} 39 | library(conflicted) 40 | library(dplyr) 41 | 42 | filter(mtcars, cyl == 8) 43 | ``` 44 | 45 | As suggested, you can either namespace individual calls: 46 | 47 | ```{r} 48 | dplyr::filter(mtcars, am & cyl == 8) 49 | ``` 50 | 51 | Or declare a session-wide preference: 52 | 53 | ```{r} 54 | conflicts_prefer(dplyr::filter()) 55 | filter(mtcars, am & cyl == 8) 56 | ``` 57 | 58 | I recommend declaring preferences directly underneath the corresponding library call: 59 | 60 | ```{r, eval = FALSE} 61 | library(dplyr) 62 | conflicts_prefer(dplyr::filter) 63 | ``` 64 | 65 | You can ask conflicted to report any conflicts in the current session: 66 | 67 | ```{r} 68 | conflict_scout() 69 | ``` 70 | 71 | Functions surrounded by `[]` have been chosen using one of the built-in rules. Here `filter()` has been selected because of the preference declared above; the set operations have been selected because they follow the superset principle and extend the API of the base equivalents. 72 | 73 | ### How it works 74 | 75 | Loading conflicted creates a new "conflicted" environment that is attached just after the global environment. This environment contains an active binding for any object that is exported by multiple packages; the active binding will throw an error message describing how to disambiguate the name. The conflicted environment also contains bindings for `library()` and `require()` that suppress conflict reporting and update the conflicted environment with any new conflicts. 76 | 77 | ## Alternative approaches 78 | 79 | It is worth comparing conflicted to [box](https://github.com/klmr/box) and [import](https://github.com/rticulate/import). Both packages provide strict alternatives to `library()`, giving much finer control over what functions are added to the search path. 80 | 81 | ```{r, eval = FALSE} 82 | # box expects you to either namespace all package functions or to load them explicitly 83 | box::use(dplyr) 84 | dplyr$filter(mtcars, cyl == 8) 85 | # or: 86 | box::use(dplyr[select, arrange, dplyr_filter = filter]) 87 | dplyr_filter(mtcars, cyl == 8) 88 | 89 | # import expects you to explicitly load functions 90 | import::from(dplyr, select, arrange, dplyr_filter = filter) 91 | dplyr_filter(mtcars, cyl == 8) 92 | ``` 93 | 94 | These require more upfront work than conflicted, in return for greater precision and control. 95 | 96 | Since conflicted was created base R also improved its tools for managing search path conflicts. See [the blog post](https://developer.r-project.org/Blog/public/2019/03/19/managing-search-path-conflicts/) by Luke Tierney for details. The main difference is that base R requires up front conflict resolution of all functions when loading a package; conflicted only reports problems as you use conflicted functions. 97 | 98 | ## Code of Conduct 99 | 100 | Please note that the conflicted project is released with a [Contributor Code of Conduct](https://conflicted.r-lib.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # conflicted 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/r-lib/conflicted/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/conflicted/actions/workflows/R-CMD-check.yaml) 9 | [![Codecov test 10 | coverage](https://codecov.io/gh/r-lib/conflicted/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/conflicted?branch=main) 11 | 12 | 13 | The goal of conflicted is to provide an alternative conflict resolution 14 | strategy. R’s default conflict resolution system gives precedence to the 15 | most recently loaded package. This can make it hard to detect conflicts, 16 | particularly when introduced by an update to an existing package. 17 | conflicted takes a different approach, making every conflict an error 18 | and forcing you to choose which function to use. 19 | 20 | Thanks to [@krlmlr](https://github.com/krlmlr) for this neat idea! This 21 | code was previously part of the experimental 22 | [strict](https://github.com/hadley/strict) package, but I decided 23 | improved conflict resolution is useful by itself and worth its own 24 | package. 25 | 26 | ## Installation 27 | 28 | ``` r 29 | # install.packages("pak") 30 | pak::pak("r-lib/conflicted") 31 | ``` 32 | 33 | ## Usage 34 | 35 | To use conflicted, all you need to do is load it: 36 | 37 | ``` r 38 | library(conflicted) 39 | library(dplyr) 40 | 41 | filter(mtcars, cyl == 8) 42 | #> Error: 43 | #> ! [conflicted] filter found in 2 packages. 44 | #> Either pick the one you want with `::`: 45 | #> • dplyr::filter 46 | #> • stats::filter 47 | #> Or declare a preference with `conflicts_prefer()`: 48 | #> • `conflicts_prefer(dplyr::filter)` 49 | #> • `conflicts_prefer(stats::filter)` 50 | ``` 51 | 52 | As suggested, you can either namespace individual calls: 53 | 54 | ``` r 55 | dplyr::filter(mtcars, am & cyl == 8) 56 | #> mpg cyl disp hp drat wt qsec vs am gear carb 57 | #> Ford Pantera L 15.8 8 351 264 4.22 3.17 14.5 0 1 5 4 58 | #> Maserati Bora 15.0 8 301 335 3.54 3.57 14.6 0 1 5 8 59 | ``` 60 | 61 | Or declare a session-wide preference: 62 | 63 | ``` r 64 | conflicts_prefer(dplyr::filter()) 65 | #> [conflicted] Will prefer dplyr::filter over any other package. 66 | filter(mtcars, am & cyl == 8) 67 | #> mpg cyl disp hp drat wt qsec vs am gear carb 68 | #> Ford Pantera L 15.8 8 351 264 4.22 3.17 14.5 0 1 5 4 69 | #> Maserati Bora 15.0 8 301 335 3.54 3.57 14.6 0 1 5 8 70 | ``` 71 | 72 | I recommend declaring preferences directly underneath the corresponding 73 | library call: 74 | 75 | ``` r 76 | library(dplyr) 77 | conflicts_prefer(dplyr::filter) 78 | ``` 79 | 80 | You can ask conflicted to report any conflicts in the current session: 81 | 82 | ``` r 83 | conflict_scout() 84 | #> 1 conflict 85 | #> • `lag()`: dplyr and stats 86 | ``` 87 | 88 | Functions surrounded by `[]` have been chosen using one of the built-in 89 | rules. Here `filter()` has been selected because of the preference 90 | declared above; the set operations have been selected because they 91 | follow the superset principle and extend the API of the base 92 | equivalents. 93 | 94 | ### How it works 95 | 96 | Loading conflicted creates a new “conflicted” environment that is 97 | attached just after the global environment. This environment contains an 98 | active binding for any object that is exported by multiple packages; the 99 | active binding will throw an error message describing how to 100 | disambiguate the name. The conflicted environment also contains bindings 101 | for `library()` and `require()` that suppress conflict reporting and 102 | update the conflicted environment with any new conflicts. 103 | 104 | ## Alternative approaches 105 | 106 | It is worth comparing conflicted to [box](https://github.com/klmr/box) 107 | and [import](https://github.com/rticulate/import). Both packages provide 108 | strict alternatives to `library()`, giving much finer control over what 109 | functions are added to the search path. 110 | 111 | ``` r 112 | # box expects you to either namespace all package functions or to load them explicitly 113 | box::use(dplyr) 114 | dplyr$filter(mtcars, cyl == 8) 115 | # or: 116 | box::use(dplyr[select, arrange, dplyr_filter = filter]) 117 | dplyr_filter(mtcars, cyl == 8) 118 | 119 | # import expects you to explicitly load functions 120 | import::from(dplyr, select, arrange, dplyr_filter = filter) 121 | dplyr_filter(mtcars, cyl == 8) 122 | ``` 123 | 124 | These require more upfront work than conflicted, in return for greater 125 | precision and control. 126 | 127 | Since conflicted was created base R also improved its tools for managing 128 | search path conflicts. See [the blog 129 | post](https://developer.r-project.org/Blog/public/2019/03/19/managing-search-path-conflicts/) 130 | by Luke Tierney for details. The main difference is that base R requires 131 | up front conflict resolution of all functions when loading a package; 132 | conflicted only reports problems as you use conflicted functions. 133 | 134 | ## Code of Conduct 135 | 136 | Please note that the conflicted project is released with a [Contributor 137 | Code of Conduct](https://conflicted.r-lib.org/CODE_OF_CONDUCT.html). By 138 | contributing to this project, you agree to abide by its terms. 139 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://conflicted.r-lib.org 2 | 3 | template: 4 | package: tidytemplate 5 | bootstrap: 5 6 | 7 | includes: 8 | in_header: | 9 | 10 | 11 | development: 12 | mode: auto 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /conflicted.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 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 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 note 4 | 5 | ## revdepcheck results 6 | 7 | We checked 11 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. 8 | 9 | * We saw 0 new problems 10 | * We failed to check 0 packages 11 | 12 | -------------------------------------------------------------------------------- /man/conflict_prefer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/prefer.R 3 | \name{conflict_prefer} 4 | \alias{conflict_prefer} 5 | \alias{conflict_prefer_matching} 6 | \alias{conflict_prefer_all} 7 | \title{Persistently prefer one function over another} 8 | \usage{ 9 | conflict_prefer(name, winner, losers = NULL, quiet = FALSE) 10 | 11 | conflict_prefer_matching(pattern, winner, losers = NULL, quiet = FALSE) 12 | 13 | conflict_prefer_all(winner, losers = NULL, quiet = FALSE) 14 | } 15 | \arguments{ 16 | \item{name}{Name of function.} 17 | 18 | \item{winner}{Name of package that should win the conflict.} 19 | 20 | \item{losers}{Optional vector of packages that should lose the conflict. 21 | If omitted, \code{winner} will beat all comers.} 22 | 23 | \item{quiet}{If \code{TRUE}, all output will be suppressed} 24 | 25 | \item{pattern}{Regular expression used to select objects from the \code{winner} 26 | package.} 27 | } 28 | \description{ 29 | \code{conflict_prefer()} allows you to declare "winners" of conflicts. 30 | You can either declare a specific pairing (i.e. \code{dplyr::filter()} beats 31 | \code{base::filter()}), or an overall winner (i.e. \code{dplyr::filter()} beats 32 | all comers). As of conflicted 1.2.0, in most case you should use 33 | \code{\link[=conflicts_prefer]{conflicts_prefer()}} instead as it's both faster and easier to use. 34 | 35 | Use \code{conflicted_prefer_all()} to prefer all functions in a package, or 36 | \code{conflicted_prefer_matching()} to prefer functions that match a regular 37 | expression. 38 | } 39 | \examples{ 40 | # Prefer over all other packages 41 | conflict_prefer("filter", "dplyr") 42 | 43 | # Prefer over specified package or packages 44 | conflict_prefer("filter", "dplyr", "base") 45 | conflict_prefer("filter", "dplyr", c("base", "filtration")) 46 | 47 | # Prefer many functions that match a pattern 48 | \dontrun{ 49 | # Prefer col_* from vroom 50 | conflict_prefer_matching("^col_", "vroom") 51 | } 52 | # Or all functions from a package: 53 | \dontrun{ 54 | # Prefer all tidylog functions over dtplyr functions 55 | conflict_prefer_all("tidylog", "dtplyr") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /man/conflict_scout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/find.R 3 | \name{conflict_scout} 4 | \alias{conflict_scout} 5 | \title{Find conflicts amongst a set of packages} 6 | \usage{ 7 | conflict_scout(pkgs = NULL) 8 | } 9 | \arguments{ 10 | \item{pkgs}{Set of packages for which to report conflicts. If \code{NULL}, 11 | the default, will report conflicts for all loaded packages} 12 | } 13 | \value{ 14 | A named list of character vectors. The names are functions and 15 | the values are the packages where they appear. Disambiguated functions 16 | are removed from that list. 17 | 18 | A user friendly print method displays the result as bulleted list. 19 | } 20 | \description{ 21 | \code{conflict_scout()} is the workhorse behind the conflicted package. You can 22 | call it directly yourself if you want to see all conflicts before hitting 23 | them in practice. 24 | } 25 | \details{ 26 | For a reexported function, this function will report the package where the 27 | function is defined. If this package is not attached, it will report 28 | the first such package on the search path or in the \code{pkgs} argument. 29 | } 30 | \examples{ 31 | conflict_scout() 32 | } 33 | -------------------------------------------------------------------------------- /man/conflicted-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/conflicted.R 3 | \docType{package} 4 | \name{conflicted-package} 5 | \alias{conflicted} 6 | \alias{conflicted-package} 7 | \title{conflicted: An Alternative Conflict Resolution Strategy} 8 | \description{ 9 | R's default conflict management system gives the most recently loaded package precedence. This can make it hard to detect conflicts, particularly when they arise because a package update creates ambiguity that did not previously exist. 'conflicted' takes a different approach, making every conflict an error and forcing you to choose which function to use. 10 | } 11 | \section{Resolving conflicts}{ 12 | 13 | 14 | To permanently resolve a conflict within a session, use assignment: 15 | 16 | \preformatted{ 17 | library(conflicted) 18 | library(dplyr) 19 | 20 | filter <- dplyr::filter 21 | } 22 | } 23 | 24 | \seealso{ 25 | Useful links: 26 | \itemize{ 27 | \item \url{https://conflicted.r-lib.org/} 28 | \item \url{https://github.com/r-lib/conflicted} 29 | \item Report bugs at \url{https://github.com/r-lib/conflicted/issues} 30 | } 31 | 32 | } 33 | \author{ 34 | \strong{Maintainer}: Hadley Wickham \email{hadley@posit.co} 35 | 36 | Other contributors: 37 | \itemize{ 38 | \item Posit Software, PBC [copyright holder, funder] 39 | } 40 | 41 | } 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /man/conflicts_prefer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/favor.R 3 | \name{conflicts_prefer} 4 | \alias{conflicts_prefer} 5 | \title{Declare many preferences at once} 6 | \usage{ 7 | conflicts_prefer(..., .quiet = FALSE) 8 | } 9 | \arguments{ 10 | \item{...}{Functions to prefer in form \code{pkg::fun} or \code{pkg::fun()}.} 11 | 12 | \item{.quiet}{If \code{TRUE}, all output will be suppressed} 13 | } 14 | \description{ 15 | \code{conflicts_prefer()} allows you to declare "winners" of conflicts, 16 | declaring one or many winners at once. 17 | 18 | See \code{\link[=conflict_prefer]{conflict_prefer()}} for more precise control. 19 | } 20 | \section{Best practices}{ 21 | 22 | I recommend placing a single call to \code{conflicts_prefer()} at the top of 23 | your script, immediately after loading all needed packages with calls to 24 | \code{library()}. 25 | } 26 | 27 | \examples{ 28 | conflicts_prefer( 29 | dplyr::filter(), 30 | dplyr::lag(), 31 | ) 32 | 33 | # or 34 | conflicts_prefer( 35 | dplyr::filter, 36 | dplyr::lag, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | data.sqlite 6 | *.html 7 | cloud.noindex 8 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Revdeps 2 | 3 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 15 reverse dependencies, 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 | -------------------------------------------------------------------------------- /revdep/email.yml: -------------------------------------------------------------------------------- 1 | release_date: ??? 2 | rel_release_date: ??? 3 | my_news_url: ??? 4 | release_version: ??? 5 | release_details: ??? 6 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(conflicted) 3 | 4 | test_check("conflicted") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/disambiguate.md: -------------------------------------------------------------------------------- 1 | # error message is informative 2 | 3 | Code 4 | disambiguate_prefix("x", c("a", "b", "c"))() 5 | Condition 6 | Error: 7 | ! [conflicted] x found in 3 packages. 8 | Either pick the one you want with `::`: 9 | * a::x 10 | * b::x 11 | * c::x 12 | Or declare a preference with `conflicts_prefer()`: 13 | * `conflicts_prefer(a::x)` 14 | * `conflicts_prefer(b::x)` 15 | * `conflicts_prefer(c::x)` 16 | Code 17 | disambiguate_prefix("if", c("a", "b", "c"))() 18 | Condition 19 | Error: 20 | ! [conflicted] if found in 3 packages. 21 | Either pick the one you want with `::`: 22 | * a::`if` 23 | * b::`if` 24 | * c::`if` 25 | Or declare a preference with `conflicts_prefer()`: 26 | * `` conflicts_prefer(a::`if`) `` 27 | * `` conflicts_prefer(b::`if`) `` 28 | * `` conflicts_prefer(c::`if`) `` 29 | Code 30 | disambiguate_infix("%in%", c("a", "b", "c"))() 31 | Condition 32 | Error: 33 | ! [conflicted] %in% found in 3 packages. 34 | Declare a preference with `conflicts_prefer()`: 35 | * `` conflicts_prefer(a::`%in%`) `` 36 | * `` conflicts_prefer(b::`%in%`) `` 37 | * `` conflicts_prefer(c::`%in%`) `` 38 | 39 | # display namespace if not attached 40 | 41 | Code 42 | cnds$prefix 43 | Output 44 | 45 | Error: 46 | ! [conflicted] x found in 3 packages. 47 | Either pick the one you want with `::`: 48 | * a::x 49 | * b::x 50 | * c::x 51 | Or declare a preference with `conflicted::conflicts_prefer()`: 52 | * `conflicted::conflicts_prefer(a::x)` 53 | * `conflicted::conflicts_prefer(b::x)` 54 | * `conflicted::conflicts_prefer(c::x)` 55 | Code 56 | cnds$infix 57 | Output 58 | 59 | Error: 60 | ! [conflicted] %in% found in 3 packages. 61 | Declare a preference with `conflicted::conflicts_prefer()`: 62 | * `` conflicted::conflicts_prefer(a::`%in%`) `` 63 | * `` conflicted::conflicts_prefer(b::`%in%`) `` 64 | * `` conflicted::conflicts_prefer(c::`%in%`) `` 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/favor.md: -------------------------------------------------------------------------------- 1 | # trailing () is optional 2 | 3 | Code 4 | conflicts_prefer(dplyr::lag, dplyr::filter()) 5 | Message 6 | [conflicted] Will prefer dplyr::lag over any other package. 7 | [conflicted] Will prefer dplyr::filter over any other package. 8 | 9 | # errors if invalid form 10 | 11 | Code 12 | conflicts_prefer(1) 13 | Condition 14 | Error in `conflicts_prefer()`: 15 | ! All arguments must be in form `pkg::fun` or `pkg::fun()`. 16 | Code 17 | conflicts_prefer(foo()) 18 | Condition 19 | Error in `conflicts_prefer()`: 20 | ! All arguments must be in form `pkg::fun` or `pkg::fun()`. 21 | Code 22 | conflicts_prefer(dplyr::filter(a = 1)) 23 | Condition 24 | Error in `conflicts_prefer()`: 25 | ! All arguments must be in form `pkg::fun` or `pkg::fun()`. 26 | 27 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/find.md: -------------------------------------------------------------------------------- 1 | # conflict_scout prints usefully 2 | 3 | Code 4 | conflict_scout("dplyr") 5 | Message 6 | 0 conflicts 7 | Code 8 | conflict_scout(c("rlang", "pkgload")) 9 | Message 10 | 2 conflicts 11 | * `ns_env()`: rlang and pkgload 12 | * `pkg_env()`: rlang and pkgload 13 | 14 | # preferences are obeyed 15 | 16 | Code 17 | conflict_scout(c("rlang", "prefs")) 18 | Message 19 | 1 conflict 20 | * `set_names()`: rlang and prefs 21 | Code 22 | conflicts_prefer(rlang::set_names()) 23 | Message 24 | [conflicted] Will prefer rlang::set_names over any other package. 25 | Code 26 | conflict_scout(c("rlang", "prefs")) 27 | Message 28 | 0 conflicts 29 | 30 | # using canonical reference 31 | 32 | Code 33 | conflict_scout(c("testthat", "dplyr", "pipe")) 34 | Message 35 | 1 conflict 36 | * `%>%()`: testthat and pipe 37 | 38 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/prefer.md: -------------------------------------------------------------------------------- 1 | # useful messages for specific preferences 2 | 3 | Code 4 | conflict_prefer("mean", "canoodle", c("noodle", "doodle")) 5 | Message 6 | [conflicted] Will prefer canoodle::mean over noodle::mean and doodle::mean. 7 | Code 8 | conflict_prefer("mean", "canoodle", "boodle") 9 | Message 10 | [conflicted] Removing existing preference. 11 | [conflicted] Will prefer canoodle::mean over boodle::mean. 12 | Code 13 | conflict_prefer("+", "canoodle") 14 | Message 15 | [conflicted] Will prefer canoodle::`+` over any other package. 16 | 17 | # can register preference for multiple functions 18 | 19 | Code 20 | conflict_prefer_all("funmatch") 21 | Message 22 | [conflicted] Will prefer funmatch::median over any other package. 23 | [conflicted] Will prefer funmatch::pi over any other package. 24 | 25 | --- 26 | 27 | Code 28 | conflict_prefer_all("funmatch", "base") 29 | Message 30 | [conflicted] Will prefer funmatch::pi over base::pi. 31 | 32 | --- 33 | 34 | Code 35 | conflict_prefer_matching("m", "funmatch") 36 | Message 37 | [conflicted] Will prefer funmatch::median over any other package. 38 | 39 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/shim.md: -------------------------------------------------------------------------------- 1 | # package_name throws errors with invalid names 2 | 3 | Code 4 | package_name_(c("x", "y")) 5 | Condition 6 | Error in `package_name()`: 7 | ! `package` must be character vector of length 1. 8 | Code 9 | package_name_(1:10) 10 | Condition 11 | Error in `package_name()`: 12 | ! `package` must be character vector of length 1. 13 | Code 14 | package_name_(NA_character_) 15 | Condition 16 | Error in `package_name()`: 17 | ! `package` must not be NA or ''. 18 | Code 19 | package_name_("") 20 | Condition 21 | Error in `package_name()`: 22 | ! `package` must not be NA or ''. 23 | 24 | -------------------------------------------------------------------------------- /tests/testthat/data/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: data 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: What the package does (one paragraph). 9 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 10 | license 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.1.2 14 | Depends: 15 | R (>= 2.10) 16 | LazyData: true 17 | -------------------------------------------------------------------------------- /tests/testthat/data/NAMESPACE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/conflicted/4d759ac62d49ec77cb3d16db41e4c40ffa4cfa1e/tests/testthat/data/NAMESPACE -------------------------------------------------------------------------------- /tests/testthat/data/data/mtcars.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/conflicted/4d759ac62d49ec77cb3d16db41e4c40ffa4cfa1e/tests/testthat/data/data/mtcars.rda -------------------------------------------------------------------------------- /tests/testthat/funmatch/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: funmatch 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: What the package does (one paragraph). 9 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 10 | license 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.1.2 14 | -------------------------------------------------------------------------------- /tests/testthat/funmatch/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(pi) 2 | export(median) 3 | -------------------------------------------------------------------------------- /tests/testthat/funmatch/R/test.R: -------------------------------------------------------------------------------- 1 | median <- 10 2 | 3 | pi <- function() 3 4 | -------------------------------------------------------------------------------- /tests/testthat/pipe/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pipe 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: What the package does (one paragraph). 9 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 10 | license 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.1.2 14 | -------------------------------------------------------------------------------- /tests/testthat/pipe/NAMESPACE: -------------------------------------------------------------------------------- 1 | export("%>%") 2 | -------------------------------------------------------------------------------- /tests/testthat/pipe/R/test.R: -------------------------------------------------------------------------------- 1 | `%>%` <- function(...) {} 2 | -------------------------------------------------------------------------------- /tests/testthat/prefs/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: prefs 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: What the package does (one paragraph). 9 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 10 | license 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.1.2 14 | -------------------------------------------------------------------------------- /tests/testthat/prefs/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(set_names) 2 | -------------------------------------------------------------------------------- /tests/testthat/prefs/R/test.R: -------------------------------------------------------------------------------- 1 | set_names <- function() {} 2 | -------------------------------------------------------------------------------- /tests/testthat/primitive/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: primitive 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: What the package does (one paragraph). 9 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 10 | license 11 | Encoding: UTF-8 12 | Roxygen: list(markdown = TRUE) 13 | RoxygenNote: 7.1.2 14 | -------------------------------------------------------------------------------- /tests/testthat/primitive/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(sum) 2 | -------------------------------------------------------------------------------- /tests/testthat/primitive/R/sum.R: -------------------------------------------------------------------------------- 1 | sum <- function(x) 10 2 | -------------------------------------------------------------------------------- /tests/testthat/test-disambiguate.R: -------------------------------------------------------------------------------- 1 | test_that("error message is informative", { 2 | expect_snapshot(error = TRUE, { 3 | disambiguate_prefix("x", c("a", "b", "c"))() 4 | disambiguate_prefix("if", c("a", "b", "c"))() 5 | disambiguate_infix("%in%", c("a", "b", "c"))() 6 | }) 7 | }) 8 | 9 | test_that("display namespace if not attached", { 10 | cnds <- callr::r(function(is_devel) { 11 | if (is_devel) { 12 | pkgload::load_all(attach = FALSE) 13 | } 14 | list( 15 | prefix = rlang::catch_cnd( 16 | conflicted:::disambiguate_prefix("x", c("a", "b", "c"))() 17 | ), 18 | infix = rlang::catch_cnd( 19 | conflicted:::disambiguate_infix("%in%", c("a", "b", "c"))() 20 | ) 21 | ) 22 | }, list(is_devel = pkgload::is_dev_package("conflicted"))) 23 | 24 | expect_snapshot({ 25 | cnds$prefix 26 | cnds$infix 27 | }) 28 | }) 29 | 30 | 31 | test_that("can save active binding without error", { 32 | env <- env( 33 | a = disambiguate_prefix("x", c("a", "b", "c")), 34 | b = disambiguate_infix("y", c("a", "b", "c")) 35 | ) 36 | 37 | expect_error(save(a, envir = env, file = tempfile()), NA) 38 | expect_error(save(b, envir = env, file = tempfile()), NA) 39 | }) 40 | -------------------------------------------------------------------------------- /tests/testthat/test-favor.R: -------------------------------------------------------------------------------- 1 | test_that("trailing () is optional", { 2 | withr::defer(prefs_reset()) 3 | 4 | expect_snapshot({ 5 | conflicts_prefer( 6 | dplyr::lag, 7 | dplyr::filter() 8 | ) 9 | }) 10 | }) 11 | 12 | test_that("errors if invalid form", { 13 | expect_snapshot(error = TRUE, { 14 | conflicts_prefer(1) 15 | conflicts_prefer(foo()) 16 | conflicts_prefer(dplyr::filter(a = 1)) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-find.R: -------------------------------------------------------------------------------- 1 | test_that("conflict_scout prints usefully", { 2 | expect_snapshot({ 3 | conflict_scout("dplyr") 4 | conflict_scout(c("rlang", "pkgload")) 5 | }) 6 | }) 7 | 8 | test_that("primitive functions are never supersets", { 9 | pkgload::load_all(test_path("primitive"), quiet = TRUE) 10 | on.exit(pkgload::unload("primitive")) 11 | 12 | expect_false(is_superset("sum", "primitive", "base")) 13 | expect_equal( 14 | superset_principle("sum", c("primitive", "base")), 15 | c("primitive", "base") 16 | ) 17 | }) 18 | 19 | test_that("superset", { 20 | # by definition/design, there are no real conflicts in base functions 21 | expect_equal(superset_principle("cbind", c("base", "methods")), character()) 22 | 23 | skip_if_not_installed("Matrix") 24 | # Automatically created S4 generics obey the superset principle 25 | expect_equal(superset_principle("print", c("base", "Matrix")), character()) 26 | # Even if the arguments have been customised 27 | expect_equal(superset_principle("rcond", c("base", "Matrix")), character()) 28 | }) 29 | 30 | test_that("functions aren't conflicts with non-functions", { 31 | pkgload::load_all(test_path("funmatch"), quiet = TRUE) 32 | on.exit(pkgload::unload("funmatch")) 33 | 34 | expect_equal(function_lookup("pi", c("base", "funmatch")), character()) 35 | expect_equal(function_lookup("median", c("stats", "funmatch")), character()) 36 | }) 37 | 38 | test_that("can find conflicts with data", { 39 | pkgload::load_all(test_path("data"), quiet = TRUE) 40 | on.exit(pkgload::unload("data")) 41 | 42 | expect_named(conflict_scout(c("datasets", "data")), "mtcars") 43 | }) 44 | 45 | test_that("preferences are obeyed", { 46 | pkgload::load_all(test_path("prefs"), quiet = TRUE) 47 | withr::defer(pkgload::unload("prefs")) 48 | 49 | on.exit(prefs_reset(), add = TRUE) 50 | 51 | expect_snapshot({ 52 | conflict_scout(c("rlang", "prefs")) 53 | conflicts_prefer(rlang::set_names()) 54 | conflict_scout(c("rlang", "prefs")) 55 | }) 56 | }) 57 | 58 | test_that("using canonical reference", { 59 | pkgload::load_all(test_path("pipe"), quiet = TRUE) 60 | withr::defer(pkgload::unload("pipe")) 61 | 62 | expect_snapshot({ 63 | conflict_scout(c("testthat", "dplyr", "pipe")) 64 | }) 65 | }) 66 | 67 | # moved functions ---------------------------------------------------- 68 | 69 | test_that(".Deprecated call contains function name", { 70 | f <- function() { 71 | .Deprecated("pkg::x") 72 | } 73 | 74 | expect_false(has_moved("pkg", "foo", f)) 75 | expect_true(has_moved("pkg", "x", f)) 76 | }) 77 | 78 | test_that("returns FALSE for weird inputs", { 79 | expect_false(has_moved(obj = 20)) 80 | expect_false(has_moved(obj = mean)) 81 | 82 | f <- function() {} 83 | expect_false(has_moved(obj = mean)) 84 | 85 | f <- function() { 86 | .Deprecated() 87 | } 88 | expect_false(has_moved(obj = mean)) 89 | 90 | f <- function() { 91 | .Deprecated(1) 92 | } 93 | expect_false(has_moved(obj = mean)) 94 | 95 | }) 96 | -------------------------------------------------------------------------------- /tests/testthat/test-package.R: -------------------------------------------------------------------------------- 1 | test_that("pkg_ls() respects exclude", { 2 | skip_if_not(getRversion() >= "3.6") 3 | 4 | library(callr) 5 | expect_true("r" %in% pkg_ls("callr")) 6 | pkgload::unload("callr") 7 | 8 | library(callr, exclude = "r") 9 | expect_false("r" %in% pkg_ls("callr")) 10 | pkgload::unload("callr") 11 | }) 12 | -------------------------------------------------------------------------------- /tests/testthat/test-prefer.R: -------------------------------------------------------------------------------- 1 | test_that("can register preference over all or selected packages", { 2 | on.exit(prefs_reset()) 3 | 4 | conflict_prefer("x1", "pkga", quiet = TRUE) 5 | conflict_prefer("x2", "pkga", "pkgb", quiet = TRUE) 6 | conflict_prefer("x3", "pkga", c("pkgb", "pkgc"), quiet = TRUE) 7 | 8 | expect_setequal(prefs_ls(), c("x1", "x2", "x3")) 9 | }) 10 | 11 | # resolution -------------------------------------------------------------- 12 | 13 | test_that("length 1 vector beats all comers", { 14 | on.exit(prefs_reset()) 15 | 16 | conflict_prefer("x1", "pkga", quiet = TRUE) 17 | expect_equal(prefs_resolve("x1"), "pkga") 18 | }) 19 | 20 | test_that("length n vector beats listed others", { 21 | on.exit(prefs_reset()) 22 | 23 | conflict_prefer("x1", "pkga", "pkgb", quiet = TRUE) 24 | expect_equal(prefs_resolve("x1", c("pkga", "pkgb")), "pkga") 25 | expect_equal(prefs_resolve("x1", c("pkga", "pkgb", "pkgc")), c("pkga", "pkgc")) 26 | }) 27 | 28 | 29 | # messaging ---------------------------------------------------------------- 30 | 31 | test_that("useful messages for specific preferences", { 32 | withr::defer(prefs_reset()) 33 | 34 | expect_snapshot({ 35 | conflict_prefer("mean", "canoodle", c("noodle", "doodle")) 36 | conflict_prefer("mean", "canoodle", "boodle") 37 | conflict_prefer("+", "canoodle") 38 | }) 39 | }) 40 | 41 | test_that("can register preference for multiple functions", { 42 | pkgload::load_all(test_path("funmatch"), quiet = TRUE, export_all = FALSE) 43 | withr::defer({ 44 | pkgload::unload("funmatch") 45 | prefs_reset() 46 | }) 47 | 48 | expect_snapshot({ 49 | conflict_prefer_all("funmatch") 50 | }) 51 | expect_setequal(prefs_ls(), c("median", "pi")) 52 | prefs_reset() 53 | 54 | expect_snapshot({ 55 | conflict_prefer_all("funmatch", "base") 56 | }) 57 | expect_setequal(prefs_ls(), "pi") 58 | prefs_reset() 59 | 60 | expect_snapshot({ 61 | conflict_prefer_matching("m", "funmatch") 62 | }) 63 | expect_setequal(prefs_ls(), "median") 64 | prefs_reset() 65 | }) 66 | -------------------------------------------------------------------------------- /tests/testthat/test-shim.R: -------------------------------------------------------------------------------- 1 | test_that("shimmed arguments match unshimmed", { 2 | shims_bind() 3 | expect_equal(formals(require), formals(base::require)) 4 | expect_equal(formals(library), formals(base::library)) 5 | }) 6 | 7 | test_that("shims load package with conflicts silently", { 8 | col_red <- function() {} 9 | shims_bind() 10 | 11 | expect_message(library(cli), NA) 12 | detach("package:cli") 13 | 14 | expect_message(require(cli, quietly = TRUE), NA) 15 | detach("package:cli") 16 | }) 17 | 18 | test_that("detaching package removes shims", { 19 | conflict <- "ns_env" 20 | skip_if_not(conflict %in% pkg_ls("pkgload") && conflict %in% pkg_ls("rlang")) 21 | shims_bind() 22 | 23 | library(pkgload) 24 | library(rlang) 25 | expect_true(exists(conflict, ".conflicts", inherits = FALSE)) 26 | 27 | detach("package:pkgload") 28 | detach("package:rlang") 29 | expect_false(exists(conflict, ".conflicts", inherits = FALSE)) 30 | }) 31 | 32 | test_that("shimmed help returns same as unshimmed", { 33 | shims_bind() 34 | 35 | expect_equal( 36 | library(help = "rlang"), 37 | base::library(help = "rlang") 38 | ) 39 | 40 | expect_equal( 41 | library(help = rlang), 42 | base::library(help = rlang) 43 | ) 44 | }) 45 | 46 | test_that("shimmed library() returns same as unshimmed", { 47 | # skip on CRAN because library() returns list of all installed packages 48 | # which might be changing in the background as other packages are installed 49 | skip_on_cran() 50 | shims_bind() 51 | 52 | expect_equal(library(), base::library()) 53 | }) 54 | 55 | # package_name ------------------------------------------------------------ 56 | 57 | test_that("package_name mimics library", { 58 | expect_equal(package_name(quo(ggplot2)), "ggplot2") 59 | 60 | x <- "ggplot2" 61 | expect_equal(package_name(quo(x), TRUE), "ggplot2") 62 | }) 63 | 64 | test_that("package_name throws errors with invalid names" ,{ 65 | package_name_ <- function(x) { 66 | package_name(quo(x), character.only = TRUE) 67 | } 68 | 69 | expect_snapshot(error = TRUE, { 70 | package_name_(c("x", "y")) 71 | package_name_(1:10) 72 | package_name_(NA_character_) 73 | package_name_("") 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("canonical_objs()", { 2 | expect_equal( 3 | canonical_objs( 4 | list( 5 | magrittr = dplyr::`%>%`, 6 | testthat = dplyr::`%>%`, 7 | dplyr = dplyr::`%>%` 8 | ), 9 | "%>%" 10 | ), 11 | list(magrittr = dplyr::`%>%`) 12 | ) 13 | 14 | # Only using packages that are attached, in order 15 | expect_equal( 16 | canonical_objs( 17 | list( 18 | testthat = dplyr::`%>%`, 19 | dplyr = dplyr::`%>%` 20 | ), 21 | "%>%" 22 | ), 23 | list(testthat = dplyr::`%>%`) 24 | ) 25 | 26 | expect_equal( 27 | canonical_objs( 28 | list( 29 | dplyr = dplyr::`%>%`, 30 | testthat = dplyr::`%>%` 31 | ), 32 | "%>%" 33 | ), 34 | list(dplyr = dplyr::`%>%`) 35 | ) 36 | }) 37 | -------------------------------------------------------------------------------- /tests/testthat/test-zzz.R: -------------------------------------------------------------------------------- 1 | test_that("unloading removes shims", { 2 | # Do in separate process to avoid interferring with tests 3 | in_search <- callr::r(function() { 4 | library(conflicted) 5 | pkgload::unload("conflicted") 6 | ".conflicts" %in% search() 7 | }) 8 | expect_false(in_search) 9 | }) 10 | --------------------------------------------------------------------------------