# \[ANN\] ExplicitImports.jl: tooling to help use and maintain explicit imports in your package

**URL:** <https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973>\
**Category:** Package Announcements\
**Created:** [February 29, 2024, 8:07pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973 "2024-02-29T20:07:24Z")\
**Posts on this page:** 20\
**Page:** 1

<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:** [February 29, 2024, 8:07pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/1 "2024-02-29T20:07:24Z")

</div>

## Background: what are implicit and explicit imports?

An _explicit import_ is a statement like `using SomePkg: foo` or `import SomePkg: foo` which explicitly brings the name `foo` into your module (often another package). In contrast, an _implicit_ one is a statement like `using SomePkg`, which brings all names exported by SomePkg into your module.

Implicit imports can be convenient, but they occasionally have issues in which another package `OtherPkg` starts exporting the same name `foo` (pointing to a different function, say), which makes it ambiguous which function should be associated to the name `foo` in your module.

Some also criticize that with implicit imports, if you only have access to the source code, you can’t see where each name is coming from. (If you are in a REPL, use `@which` to easily do so).

Explicit imports, on the other hand, can be quite inconvenient. You need to manually specify each name that you want to import. You also need to maintain this list as you use more names or stop using some names.

The choice of implicit vs explicit exports can be somewhat contentious, and I think in general the right choice depends on the specific usage (and your preference). I would prefer if folks **don’t use this thread to debate which to use** , and focus on the functionality ExplicitImports.jl provides (or fails to provide, as the case may be).

## What does [ExplicitImports.jl](https://github.com/ericphanson/ExplicitImports.jl) do?

[ExplicitImports.jl](https://github.com/ericphanson/ExplicitImports.jl) attempts to make it easier to use and maintain lists of explicit imports. The main interactive entrypoint is:

```julia
using ExplicitImports, MyPackage
print_explicit_imports(MyPackage)

```

For example:

````julia
julia> using ExplicitImports

julia> print_explicit_imports(ExplicitImports)
Module ExplicitImports is relying on implicit imports for 6 names. These could be explicitly imported as follows:

```julia
using AbstractTrees: AbstractTrees
using AbstractTrees: Leaves
using AbstractTrees: TreeCursor
using AbstractTrees: children
using AbstractTrees: nodevalue
using JuliaSyntax: JuliaSyntax
```

Additionally, ExplicitImports has stale explicit imports for these unused names:
- GreenNode

````

This allows one to develop some code using implicit imports, then call `print_explicit_imports` to get a list of explicit import statements to copy into their code.

Additionally, if one would like to only rely on explicit imports, you can add ExplicitImports as a test dependency, and add `check_no_implicit_imports(ExplicitImports)` to your tests. ExplicitImports is registered in General to make it convenient to add as a test dependency.

There’s a bit more discussion and a few more features in the [docs](https://ericphanson.github.io/ExplicitImports.jl/stable/), so please check those out for more!

And if you run into any bugs or problems, please file an issue. ExplicitImports works by parsing your code and trying to re-implement Julia’s scoping rules to figure out whether any given name refers to a global that may be implicitly imported, or just a local variable. There’s a ton of edge cases and it’s very possible there are common failure modes. I am hoping we can squash those as they arise, and rely on the `ignore` kwarg in `check_no_implicit_imports` in the meantime to manually suppress false positives. (And if you know about parsing and have ideas about how I can do it better, please file an issue or PR!).

---

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [February 29, 2024, 9:36pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/2 "2024-02-29T21:36:58Z")

</div>

This would be a nice addition to the language server code actions. The language server should already know where bindings come from so you wouldn’t have to traverse the source code in this case (I think).

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [February 29, 2024, 9:53pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/3 "2024-02-29T21:53:56Z")

</div>

Awesome idea!!

Is the plan to (eventually) add this to Aqua.jl?

---

<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:** [February 29, 2024, 9:56pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/4 "2024-02-29T21:56:50Z")

</div>

I think LanguageServer integration would be great. I have no idea how that works though.

---

<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:** [February 29, 2024, 9:58pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/5 "2024-02-29T21:58:51Z")

</div>

I am not sure if Aqua would want it, since it adds two dependencies (AbstractTrees and JuliaSyntax - though we could use `Base.JuliaSyntax` to get rid of the latter). It also might be too immature at this phase. Lastly I think automatically erroring on stuff like implicit imports seems to opinionated/aggressive to me. But I think it’s really up to the Aqua maintainers in terms of what they want to do and what the vision for the package is.

---

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [February 29, 2024, 9:58pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/6 "2024-02-29T21:58:59Z")

</div>

If you, or anyone else, wants to give it a try perhaps [the PR where I implemented import sorting](https://github.com/julia-vscode/LanguageServer.jl/pull/1076) could be useful to get an idea about what you need to add.

---

<div class="post-metadata">

**Author:** ![ranocha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ranocha/32/35588_2.png) [@ranocha](https://discourse.julialang.org/u/ranocha)\
**Post date:** [March 1, 2024, 4:19am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/7 "2024-03-01T04:19:03Z")

</div>

Thanks for the nice package! Right now, it requires Julia v1.10. Thus, we cannot use it for packages running CI also with older versions of Julia. Do you have a suggestion for a workaround for such a case?

---

<div class="post-metadata">

**Author:** ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)\
**Post date:** [March 1, 2024, 5:04am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/8 "2024-03-01T05:04:35Z")

</div>

Would just putting a conditional on the test set work:

```julia
if VERSION >= v"1.10"
    @testset ...
end

```

---

<div class="post-metadata">

**Author:** ![ranocha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ranocha/32/35588_2.png) [@ranocha](https://discourse.julialang.org/u/ranocha)\
**Post date:** [March 1, 2024, 5:44am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/9 "2024-03-01T05:44:17Z")

</div>

Not in a nice way, since we need to add ExplicitImports.jl to `test/Project.toml` and the package cannot be installed on older Julia versions.  
The only way I see would be to depend on Pkg for tests and install ExplicitImports.jl after a check for the Julia version, but that appears to be more cumbersome than I would like.

---

<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:** [March 1, 2024, 2:55pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/10 "2024-03-01T14:55:41Z")

</div>

I think we should be able to drop the julia compat bound to at least 1.7 (I’m using the destructuring syntax there so dropping it further would require not using that syntax). But when I tried some tests failed, so I need to debug further.

---

<div class="post-metadata">

**Author:** ![ranocha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ranocha/32/35588_2.png) [@ranocha](https://discourse.julialang.org/u/ranocha)\
**Post date:** [March 1, 2024, 2:58pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/11 "2024-03-01T14:58:15Z")

</div>

That would be very nice, thanks a lot.

---

<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:** [March 2, 2024, 12:33am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/12 "2024-03-02T00:33:43Z")

</div>

Just to say v1.0.1 is out and supports Julia 1.7+.

---

<div class="post-metadata">

**Author:** ![ranocha](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ranocha/32/35588_2.png) [@ranocha](https://discourse.julialang.org/u/ranocha)\
**Post date:** [March 2, 2024, 12:13pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/13 "2024-03-02T12:13:52Z")

</div>

Nice, thanks!

---

<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:** [May 18, 2024, 12:44pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/14 "2024-05-18T12:44:49Z")

</div>

[ExplicitImports v1.44](https://github.com/ericphanson/ExplicitImports.jl/releases) is out, which fixes a lot of bugs in determining whether or not a name comes from an external package or is local to the module. I am hopeful that eventually [JuliaLowering.jl](https://github.com/c42f/JuliaLowering.jl) will provide better access to scope information than my hacky parsing, and at some point we can switch to using that instead for precise information. However, in the meantime I think what we have is “good enough” for a lot of practical use, and it’s easy enough to whackamole false positives as they arise (please do file issues!).

Also, since ExplicitImports v1.4, the printing is grouped by package and linewidth aware (with a default of 80):

````julia
julia> print_explicit_imports(ExplicitImports; linewidth=60)
Module ExplicitImports is relying on implicit imports for 7 names. These could be explicitly imported as follows:

```julia
using AbstractTrees: AbstractTrees, Leaves, TreeCursor,
                     children, nodevalue
using JuliaSyntax: JuliaSyntax, @K_str
```

````

I think this formatting is a bit nicer than the long list of `using Foo: bar`’s.

---

<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 10, 2024, 9:56am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/15 "2024-06-10T09:56:59Z")

</div>

ExplicitImports v1.5 and v1.6 add a bunch of functionality to make it easier to work with qualified names and explicit imports. It is all summarized here:

| Problem | Example | Interactive detection | Programmatic detection | Regression-testing check |
| --- | --- | --- | --- | --- |
| Implicit imports | `using LinearAlgebra` | `print_explicit_imports` | `implicit_imports` | `check_no_implicit_imports` |
| Non-owning import | `using LinearAlgebra: map` | `print_explicit_imports` | `improper_explicit_imports` | `check_all_explicit_imports_via_owners` |
| Non-public import | `using LinearAlgebra: _svd!` | `print_explicit_imports` with `report_non_public=true` | `improper_explicit_imports` | `check_all_explicit_imports_are_public` |
| Stale import | `using LinearAlgebra: svd # unused` | `print_explicit_imports` | `improper_explicit_imports` | `check_no_stale_explicit_imports` |
| Non-owning access | `LinearAlgebra.map` | `print_explicit_imports` | `improper_qualified_accesses` | `check_all_qualified_accesses_via_owners` |
| Non-public access | `LinearAlgebra._svd!` | `print_explicit_imports` with `report_non_public=true` | `improper_qualified_accesses` | `check_all_qualified_accesses_are_public` |
| Self-qualified access | `Foo.bar` within the module `Foo` | `print_explicit_imports` | `improper_qualified_accesses` | `check_no_self_qualified_accesses` |

(from [https://github.com/ericphanson/ExplicitImports.jl/tree/main?tab=readme-ov-file#summary](https://github.com/ericphanson/ExplicitImports.jl/tree/main?tab=readme-ov-file#summary))

---

<div class="post-metadata">

**Author:** ![e3c6](https://avatars.discourse-cdn.com/v4/letter/e/e79b87/32.png) [@e3c6](https://discourse.julialang.org/u/e3c6)\
**Post date:** [July 2, 2024, 1:07pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/16 "2024-07-02T13:07:35Z")

</div>

> [@ericphanson](#):
>
> Also, since ExplicitImports v1.4, the printing is grouped by package and linewidth aware (with a default of 80):
> 
> ```julia
> julia> print_explicit_imports(ExplicitImports; linewidth=60)
> Module ExplicitImports is relying on implicit imports for 7 names. These could be explicitly imported as follows:
> 
> using AbstractTrees: AbstractTrees, Leaves, TreeCursor,
> children, nodevalue
> 
> ```

Actually, I preferred the previous way,

> [@ericphanson](#):
>
> ```julia
> julia> print_explicit_imports(ExplicitImports)
> Module ExplicitImports is relying on implicit imports for 6 names. These could be explicitly imported as follows:
> 
> using AbstractTrees: AbstractTrees
> using AbstractTrees: Leaves
> using AbstractTrees: TreeCursor
> ...
> 
> ```

I like having each `using` in its own line.

Any chance this can be controlled by a setting (or a keyword argument to `print_explicit_imports`) ?

---

<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:** [July 2, 2024, 9:02pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/17 "2024-07-02T21:02:46Z")

</div>

Sure, I just tagged ExplicitExports v1.7 which adds a `separate_lines` kwarg to `print_explicit_imports`, and also unrelatedly allows the package to work on Julia 1.6 instead of only 1.7+.

---

<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 12, 2025, 10:47pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/18 "2025-06-12T22:47:34Z")

</div>

Since developing ExplicitImports, JuliaSyntax has reached 1.0! 🎉

This however has meant some breaking changes in the syntax tree, which required some updating in ExplicitImports. I have a [PR](https://github.com/ericphanson/ExplicitImports.jl/pull/110) that is JuliaSyntax-1.0-compatible that you can install here:

```julia
pkg> add ExplicitImports#eph/js_v1

```

The branch is now passing all my tests, and I plan to release it as ExplicitImports v1.12 next week.

However in the meantime if you are currently using ExplicitImports in your codebase, **if you could test out this branch** and verify it still works (e.g. does not start failing your tests) that would be very helpful.

Both success and failure reports are useful, and you can reply here, [file an issue on ExplicitImports.jl](https://github.com/ericphanson/ExplicitImports.jl/issues/new), on the [PR](https://github.com/ericphanson/ExplicitImports.jl/pull/110) or message me on the [open source Julia slack](https://julialang.org/slack/).

---

<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 18, 2025, 11:37am UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/19 "2025-06-18T11:37:00Z")

</div>

Just to say this is now registered as v1.12. I’ve also transferred ExplicitImports.jl to the JuliaTesting github org so that fixes/updates can still be made if I’m unavailable.

---

<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:** [July 19, 2025, 5:03pm UTC](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973/20 "2025-07-19T17:03:50Z")

</div>

# ExplicitImports v1.13

ExplicitImports v1.13 now requires Julia 1.10+. This makes ExplicitImports a bit easier to maintain and allows us to use newer Julia features in the package itself.

ExplicitImports v1.13 also now supports ignoring parts of source code or submodules: [https://juliatesting.github.io/ExplicitImports.jl/stable/api/#Advanced:-telling-ExplicitImports-to-ignore-parts-of-source-code](https://juliatesting.github.io/ExplicitImports.jl/stable/api/#Advanced:-telling-ExplicitImports-to-ignore-parts-of-source-code). I expect this to be a niche feature.

More importantly, it also drops JuliaSyntax and AbstractTrees as Pkg-managed dependencies. JuliaSyntax in particular was presenting some compatibility issues; ExplicitImports v1.12 updated to JuliaSyntax v1, but some other packages (or versions of packages) do not support JuliaSyntax v1 leading to compatibility conflicts. This is particularly problematic for ExplicitImports for two reasons:

- ExplicitImports needs to load the user’s package in the same session as it performs a dynamic (not static) analysis. So the ExplicitImports CLI app, for example, would fail to analyze a package which (perhaps transitively) required a different JuliaSyntax than ExplicitImports itself. I have not seen reports of this in practice yet, likely since JuliaSyntax is a somewhat rare dependency and the CLI app (which probably comes with higher expectations of freedom-from-compat-issues than the library) is not widely used.
- ExplicitImports and other dev dependencies like Cthulhu.jl which depend on JuliaSyntax are often added to the global environment or in startup files. Once one of these libraries loads some version of JuliaSyntax, the package is loaded once and for all. Then if the user activates an environment (perhaps even a temp env) and tries to load a package which requires a different JuliaSyntax version, it will “silently” break. Pkg will _not_ report a compatibility violation (as each environment is separately valid) but the loaded version of the package will be incompatible with the required version of the package. There have been 3 reports of this.

ExplicitImports v1.13 solves these issues by _vendoring_ JuliaSyntax and AbstractTrees. I.e. the source code of JuliaSyntax and AbstractTrees is programmatically copied into the source code of ExplicitImports during development so that ExplicitImports has submodules corresponding to particular versions of JuliaSyntax and AbstractTrees which are independent of the packages themselves. This allows us to remove the dependencies from our Project.toml to allow Pkg to freely load whichever versions of the libraries the user needs, since we will rely on our own private copies of the code instead. ExplicitImports now only has Pkg dependencies on Markdown, Pkg, PrecompileTools, and TOML.

Vendoring is _not_ a general solution to compatibility problems, although it works well in this case. If you are thinking about vendoring a dependency, here are some things to consider:

- a private copy of a module means types and functions are not shared. `ExplicitImports.Vendored.JuliaSyntax.SyntaxData` is a different type than `JuliaSyntax.SyntaxData`. If other libraries define methods for types or functions of JuliaSyntax, we won’t see them, since they won’t apply to our private copy. This totally breaks composability and multiple dispatch! That’s fine for our use case here, but can be a big issue in other use cases.
- one no longer gets patch releases “for free”; if a vendored dependency needs an update, the library that vendored it needs to run a script to update the code, then release its own patch release.
- we no longer have the power of Pkg to manage dependency versions, e.g. to choose a different version of the dependency depending on the Julia version. This makes it hard to vendor dependencies which are tightly coupled to Julia itself, like PrecompileTools.
- all transitive dependencies either need to become our own dependencies or be additionally vendored. In the case of ExplicitImports, neither JuliaSyntax nor AbstractTrees add additional transitive dependencies, making this easy for us.

[Next page](https://discourse.julialang.org/t/ann-explicitimports-jl-tooling-to-help-use-and-maintain-explicit-imports-in-your-package/110973.md?page=2)
