# Is there tooling to mark unused imports?

**URL:** <https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638>\
**Category:** Tooling\
**Tags:** question\
**Created:** [June 4, 2025, 12:16pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638 "2025-06-04T12:16:58Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 4, 2025, 12:16pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/1 "2025-06-04T12:16:59Z")

</div>

Is there a tool (that I can run as part of CI for example) that detects unused namespace imports within a module (or the corresponding package)?

Eg in

```julia
module Foo

import Statistics: mean

awesome_functionality() = 1 # does not use mean at all

end

```

I would expect it to tell me that I imported `Statistics.mean` and yet it is unused.

A more advanced form would check all symbols and error when nothing at all is used from a module, eg

```julia
module Bar

using LinearAlgebra # but nothing it exports is used
import Random # never used

awesome_functionality() = 1

end

```

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [June 4, 2025, 12:24pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/2 "2025-06-04T12:24:38Z")

</div>

I think that’s the “stale imports” feature of

> **[GitHub - ericphanson/ExplicitImports.jl: Developer tooling for Julia namespace management:...](https://github.com/ericphanson/ExplicitImports.jl)**
>
> Developer tooling for Julia namespace management: detecting implicit imports, improper explicit imports, and improper qualified accesses

?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 4, 2025, 1:32pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/3 "2025-06-04T13:32:56Z")

</div>

Thanks! I was not aware of the package. `ExplicitImports.check_no_stale_explicit_imports` catches the first case (`using Statistics: mean`) but not the `using` / `import` of packages that end up unused.

Is there a functionality in the package that does the latter?

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [June 4, 2025, 2:06pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/4 "2025-06-04T14:06:51Z")

</div>

This would be tricky to properly detect. For example, the checking system would have to make sure that a package extension wasn’t triggered by the `import` which you relied on indirectly.

A classic example would be

```julia
using DifferentiationInterface: AutoForwardDiff, derivative
import ForwardDiff

derivative(sin, AutoForwardDiff(), 1.0)

```

This looks like it doesn’t use `ForwardDiff` at all, but it would fail if ForwardDiff wasn’t loaded.

---

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [June 4, 2025, 2:53pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/5 "2025-06-04T14:53:08Z")

</div>

You can use `print_explicit_imports`, when I tried it, it just left out the packages that weren’t used at all.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [June 4, 2025, 3:10pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/6 "2025-06-04T15:10:11Z")

</div>

Aqua.jl also does some of that

---

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [June 4, 2025, 7:41pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/7 "2025-06-04T19:41:58Z")

</div>

Yeah, ExplicitImports can’t tell if you’ve loaded a package for the “side-effects” (e.g. new methods) vs to get new names in your namespace. It can only say “PkgX has provided such and such names into your namespace and you aren’t using them” (though it isn’t 100% accurate at this currently and I don’t think the `check_` functions use this).

However, I think a good syntax here is: if you are only loading ForwardDiff for the “side-effects”, you could indicate this with

```julia
import ForwardDiff as _

```

i.e., I want to load ForwardDiff but I don’t want it to bring _any_ names into my namespace, not even the module itself, since I’m not using them. I checked Julia 1.11 and this syntax does seem to be supported already!

I could imagine a future ExplicitImports version suggesting rewrites of `import ForwardDiff` into `import ForwardDiff as _` if there are no names being used (thereby assuming it is there for the side-effects). Then the dev can delete it if they know they aren’t in fact using the side-effects.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 4, 2025, 8:39pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/8 "2025-06-04T20:39:56Z")

</div>

Worth adding to the documentation?

---

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [June 5, 2025, 12:10pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/9 "2025-06-05T12:10:17Z")

</div>

ExplicitImports docs or Julia docs?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 5, 2025, 12:13pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/10 "2025-06-05T12:13:12Z")

</div>

Probably both:

1. first in the Julia docs. It is a useful trick for conditional loading without bringing anything into the namespace, regardless of other usage
2. into ExplicitImports.jl for the purpose discussed in this topic.

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [June 5, 2025, 12:18pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/11 "2025-06-05T12:18:07Z")

</div>

Is Aqua.jl able to detect that? If not, perhaps create a feature request for this package?

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [June 5, 2025, 5:36pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/12 "2025-06-05T17:36:51Z")

</div>

I for one think Aqua.jl should absorb ExplicitImports.jl, or at least offer to call it in a nice way

---

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [June 5, 2025, 11:37pm UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/13 "2025-06-05T23:37:01Z")

</div>

I also think that would be good. Some notes:

- Currently Aqua supports Julia 1.6, but ExplicitImports needs 1.7+; the easy fix is to just make Aqua more restrictive and add the dep
- Currently ExplicitImports has some false positives and false negatives (see README/issues). Many of those could be fixed once JuliaLowering is stable by using it instead of my ad-hoc buggy lowering. But maybe it would be better to just rewrite the functionality on top of JuliaLowering rather than adapting the existing code; I’m not sure. I do hope to have a version of this in ExplicitImports.jl at some point, whether as a rewrite or an upgrade.
- Currently ExplicitImports does not yet support JuliaSyntax v1 (its work-in-progress to upgrade it). So it could cause downgrades.
- adding ExplicitImports would add a few deps like AbstractTrees and Markdown (not too many though, I’ve tried to be careful there).
- There is an issue here: [Check unused explicit imports, e.g. using `ExplicitImports.jl` · Issue #268 · JuliaTesting/Aqua.jl · GitHub](https://github.com/JuliaTesting/Aqua.jl/issues/268)
- I’d prefer to keep them as separate packages and expose any API Aqua needs from ExplicitImports rather than move the code into a submodule of Aqua, since the styles/implementations are fairly different and it’s a big chunk of code. But I’d be happy to move ExplicitImports into the JuliaTesting GitHub org if that would be preferable.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [June 6, 2025, 7:31am UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/14 "2025-06-06T07:31:49Z")

</div>

Maybe we could add ExplicitImports.jl to Aqua.jl as an extension? There could be a function like

```julia
function Aqua.test_explicit_imports(
    package,
    check_no_implicit_imports=true,
    check_no_stale_explicit_imports=true,
    check_all_explicit_imports_via_owners=true,
    check_all_explicit_imports_are_public=true,
    check_all_qualified_accesses_via_owners=true,
    check_all_qualified_accesses_are_public=true,
    check_no_self_qualified_accesses=true
)

```

which does nothing unless the weakdep is loaded. It would also solve my main gripe with ExplicitImports.jl, namely the need to use these six functions separately.

---

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [June 6, 2025, 7:40am UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/15 "2025-06-06T07:40:42Z")

</div>

Sure, well if you just want that function we could just add it to ExplicitImports. To me it seems like the advantage of integration with Aqua would be `Aqua.test_all` – but I don’t think that should be via an extension since it would give different results if ExplicitImports was loaded or not.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [June 6, 2025, 7:45am UTC](https://discourse.julialang.org/t/is-there-tooling-to-mark-unused-imports/129638/16 "2025-06-06T07:45:02Z")

</div>

> [@ericphanson](#):
>
> To me it seems like the advantage of integration with Aqua would be `Aqua.test_all`

Yeah, I was thinking `Aqua.test_all` could also have a `test_explicit_imports` option like for the other types of tests. The fact that results differ based on whether or not ExplicitImports.jl is loaded does suck a little, but in that case we could either

- print a warning
- straight up error (and then set the default to `test_explicit_imports=false`)

The last option would enable reliable but voluntary integration. We did a variant of that for [`test_undocumented_names`](https://juliatesting.github.io/Aqua.jl/stable/undocumented_names/) too because it is not available on Julia 1.10.
