API
The main entrypoint for interactive use is print_explicit_imports
. ExplicitImports.jl API also includes several other functions to provide programmatic access to the information gathered by the package, as well as utilities to use in regression testing.
Detecting implicit imports which could be made explicit
ExplicitImports.print_explicit_imports
— Functionprint_explicit_imports([io::IO=stdout,] mod::Module, file=pathof(mod); skip=(mod, Base, Core), warn_stale=true, strict=true)
Runs explicit_imports
and prints the results, along with those of stale_explicit_imports
.
Keyword arguments
skip=(mod, Base, Core)
: any names coming from the listed modules (or any submodules thereof) will be skipped. Sincemod
is included by default, implicit imports of names exported from its own submodules will not count by default.warn_stale=true
: if set, this function will also print information about stale explicit imports.strict=true
: whenstrict
is set, a module will be noted as unanalyzable in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.show_locations=false
: whether or not to print locations of where the names are being used (and, ifwarn_stale=true
, where the stale explicit imports are).linewidth=80
: format into lines of up to this length. Set to 0 to indicate one name should be printed per line.
See also check_no_implicit_imports
and check_no_stale_explicit_imports
.
ExplicitImports.explicit_imports
— Functionexplicit_imports(mod::Module, file=pathof(mod); skip=(mod, Base, Core), warn_stale=true, strict=true)
Returns a nested structure providing information about explicit import statements one could make for each submodule of mod
. This information is structured as a collection of pairs, where the keys are the submodules of mod
(including mod
itself), and the values are NamedTuple
s, with at least the keys name
, source
, exporters
, and location
, showing which names are being used implicitly, which modules they were defined in, which modules they were exported from, and the location of those usages. Additional keys may be added to the NamedTuple
's in the future in non-breaking releases of ExplicitImports.jl.
Arguments
mod::Module
: the module to (recursively) analyze. Often this is a package.file=pathof(mod)
: this should be a path to the source code that contains the modulemod
.- if
mod
is the top-level module of a package,pathof
will be unable to find the code, and a file must be passed which containsmod
(either directly or indirectly throughinclude
s) mod
can be a submodule defined withinfile
, but if two modules have the same name (e.g.X.Y.X
andX
), results may be inaccurate.
- if
Keyword arguments
skip=(mod, Base, Core)
: any names coming from the listed modules (or any submodules thereof) will be skipped. Sincemod
is included by default, implicit imports of names exported from its own submodules will not count by default.warn_stale=true
: whether or not to warn about stale explicit imports.strict=true
: whenstrict
is set, results for a module will benothing
in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.
If mod
is a package, we can detect the explicit_imports in the package extensions if those extensions are explicitly loaded before calling this function.
For example, consider PackageA
has a weak-dependency on PackageB
and PackageC
in the module PkgBPkgCExt
julia> using ExplicitImports, PackageA
julia> explicit_imports(PackageA) # Only checks for explicit imports in PackageA and its submodules but not in `PkgBPkgCExt`
To check for explicit imports in PkgBPkgCExt
, you can do the following:
julia> using ExplicitImports, PackageA, PackageB, PackageC
julia> explicit_imports(PackageA) # Now checks for explicit imports in PackageA and its submodules and also in `PkgBPkgCExt`
See also print_explicit_imports
to easily compute and print these results, explicit_imports_nonrecursive
for a non-recursive version which ignores submodules, and check_no_implicit_imports
for a version that throws errors, for regression testing.
Looking just for stale explicit exports
While print_explicit_imports
prints stale explicit exports, and explicit_imports
by default provides a warning when stale explicit exports are present, sometimes one wants to only look for stale explicit imports without looking at implicit imports. Here we provide some entrypoints that help for this use-case.
ExplicitImports.print_stale_explicit_imports
— Functionprint_stale_explicit_imports([io::IO=stdout,] mod::Module, file=pathof(mod); strict=true, show_locations=false)
Runs stale_explicit_imports
and prints the results.
Keyword arguments
strict=true
: whenstrict
is set, a module will be noted as unanalyzable in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.show_locations=false
: whether or not to print where the explicit imports were made. If the same name was explicitly imported more than once, it will only show one such import.
See also print_explicit_imports
and check_no_stale_explicit_imports
.
ExplicitImports.stale_explicit_imports
— Functionstale_explicit_imports(mod::Module, file=pathof(mod); strict=true)
Returns a collection of pairs, where the keys are submodules of mod
(including mod
itself), and the values are either nothing
if strict=true
and the module couldn't analyzed, or else a vector of NamedTuple
s with at least the keys name
and location
, consisting of names that are explicitly imported in that submodule, but which either are not used, or are only used in a qualified fashion, making the explicit import a priori unnecessary.
More keys may be added to the NamedTuples in the future in non-breaking releases of ExplicitImports.jl.
Note that it is possible for an import from a module (say X
) into one module (say A
) to be relied on from another unrelated module (say B
). For example, if A
contains the code using X: x
, but either does not use x
at all or only uses x
in the form X.x
, then x
will be flagged as a stale explicit import by this function. However, it could be that the code in some unrelated module B
uses A.x
or using A: x
, relying on the fact that x
has been imported into A
's namespace.
This is an unusual situation (generally B
should just get x
directly from X
, rather than indirectly via A
), but there are situations in which it arises, so one may need to be careful about naively removing all "stale" explicit imports flagged by this function.
Keyword arguments
strict=true
: whenstrict
is set, results for a module will benothing
in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.
See stale_explicit_imports_nonrecursive
for a non-recursive version, and check_no_stale_explicit_imports
for a version that throws an error when encountering stale explicit imports.
See also print_explicit_imports
which prints this information.
Checks to use in testing
ExplicitImports.jl provides two functions which can be used to regression test that there is no reliance on implicit imports or stale explicit imports:
ExplicitImports.check_no_implicit_imports
— Functioncheck_no_implicit_imports(mod::Module, file=pathof(mod); skip=(mod, Base, Core), ignore::Tuple=(), allow_unanalyzable::Tuple=())
Checks that neither mod
nor any of its submodules is relying on implicit imports, throwing an ImplicitImportsException
if so, and returning nothing
otherwise.
This function can be used in a package's tests, e.g.
@test check_no_implicit_imports(MyPackage) === nothing
Allowing some submodules to be unanalyzable
Pass allow_unanalyzable
as a tuple of submodules which are allowed to be unanalyzable. Any other submodules found to be unanalyzable will result in an UnanalyzableModuleException
being thrown.
These unanalyzable submodules can alternatively be included in ignore
.
Allowing some implicit imports
The skip
keyword argument can be passed to allow implicit imports from some modules (and their submodules). By default, skip
is set to (Base, Core)
. For example:
@test check_no_implicit_imports(MyPackage; skip=(Base, Core, DataFrames)) === nothing
would verify there are no implicit imports from modules other than Base, Core, and DataFrames.
Additionally, the keyword ignore
can be passed to represent a tuple of items to ignore. These can be:
- modules. Any submodule of
mod
matching an element ofignore
is skipped. This can be used to allow the usage of implicit imports in some submodule of your package. - symbols: any implicit import of a name matching an element of
ignore
is ignored (does not throw) symbol => module
pairs. Any implicit import of a name matching that symbol from a module matching the module is ignored.
One can mix and match between these type of ignored elements. For example:
@test check_no_implicit_imports(MyPackage; ignore=(:DataFrame => DataFrames, :ByRow, MySubModule)) === nothing
This would:
- Ignore any implicit import of
DataFrame
from DataFrames - Ignore any implicit import of the name
ByRow
from any module. - Ignore any implicit imports present in
MyPackage
's submoduleMySubModule
but verify there are no other implicit imports.
ExplicitImports.check_no_stale_explicit_imports
— Functioncheck_no_stale_explicit_imports(mod::Module, file=pathof(mod); ignore::Tuple=(), allow_unanalyzable::Tuple=())
Checks that neither mod
nor any of its submodules has stale (unused) explicit imports, throwing an StaleImportsException
if so, and returning nothing
otherwise.
This can be used in a package's tests, e.g.
@test check_no_stale_explicit_imports(MyPackage) === nothing
Allowing some submodules to be unanalyzable
Pass allow_unanalyzable
as a tuple of submodules which are allowed to be unanalyzable. Any other submodules found to be unanalyzable will result in an UnanalyzableModuleException
being thrown.
Allowing some stale explicit imports
If ignore
is supplied, it should be a tuple of Symbol
s, representing names that are allowed to be stale explicit imports. For example,
@test check_no_stale_explicit_imports(MyPackage; ignore=(:DataFrame,)) === nothing
would check there were no stale explicit imports besides that of the name DataFrame
.
Usage with scripts (such as runtests.jl
)
We also provide a helper function to analyze scripts (rather than modules). If you are using a module in your script (e.g. if your script starts with module
), then use the ordinary print_explicit_imports
function instead. This functionality is somewhat experimental and attempts to filter the relevant names in Main
to those used in your script.
ExplicitImports.print_explicit_imports_script
— Functionprint_explicit_imports_script([io::IO=stdout,] path; skip=(Base, Core), warn_stale=true)
Analyzes the script located at path
and prints information about reliance on implicit exports as well as any stale explicit imports (if warn_stale=true
).
The script (or at least, all imports in the script) must be run before this function can give reliable results, since it relies on introspecting what names are present in Main
.
Keyword arguments
skip=(mod, Base, Core)
: any names coming from the listed modules (or any submodules thereof) will be skipped. Sincemod
is included by default, implicit imports of names exported from its own submodules will not count by default.warn_stale=true
: if set, this function will also print information about stale explicit imports.
Non-recursive variants
The above functions all recurse through submodules of the provided module, providing information about each. Here, we provide non-recursive variants (which in fact power the recursive ones), in case it is useful, perhaps for building other tooling on top of ExplicitImports.jl.
ExplicitImports.explicit_imports_nonrecursive
— Functionexplicit_imports_nonrecursive(mod::Module, file=pathof(mod); skip=(mod, Base, Core), warn_stale=true, strict=true)
A non-recursive version of explicit_imports
, meaning it only analyzes the module mod
itself, not any of its submodules; see that function for details.
Keyword arguments
skip=(mod, Base, Core)
: any names coming from the listed modules (or any submodules thereof) will be skipped. Sincemod
is included by default, implicit imports of names exported from its own submodules will not count by default.warn_stale=true
: whether or not to warn about stale explicit imports.strict=true
: whenstrict=true
, results will benothing
in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.
ExplicitImports.stale_explicit_imports_nonrecursive
— Functionstale_explicit_imports_nonrecursive(mod::Module, file=pathof(mod); strict=true)
A non-recursive version of stale_explicit_imports
, meaning it only analyzes the module mod
itself, not any of its submodules.
If mod
was unanalyzable and strict=true
, returns nothing
. Otherwise, returns a collection of NamedTuple
's, with at least the keys name
and location
, corresponding to the names of stale explicit imports. More keys may be added in the future in non-breaking releases of ExplicitImports.jl.
Keyword arguments
strict=true
: whenstrict=true
, results will benothing
in the case that the analysis could not be performed accurately, due to e.g. dynamicinclude
statements. Whenstrict=false
, results are returned in all cases, but may be inaccurate.
See also print_explicit_imports
and check_no_stale_explicit_imports
, both of which do recurse through submodules.