Skip to content

[Linting Rule] Unused Import

github-actions[bot] edited this page Aug 20, 2026 · 1 revision

Generated from 'wiki-linter.ts' on 2026-08-19, 17:34:38 UTC (v2.14.1), please do not edit directly.

Unused Import [overview]

smell quickfix readability

This rule is a best-effort rule.

Highlights packages that are attached but never used, so the code runs just the same without them. Requires a signature database, and packages that only do their work on load should be whitelisted in the configuration.
This linting rule is implemented in src/linter/rules/unused-import.ts.

Configuration

Linting rules can be configured by passing a configuration object to the linter query as shown in the example below. The unused-import rule accepts the following configuration options:

  • whitelist
    packages that do their work on load and hence should never be reported, however unused they look

Examples

library(stats)
print("no stats function is used")

The linting query can be used to run this rule on the above example:

[ { "type": "linter",   "rules": [ { "name": "unused-import",     "config": {} } ] } ]

Results (prettified and summarized):

Query: linter (6 ms)
   ╰ Unused Import (unused-import):
       ╰ uncertain:
           ╰ Import of stats at 1.1-14 (1 quick fix(es) available)
       ╰ Metadata: totalConsidered: 1, totalUnresolved: 0, totalMultiPackage: 0, totalUnused: 1, searchTimeMs: 6, processTimeMs: 0
All queries together required ≈6 ms (1ms accuracy, total 7 ms)

Show Detailed Results as Json

The analysis required 7.0 ms (including parsing and normalization and the query) within the generation environment.

In general, the JSON contains the Ids of the nodes in question as they are present in the normalized AST or the dataflow graph of flowR. Please consult the Interface wiki page for more information on how to get those.

{
  "linter": {
    "results": {
      "unused-import": {
        "results": [
          {
            "certainty": "uncertain",
            "involvedId": 3,
            "loc": [
              1,
              1,
              1,
              14
            ],
            "package": "stats",
            "version": "4.5.3",
            "quickFix": [
              {
                "type": "remove",
                "description": "Remove the unused import of stats",
                "loc": [
                  1,
                  1,
                  1,
                  14
                ]
              }
            ]
          }
        ],
        ".meta": {
          "totalConsidered": 1,
          "totalUnresolved": 0,
          "totalMultiPackage": 0,
          "totalUnused": 1,
          "searchTimeMs": 6,
          "processTimeMs": 0
        }
      }
    },
    ".meta": {
      "timing": 6
    }
  },
  ".meta": {
    "timing": 6
  }
}

Additional Examples

These examples are synthesized from the test cases in: test/functionality/linter/lint-unused-import.test.ts

Test Case: a lone import is unused

Given the following input:

library(ggplot2)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [1, 1, 1, 16])]

See here for the test-case implementation.

Test Case: character.only resolves the package from the variable

Given the following input:

pkg <- "ggplot2"
library(pkg, character.only = TRUE)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [2, 1, 2, 35])]

See here for the test-case implementation.

Test Case: a called export keeps the import

Given the following input:

library(ggplot2)
ggplot()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: require counts just like library

Given the following input:

require(ggplot2)
require(random1)
aes()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('random1', [2, 1, 2, 16])]

See here for the test-case implementation.

Test Case: only the unused ones are reported

Given the following input:

library(p)
library(ggplot2)
library(random1)
ggplot()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('p', [1, 1, 1, 10]), unused('random1', [3, 1, 3, 16])]

See here for the test-case implementation.

Test Case: a namespaced call keeps the import

Given the following input:

library(ggplot2)
ggplot2::ggplot()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: a use inside a function body keeps the import

Given the following input:

library(ggplot2)
f <- function() aes()
f()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: a use inside a branch keeps the import

Given the following input:

library(ggplot2)
if(x) { ggplot() }

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: a shadowed export does not keep the import

Given the following input:

library(ggplot2)
aes <- function() 1
aes()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [1, 1, 1, 16])]

See here for the test-case implementation.

Test Case: a call we cannot bind yet keeps the import, even when the code defines the name itself

Given the following input:

library(ggplot2)
aes <- function() 1
f <- function() aes()
f()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: using one package does not excuse the others

Given the following input:

library(p)
library(ggplot2)
library(random1)
p::f()
test1()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [2, 1, 2, 16])]

See here for the test-case implementation.

Test Case: a braced branch can be emptied

Given the following input:

library(ggplot2)
if(x) { print(1) }

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [1, 1, 1, 16])]

See here for the test-case implementation.

Test Case: an unbraced branch offers no removal

Given the following input:

if(x) library(ggplot2)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [1, 7, 1, 22], false)]

See here for the test-case implementation.

Test Case: an unbraced function body offers no removal

Given the following input:

f <- function() library(ggplot2)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('ggplot2', [1, 17, 1, 32], false)]

See here for the test-case implementation.

Test Case: a package the database does not know is skipped

Given the following input:

library(ggplot2)
library(random1)
library(notInDb)
aes()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

[unused('random1', [2, 1, 2, 16])]

See here for the test-case implementation.

Test Case: a whitelisted package is never reported

Given the following input:

require(p)
require(ggplot2)
require(random1)
aes()

And using the following configuration:

{ sigDb, whitelist: ['random1'] }

We expect the linter to report the following:

[unused('p', [1, 1, 1, 10])]

See here for the test-case implementation.

Test Case: nothing is reported without a signature database

Given the following input:

library(ggplot2)

And using the following configuration:

{ noSigDb: true }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: requireNamespace is not an import

Given the following input:

if(!requireNamespace("ggplot2", quietly = TRUE)) stop("need it")

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: loadNamespace is not an import

Given the following input:

loadNamespace("ggplot2")

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: a qualified call is not an import of its own

Given the following input:

p::f()

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: an attach naming several packages at once is skipped

Given the following input:

for(pkg in c("ggplot2", "p")) library(pkg, character.only = TRUE)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Test Case: an attach whose package cannot be resolved is skipped

Given the following input:

library(pkg, character.only = TRUE)

And using the following configuration:

{ sigDb }

We expect the linter to report the following:

* no lints

See here for the test-case implementation.

Clone this wiki locally