From b06d41ec3f03d380c7307e4f6b8457a0fa3791e6 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Sat, 24 Feb 2024 21:06:05 +0000 Subject: [PATCH] build based on 25b9f4d --- dev/.documenter-siteinfo.json | 2 +- dev/api/index.html | 2 +- dev/index.html | 2 +- dev/internals/index.html | 2 +- dev/search_index.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 5727af0..790a375 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-24T19:42:12","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-24T21:06:02","documenter_version":"1.2.1"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index cfa7d55..deaded9 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,2 +1,2 @@ -API reference · ExplicitImports.jl

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.

ExplicitImports.explicit_importsFunction
explicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=true)

Returns a nested structure providing information about explicit import statements one could make for each submodule of mod.

  • file=pathof(mod): this should be a path to the source code that contains the module mod.
    • if mod is not from a package, pathof will be unable to find the code, and a file must be passed which contains mod (either directly or indirectly through includes)
    • mod can be a submodule defined within file, but if two modules have the same name (e.g. X.Y.X and X), results may be inaccurate.
  • skips=(Base, Core): any names coming from the listed modules (or any submodules thereof) will be skipped.
  • warn=true: whether or not to warn about stale explicit imports.

See also print_explicit_imports to easily compute and print these results, and explicit_imports_single for a non-recursive version which ignores submodules.

source

Usage 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_importsFunction
check_no_implicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=false)

Checks that neither mod nor any of its submodules is relying on implicit imports, throwing an ImplicitImportsException if so, and returning nothing otherwise.

This can be used in a package's tests, e.g.

@test check_no_implicit_imports(MyPackage) === nothing
source
ExplicitImports.check_no_stale_explicit_importsFunction
check_no_stale_explicit_imports(mod, file=pathof(mod))

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
source
+API reference · ExplicitImports.jl

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.

ExplicitImports.explicit_importsFunction
explicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=true)

Returns a nested structure providing information about explicit import statements one could make for each submodule of mod.

  • file=pathof(mod): this should be a path to the source code that contains the module mod.
    • if mod is not from a package, pathof will be unable to find the code, and a file must be passed which contains mod (either directly or indirectly through includes)
    • mod can be a submodule defined within file, but if two modules have the same name (e.g. X.Y.X and X), results may be inaccurate.
  • skips=(Base, Core): any names coming from the listed modules (or any submodules thereof) will be skipped.
  • warn=true: whether or not to warn about stale explicit imports.

See also print_explicit_imports to easily compute and print these results, and explicit_imports_single for a non-recursive version which ignores submodules.

source

Usage 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_importsFunction
check_no_implicit_imports(mod, file=pathof(mod); skips=(Base, Core), ignore = (), warn=false)

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 implicit imports

The skips keyword argument can be passed to allow implicit imports from some modules (and their submodules). By default, skips is set to (Base, Core). For example:

@test check_no_implicit_imports(MyPackage; skips=(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 collection of items to ignore. These can be:

  • modules. Any submodule of mod matching an element of ignore 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:

  1. Ignore any implicit import of DataFrame from DataFrames
  2. Ignore any implicit import of the name ByRow from any module.
  3. Ignore any implicit imports present in MyPackage's submodule MySubModule

but verify there are no other implicit imports.

source
ExplicitImports.check_no_stale_explicit_importsFunction
check_no_stale_explicit_imports(mod, file=pathof(mod); ignore=())

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 stale explicit imports

If ignore is supplied, it should be a collection of Symbols, 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.

source
diff --git a/dev/index.html b/dev/index.html index 38a1350..4afb1b0 100644 --- a/dev/index.html +++ b/dev/index.html @@ -23,4 +23,4 @@

Note: the WARNING is more or less harmless; the way this package is written, it will happen any time there is a clash, even if that clash is not realized in your code. I cannot figure out how to suppress it.

Limitations

global scope quantifier ignored

Currently, my parsing implementation does not take into account the global keyword, and thus results may be inaccurate when that is used. This could be fixed by improving the code in src/get_names_used.jl.

Cannot recurse through dynamic include statements

These are include in which the argument is not a string literal. For example:

julia> print_explicit_imports(MathOptInterface)
 ┌ Warning: Dynamic `include` found at /Users/eph/.julia/packages/MathOptInterface/tpiUw/src/Test/Test.jl:631:9; not recursing
 └ @ ExplicitImports ~/ExplicitImports/src/get_names_used.jl:37
-...

In this case, names in files which are included via include are not analyzed while parsing. This can result in inaccurate results, such as false positives in explicit_imports and false negatives (or false positives) in stale_explicit_imports.

This is essentially an inherent limitation of the approach used in this package. An alternate implementation using an AbstractInterpreter (like JET does) may be able to handle this (at the cost of increased complexity).

Documentation Index

+...

In this case, names in files which are included via include are not analyzed while parsing. This can result in inaccurate results, such as false positives in explicit_imports and false negatives (or false positives) in stale_explicit_imports.

This is essentially an inherent limitation of the approach used in this package. An alternate implementation using an AbstractInterpreter (like JET does) may be able to handle this (at the cost of increased complexity).

Documentation Index

diff --git a/dev/internals/index.html b/dev/internals/index.html index e0c1005..563d611 100644 --- a/dev/internals/index.html +++ b/dev/internals/index.html @@ -1,2 +1,2 @@ -Dev docs · ExplicitImports.jl

Implementation strategy

  1. [DONE hackily] Figure out what names used in the module are being used to refer to bindings in global scope (as opposed to e.g. shadowing globals).
    • We do this by parsing the code (thanks to JuliaSyntax), then reimplementing scoping rules on top of the parse tree
    • This is finicky, but assuming scoping doesn't change, should be robust enough (once the long tail of edge cases are dealt with...)
      • Currently, I don't handle the global keyword, so those may look like local variables and confuse things
    • This means we need access to the raw source code; pathof works well for packages, but for local modules one has to pass the path themselves. Also doesn't seem to work well for stdlibs in the sysimage
  2. [DONE] Figure out what implicit imports are available in the module, and which module they come from
    • done, via a magic ccall from Discourse, and Base.which.
  3. [DONE] Figure out which names have been explicitly imported already
    • Done via parsing

Then we can put this information together to figure out what names are actually being used from other modules, and whose usage could be made explicit, and also which existing explicit imports are not being used.

Internals

ExplicitImports.find_implicit_importsFunction
find_implicit_imports(mod::Module; skips=(Base, Core))

Given a module mod, returns a Dict{Symbol, Module} showing names exist in mod's namespace which are available due to implicit exports by other modules. The dict's keys are those names, and the values are the module that the name comes from.

In the case of ambiguities (two modules exporting the same name), the name is unavailable in the module, and hence the name will not be present in the dict.

This is powered by Base.which.

source
ExplicitImports.get_names_usedFunction
get_names_used(file) -> DataFrame

Figures out which global names are used in file, and what modules they are used within.

Traverses static include statements.

Returns a DataFrame with four columns:

  • name: the name in question
  • module_path: the path of modules to access the name, where the first module in the path is the innermost.
  • needs_explicit_import::Bool
  • unnecessary_explicit_import::Bool
source
ExplicitImports.analyze_all_namesFunction
analyze_all_names(file) -> Tuple{DataFrame, DataFrame}

Returns:

  • a DataFrame with one row per name per scope, with information about whether or not it is within global scope, what modules it is in, and whether or not it was assigned before ever being used in that scope.
  • a DataFrame with one row per name per module path, consisting of names that have been explicitly imported in that module.
source
+Dev docs · ExplicitImports.jl

Implementation strategy

  1. [DONE hackily] Figure out what names used in the module are being used to refer to bindings in global scope (as opposed to e.g. shadowing globals).
    • We do this by parsing the code (thanks to JuliaSyntax), then reimplementing scoping rules on top of the parse tree
    • This is finicky, but assuming scoping doesn't change, should be robust enough (once the long tail of edge cases are dealt with...)
      • Currently, I don't handle the global keyword, so those may look like local variables and confuse things
    • This means we need access to the raw source code; pathof works well for packages, but for local modules one has to pass the path themselves. Also doesn't seem to work well for stdlibs in the sysimage
  2. [DONE] Figure out what implicit imports are available in the module, and which module they come from
    • done, via a magic ccall from Discourse, and Base.which.
  3. [DONE] Figure out which names have been explicitly imported already
    • Done via parsing

Then we can put this information together to figure out what names are actually being used from other modules, and whose usage could be made explicit, and also which existing explicit imports are not being used.

Internals

ExplicitImports.find_implicit_importsFunction
find_implicit_imports(mod::Module; skips=(Base, Core))

Given a module mod, returns a Dict{Symbol, Module} showing names exist in mod's namespace which are available due to implicit exports by other modules. The dict's keys are those names, and the values are the module that the name comes from.

In the case of ambiguities (two modules exporting the same name), the name is unavailable in the module, and hence the name will not be present in the dict.

This is powered by Base.which.

source
ExplicitImports.get_names_usedFunction
get_names_used(file) -> DataFrame

Figures out which global names are used in file, and what modules they are used within.

Traverses static include statements.

Returns a DataFrame with four columns:

  • name: the name in question
  • module_path: the path of modules to access the name, where the first module in the path is the innermost.
  • needs_explicit_import::Bool
  • unnecessary_explicit_import::Bool
source
ExplicitImports.analyze_all_namesFunction
analyze_all_names(file) -> Tuple{DataFrame, DataFrame}

Returns:

  • a DataFrame with one row per name per scope, with information about whether or not it is within global scope, what modules it is in, and whether or not it was assigned before ever being used in that scope.
  • a DataFrame with one row per name per module path, consisting of names that have been explicitly imported in that module.
source
diff --git a/dev/search_index.js b/dev/search_index.js index 17ef8b2..eb497b6 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"api/#API","page":"API reference","title":"API","text":"","category":"section"},{"location":"api/","page":"API reference","title":"API reference","text":"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.","category":"page"},{"location":"api/","page":"API reference","title":"API reference","text":"print_explicit_imports\nexplicit_imports\nstale_explicit_imports\nexplicit_imports_single","category":"page"},{"location":"api/#ExplicitImports.print_explicit_imports","page":"API reference","title":"ExplicitImports.print_explicit_imports","text":"print_explicit_imports([io::IO=stdout,] mod, file=pathof(mod); kw...)\n\nRuns explicit_imports and prints the results, along with those of stale_explicit_imports. Accepts the same keyword arguments as that function.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.explicit_imports","page":"API reference","title":"ExplicitImports.explicit_imports","text":"explicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=true)\n\nReturns a nested structure providing information about explicit import statements one could make for each submodule of mod.\n\nfile=pathof(mod): this should be a path to the source code that contains the module mod.\nif mod is not from a package, pathof will be unable to find the code, and a file must be passed which contains mod (either directly or indirectly through includes)\nmod can be a submodule defined within file, but if two modules have the same name (e.g. X.Y.X and X), results may be inaccurate.\nskips=(Base, Core): any names coming from the listed modules (or any submodules thereof) will be skipped.\nwarn=true: whether or not to warn about stale explicit imports.\n\nSee also print_explicit_imports to easily compute and print these results, and explicit_imports_single for a non-recursive version which ignores submodules.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.stale_explicit_imports","page":"API reference","title":"ExplicitImports.stale_explicit_imports","text":"stale_explicit_imports(mod, file=pathof(mod)) -> Vector{Symbol}\n\nReturns a list of names that are not used in mod, but are still explicitly imported.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.explicit_imports_single","page":"API reference","title":"ExplicitImports.explicit_imports_single","text":"explicit_imports_single(mod, file=pathof(mod); skips=(Base, Core), warn=true)\n\nA non-recursive version of explicit_imports; see that function for details.\n\n\n\n\n\n","category":"function"},{"location":"api/#Usage-in-testing","page":"API reference","title":"Usage in testing","text":"","category":"section"},{"location":"api/","page":"API reference","title":"API reference","text":"ExplicitImports.jl provides two functions which can be used to regression test that there is no reliance on implicit imports or stale explicit imports:","category":"page"},{"location":"api/","page":"API reference","title":"API reference","text":"check_no_implicit_imports\ncheck_no_stale_explicit_imports","category":"page"},{"location":"api/#ExplicitImports.check_no_implicit_imports","page":"API reference","title":"ExplicitImports.check_no_implicit_imports","text":"check_no_implicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=false)\n\nChecks that neither mod nor any of its submodules is relying on implicit imports, throwing an ImplicitImportsException if so, and returning nothing otherwise.\n\nThis can be used in a package's tests, e.g.\n\n@test check_no_implicit_imports(MyPackage) === nothing\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.check_no_stale_explicit_imports","page":"API reference","title":"ExplicitImports.check_no_stale_explicit_imports","text":"check_no_stale_explicit_imports(mod, file=pathof(mod))\n\nChecks that neither mod nor any of its submodules has stale (unused) explicit imports, throwing an StaleImportsException if so, and returning nothing otherwise.\n\nThis can be used in a package's tests, e.g.\n\n@test check_no_stale_explicit_imports(MyPackage) === nothing\n\n\n\n\n\n","category":"function"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = ExplicitImports","category":"page"},{"location":"","page":"Home","title":"Home","text":"using ExplicitImports, Markdown\ncontents = read(joinpath(pkgdir(ExplicitImports), \"README.md\"), String)\ncontents = replace(contents, \"[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://ericphanson.github.io/ExplicitImports.jl/dev/)\" => \"\")\nMarkdown.parse(contents)","category":"page"},{"location":"#Documentation-Index","page":"Home","title":"Documentation Index","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"internals/#Implementation-strategy","page":"Dev docs","title":"Implementation strategy","text":"","category":"section"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"[DONE hackily] Figure out what names used in the module are being used to refer to bindings in global scope (as opposed to e.g. shadowing globals).\nWe do this by parsing the code (thanks to JuliaSyntax), then reimplementing scoping rules on top of the parse tree\nThis is finicky, but assuming scoping doesn't change, should be robust enough (once the long tail of edge cases are dealt with...)\nCurrently, I don't handle the global keyword, so those may look like local variables and confuse things\nThis means we need access to the raw source code; pathof works well for packages, but for local modules one has to pass the path themselves. Also doesn't seem to work well for stdlibs in the sysimage\n[DONE] Figure out what implicit imports are available in the module, and which module they come from\ndone, via a magic ccall from Discourse, and Base.which.\n[DONE] Figure out which names have been explicitly imported already\nDone via parsing","category":"page"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"Then we can put this information together to figure out what names are actually being used from other modules, and whose usage could be made explicit, and also which existing explicit imports are not being used.","category":"page"},{"location":"internals/#Internals","page":"Dev docs","title":"Internals","text":"","category":"section"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"ExplicitImports.find_implicit_imports\nExplicitImports.get_names_used\nExplicitImports.analyze_all_names","category":"page"},{"location":"internals/#ExplicitImports.find_implicit_imports","page":"Dev docs","title":"ExplicitImports.find_implicit_imports","text":"find_implicit_imports(mod::Module; skips=(Base, Core))\n\nGiven a module mod, returns a Dict{Symbol, Module} showing names exist in mod's namespace which are available due to implicit exports by other modules. The dict's keys are those names, and the values are the module that the name comes from.\n\nIn the case of ambiguities (two modules exporting the same name), the name is unavailable in the module, and hence the name will not be present in the dict.\n\nThis is powered by Base.which.\n\n\n\n\n\n","category":"function"},{"location":"internals/#ExplicitImports.get_names_used","page":"Dev docs","title":"ExplicitImports.get_names_used","text":"get_names_used(file) -> DataFrame\n\nFigures out which global names are used in file, and what modules they are used within.\n\nTraverses static include statements.\n\nReturns a DataFrame with four columns:\n\nname: the name in question\nmodule_path: the path of modules to access the name, where the first module in the path is the innermost.\nneeds_explicit_import::Bool\nunnecessary_explicit_import::Bool\n\n\n\n\n\n","category":"function"},{"location":"internals/#ExplicitImports.analyze_all_names","page":"Dev docs","title":"ExplicitImports.analyze_all_names","text":"analyze_all_names(file) -> Tuple{DataFrame, DataFrame}\n\nReturns:\n\na DataFrame with one row per name per scope, with information about whether or not it is within global scope, what modules it is in, and whether or not it was assigned before ever being used in that scope.\na DataFrame with one row per name per module path, consisting of names that have been explicitly imported in that module.\n\n\n\n\n\n","category":"function"}] +[{"location":"api/#API","page":"API reference","title":"API","text":"","category":"section"},{"location":"api/","page":"API reference","title":"API reference","text":"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.","category":"page"},{"location":"api/","page":"API reference","title":"API reference","text":"print_explicit_imports\nexplicit_imports\nstale_explicit_imports\nexplicit_imports_single","category":"page"},{"location":"api/#ExplicitImports.print_explicit_imports","page":"API reference","title":"ExplicitImports.print_explicit_imports","text":"print_explicit_imports([io::IO=stdout,] mod, file=pathof(mod); kw...)\n\nRuns explicit_imports and prints the results, along with those of stale_explicit_imports. Accepts the same keyword arguments as that function.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.explicit_imports","page":"API reference","title":"ExplicitImports.explicit_imports","text":"explicit_imports(mod, file=pathof(mod); skips=(Base, Core), warn=true)\n\nReturns a nested structure providing information about explicit import statements one could make for each submodule of mod.\n\nfile=pathof(mod): this should be a path to the source code that contains the module mod.\nif mod is not from a package, pathof will be unable to find the code, and a file must be passed which contains mod (either directly or indirectly through includes)\nmod can be a submodule defined within file, but if two modules have the same name (e.g. X.Y.X and X), results may be inaccurate.\nskips=(Base, Core): any names coming from the listed modules (or any submodules thereof) will be skipped.\nwarn=true: whether or not to warn about stale explicit imports.\n\nSee also print_explicit_imports to easily compute and print these results, and explicit_imports_single for a non-recursive version which ignores submodules.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.stale_explicit_imports","page":"API reference","title":"ExplicitImports.stale_explicit_imports","text":"stale_explicit_imports(mod, file=pathof(mod)) -> Vector{Symbol}\n\nReturns a list of names that are not used in mod, but are still explicitly imported.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.explicit_imports_single","page":"API reference","title":"ExplicitImports.explicit_imports_single","text":"explicit_imports_single(mod, file=pathof(mod); skips=(Base, Core), warn=true)\n\nA non-recursive version of explicit_imports; see that function for details.\n\n\n\n\n\n","category":"function"},{"location":"api/#Usage-in-testing","page":"API reference","title":"Usage in testing","text":"","category":"section"},{"location":"api/","page":"API reference","title":"API reference","text":"ExplicitImports.jl provides two functions which can be used to regression test that there is no reliance on implicit imports or stale explicit imports:","category":"page"},{"location":"api/","page":"API reference","title":"API reference","text":"check_no_implicit_imports\ncheck_no_stale_explicit_imports","category":"page"},{"location":"api/#ExplicitImports.check_no_implicit_imports","page":"API reference","title":"ExplicitImports.check_no_implicit_imports","text":"check_no_implicit_imports(mod, file=pathof(mod); skips=(Base, Core), ignore = (), warn=false)\n\nChecks that neither mod nor any of its submodules is relying on implicit imports, throwing an ImplicitImportsException if so, and returning nothing otherwise.\n\nThis function can be used in a package's tests, e.g.\n\n@test check_no_implicit_imports(MyPackage) === nothing\n\nAllowing some implicit imports\n\nThe skips keyword argument can be passed to allow implicit imports from some modules (and their submodules). By default, skips is set to (Base, Core). For example:\n\n@test check_no_implicit_imports(MyPackage; skips=(Base, Core, DataFrames)) === nothing\n\nwould verify there are no implicit imports from modules other than Base, Core, and DataFrames.\n\nAdditionally, the keyword ignore can be passed to represent a collection of items to ignore. These can be:\n\nmodules. Any submodule of mod matching an element of ignore is skipped. This can be used to allow the usage of implicit imports in some submodule of your package.\nsymbols: any implicit import of a name matching an element of ignore is ignored (does not throw)\nsymbol => module pairs. Any implicit import of a name matching that symbol from a module matching the module is ignored.\n\nOne can mix and match between these type of ignored elements. For example:\n\n@test check_no_implicit_imports(MyPackage; ignore=(:DataFrame => DataFrames, :ByRow, MySubModule)) === nothing\n\nThis would:\n\nIgnore any implicit import of DataFrame from DataFrames\nIgnore any implicit import of the name ByRow from any module.\nIgnore any implicit imports present in MyPackage's submodule MySubModule\n\nbut verify there are no other implicit imports.\n\n\n\n\n\n","category":"function"},{"location":"api/#ExplicitImports.check_no_stale_explicit_imports","page":"API reference","title":"ExplicitImports.check_no_stale_explicit_imports","text":"check_no_stale_explicit_imports(mod, file=pathof(mod); ignore=())\n\nChecks that neither mod nor any of its submodules has stale (unused) explicit imports, throwing an StaleImportsException if so, and returning nothing otherwise.\n\nThis can be used in a package's tests, e.g.\n\n@test check_no_stale_explicit_imports(MyPackage) === nothing\n\nAllowing some stale explicit imports\n\nIf ignore is supplied, it should be a collection of Symbols, representing names that are allowed to be stale explicit imports. For example,\n\n@test check_no_stale_explicit_imports(MyPackage; ignore=(:DataFrame,)) === nothing\n\nwould check there were no stale explicit imports besides that of the name DataFrame.\n\n\n\n\n\n","category":"function"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = ExplicitImports","category":"page"},{"location":"","page":"Home","title":"Home","text":"using ExplicitImports, Markdown\ncontents = read(joinpath(pkgdir(ExplicitImports), \"README.md\"), String)\ncontents = replace(contents, \"[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://ericphanson.github.io/ExplicitImports.jl/dev/)\" => \"\")\nMarkdown.parse(contents)","category":"page"},{"location":"#Documentation-Index","page":"Home","title":"Documentation Index","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"internals/#Implementation-strategy","page":"Dev docs","title":"Implementation strategy","text":"","category":"section"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"[DONE hackily] Figure out what names used in the module are being used to refer to bindings in global scope (as opposed to e.g. shadowing globals).\nWe do this by parsing the code (thanks to JuliaSyntax), then reimplementing scoping rules on top of the parse tree\nThis is finicky, but assuming scoping doesn't change, should be robust enough (once the long tail of edge cases are dealt with...)\nCurrently, I don't handle the global keyword, so those may look like local variables and confuse things\nThis means we need access to the raw source code; pathof works well for packages, but for local modules one has to pass the path themselves. Also doesn't seem to work well for stdlibs in the sysimage\n[DONE] Figure out what implicit imports are available in the module, and which module they come from\ndone, via a magic ccall from Discourse, and Base.which.\n[DONE] Figure out which names have been explicitly imported already\nDone via parsing","category":"page"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"Then we can put this information together to figure out what names are actually being used from other modules, and whose usage could be made explicit, and also which existing explicit imports are not being used.","category":"page"},{"location":"internals/#Internals","page":"Dev docs","title":"Internals","text":"","category":"section"},{"location":"internals/","page":"Dev docs","title":"Dev docs","text":"ExplicitImports.find_implicit_imports\nExplicitImports.get_names_used\nExplicitImports.analyze_all_names","category":"page"},{"location":"internals/#ExplicitImports.find_implicit_imports","page":"Dev docs","title":"ExplicitImports.find_implicit_imports","text":"find_implicit_imports(mod::Module; skips=(Base, Core))\n\nGiven a module mod, returns a Dict{Symbol, Module} showing names exist in mod's namespace which are available due to implicit exports by other modules. The dict's keys are those names, and the values are the module that the name comes from.\n\nIn the case of ambiguities (two modules exporting the same name), the name is unavailable in the module, and hence the name will not be present in the dict.\n\nThis is powered by Base.which.\n\n\n\n\n\n","category":"function"},{"location":"internals/#ExplicitImports.get_names_used","page":"Dev docs","title":"ExplicitImports.get_names_used","text":"get_names_used(file) -> DataFrame\n\nFigures out which global names are used in file, and what modules they are used within.\n\nTraverses static include statements.\n\nReturns a DataFrame with four columns:\n\nname: the name in question\nmodule_path: the path of modules to access the name, where the first module in the path is the innermost.\nneeds_explicit_import::Bool\nunnecessary_explicit_import::Bool\n\n\n\n\n\n","category":"function"},{"location":"internals/#ExplicitImports.analyze_all_names","page":"Dev docs","title":"ExplicitImports.analyze_all_names","text":"analyze_all_names(file) -> Tuple{DataFrame, DataFrame}\n\nReturns:\n\na DataFrame with one row per name per scope, with information about whether or not it is within global scope, what modules it is in, and whether or not it was assigned before ever being used in that scope.\na DataFrame with one row per name per module path, consisting of names that have been explicitly imported in that module.\n\n\n\n\n\n","category":"function"}] }