diff --git a/Project.toml b/Project.toml index 4e5fd57c..62312cf6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ExplicitImports" uuid = "7d51a73a-1435-4ff3-83d9-f097790105c7" authors = ["Eric P. Hanson"] -version = "1.13.2" +version = "1.14.0" [deps] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" diff --git a/src/checks.jl b/src/checks.jl index 0529c9ba..fc3b2b70 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -340,7 +340,7 @@ end """ check_all_qualified_accesses_are_public(mod::Module, file=pathof(mod); ignore::Tuple=(), skip::$(TUPLE_MODULE_PAIRS)=(Base => Core,), - allow_internal_accesses=true) + from=nothing, allow_internal_accesses=true) Checks that neither `mod` nor any of its submodules has qualified accesses to names which are non-public (i.e. not exported, nor declared public on Julia 1.11+) throwing an `NonPublicQualifiedAccessException` if so, and returning `nothing` otherwise. @@ -372,6 +372,15 @@ that are allowed to be accessed from modules in which they are not public. For e would check there were no non-public qualified accesses besides that of the name `DataFrame`. +Alternatively, the `from` keyword can be used to apply the public names check to only certain modules (and their submodules). +For example, + +```julia +@test check_all_qualified_accesses_are_public(MyPackage; from=(DataFrames,)) === nothing +``` + +would check only that qualified accesses from the `DataFrames` module (or its submodules) are all public. + ## non-fully-analyzable modules do not cause exceptions Note that if a module is not fully analyzable (e.g. it has dynamic `include` calls), qualified accesess of non-public names which could not be analyzed will be missed. Unlike [`check_no_stale_explicit_imports`](@ref) and [`check_no_implicit_imports`](@ref), this function will *not* throw an `UnanalyzableModuleException` in such cases. @@ -380,6 +389,7 @@ See also: [`improper_qualified_accesses`](@ref) for programmatic access and the """ function check_all_qualified_accesses_are_public(mod::Module, file=pathof(mod); skip::TUPLE_MODULE_PAIRS=(Base => Core,), + from=nothing, ignore::Tuple=(), allow_internal_accesses=true) check_file(file) @@ -403,6 +413,14 @@ function check_all_qualified_accesses_are_public(mod::Module, file=pathof(mod); end end + if !isnothing(from) + filter!(problematic) do row + return any(from) do fmod + has_ancestor(row.accessing_from, fmod) + end + end + end + # Discard imports from names that are public in their module; that's OK filter!(problematic) do nt return !nt.public_access @@ -562,7 +580,7 @@ end """ check_all_explicit_imports_are_public(mod::Module, file=pathof(mod); ignore::Tuple=(), skip::$(TUPLE_MODULE_PAIRS)=(Base => Core,), - allow_internal_imports=true) + from=nothing, allow_internal_imports=true) Checks that neither `mod` nor any of its submodules has imports to names which are non-public (i.e. not exported, nor declared public on Julia 1.11+) throwing an `NonPublicExplicitImportsException` if so, and returning `nothing` otherwise. @@ -594,6 +612,15 @@ that are allowed to be imported from modules in which they are not public. For e would check there were no non-public explicit imports besides that of the name `DataFrame`. +Alternatively, the `from` keyword can be used to apply the public names check to only certain modules (and their submodules). +For example, + +```julia +@test check_all_explicit_imports_are_public(MyPackage; from=(DataFrames,)) === nothing +``` + +would check only that explicit imports from the `DataFrames` module (or its submodules) are all public. + ## non-fully-analyzable modules do not cause exceptions Note that if a module is not fully analyzable (e.g. it has dynamic `include` calls), explicit imports of non-public names which could not be analyzed will be missed. Unlike [`check_no_stale_explicit_imports`](@ref) and [`check_no_implicit_imports`](@ref), this function will *not* throw an `UnanalyzableModuleException` in such cases. @@ -602,6 +629,7 @@ See also: [`improper_explicit_imports`](@ref) for programmatic access to such im """ function check_all_explicit_imports_are_public(mod::Module, file=pathof(mod); skip::TUPLE_MODULE_PAIRS=(Base => Core,), + from=nothing, ignore::Tuple=(), allow_internal_imports=true) check_file(file) @@ -620,6 +648,14 @@ function check_all_explicit_imports_are_public(mod::Module, file=pathof(mod); end end + if !isnothing(from) + filter!(problematic) do row + return any(from) do fmod + has_ancestor(row.importing_from, fmod) + end + end + end + # Discard imports from names that are public in their module; that's OK filter!(problematic) do nt return !nt.public_import diff --git a/test/runtests.jl b/test/runtests.jl index 71318408..ef76afe6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -379,6 +379,24 @@ include("issue_129.jl") end @test contains(str, "- `ABC` is not public in") + str = exception_string() do + return check_all_qualified_accesses_are_public(TestQualifiedAccess, + "test_qualified_access.jl"; + from=(LinearAlgebra, + TestQualifiedAccess), + allow_internal_accesses=false) + end + @test contains(str, "- `ABC` is not public in") + @test contains(str, "- `map` is not public in `LinearAlgebra`") + str = exception_string() do + return check_all_qualified_accesses_are_public(TestQualifiedAccess, + "test_qualified_access.jl"; + from=(LinearAlgebra,), + allow_internal_accesses=false) + end + @test !contains(str, "- `ABC` is not public in") + @test contains(str, "- `map` is not public in `LinearAlgebra`") + @test check_all_qualified_accesses_are_public(TestQualifiedAccess, "test_qualified_access.jl"; ignore=(:X, :ABC, :map), @@ -489,6 +507,13 @@ include("issue_129.jl") @test check_all_explicit_imports_are_public(ModImports, "imports.jl"; ignore=(:map, :_svd!)) === nothing + @test check_all_explicit_imports_are_public(ModImports, + "imports.jl"; from=(DataFrames,)) === + nothing + @test_throws NonPublicExplicitImportsException check_all_explicit_imports_are_public(ModImports, + "imports.jl"; + from=(LinearAlgebra, + DataFrames)) end @testset "structs" begin