diff --git a/src/layer_static_lint.jl b/src/layer_static_lint.jl index b73e7e9..0234b1d 100644 --- a/src/layer_static_lint.jl +++ b/src/layer_static_lint.jl @@ -193,6 +193,18 @@ Salsa.@derived function derived_static_lint_diagnostics_for_root(rt, root) workspace_packages = lint_result.workspace_packages env = derived_environment(rt, project_uri) + # Names the project declares as dependencies. An unresolved import of one of + # these is a dependency whose symbols could not be indexed/cached (it would + # otherwise be in the env and resolve), as opposed to a name that simply + # isn't in the environment (typo / missing dependency). + project = derived_project(rt, project_uri) + declared_deps = project === nothing ? Set{String}() : + Set{String}(Iterators.flatten(( + keys(project.regular_packages), + keys(project.stdlib_packages), + keys(project.deved_packages), + ))) + for uri in derived_include_closure(rt, root) current_res = get!(res, uri, Set{Diagnostic}()) @@ -209,12 +221,13 @@ Salsa.@derived function derived_static_lint_diagnostics_for_root(rt, root) push!(current_res, Diagnostic(rng, :warning, "Missing reference: $(err[2].val)", nothing, Symbol[], "StaticLint.jl")) elseif StaticLint.haserror(err[2], meta_dict) && StaticLint.errorof(err[2], meta_dict) === StaticLint.UnresolvedImport name = CSTParser.str_value(err[2]) - msg = if StaticLint.is_in_wildcard_import(err[2]) - "Failed to resolve `$name`. Missing-reference checks are disabled in this scope and all nested scopes." - else - "Failed to resolve `$name`. Anything imported through this statement is assumed to exist and will not be checked." - end - push!(current_res, Diagnostic(rng, :warning, msg, nothing, Symbol[], "StaticLint.jl")) + cause = name in declared_deps ? + "`$name` is a declared dependency but its symbols could not be indexed." : + "Failed to resolve `$name`." + consequence = StaticLint.is_in_wildcard_import(err[2]) ? + "Missing-reference checks are disabled in this scope and all nested scopes." : + "Anything imported through this statement is assumed to exist and will not be checked." + push!(current_res, Diagnostic(rng, :warning, "$cause $consequence", nothing, Symbol[], "StaticLint.jl")) elseif StaticLint.haserror(err[2], meta_dict) && StaticLint.errorof(err[2], meta_dict) isa StaticLint.LintCodes code = StaticLint.errorof(err[2], meta_dict) description = get(StaticLint.LintCodeDescriptions, code, "") diff --git a/test/test_diagnostics.jl b/test/test_diagnostics.jl index 42b3e62..c1f44d7 100644 --- a/test/test_diagnostics.jl +++ b/test/test_diagnostics.jl @@ -1428,6 +1428,51 @@ end @test any(d -> startswith(d.message, "Failed to resolve `ActuallyUnresolved`"), diags) end +@testitem "unresolved import: declared-but-uncacheable dep vs unknown name" begin + using JuliaWorkspaces.URIs2: URI + + # `DeclaredButUncached` is listed in the manifest (so it's a declared + # dependency) but no symbols are cached for it, so it never enters the env. + # `TotallyUnknownPkg` is not declared anywhere. + project_toml = """ + name = "UncachedDep" + uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee41" + version = "0.1.0" + + [deps] + DeclaredButUncached = "12345678-1234-1234-1234-123456789abc" + """ + manifest_toml = """ + julia_version = "1.11.0" + manifest_format = "2.0" + project_hash = "abc123" + + [[deps.DeclaredButUncached]] + git-tree-sha1 = "0000000000000000000000000000000000000000" + uuid = "12345678-1234-1234-1234-123456789abc" + version = "1.0.0" + """ + source = """ + module UncachedDep + import DeclaredButUncached: foo + using TotallyUnknownPkg + end + """ + + jw = JuliaWorkspace() + add_file!(jw, TextFile(URI("file:///uncacheddep/Project.toml"), SourceText(project_toml, "toml"))) + add_file!(jw, TextFile(URI("file:///uncacheddep/Manifest.toml"), SourceText(manifest_toml, "toml"))) + add_file!(jw, TextFile(URI("file:///uncacheddep/src/UncachedDep.jl"), SourceText(source, "julia"))) + JuliaWorkspaces.set_input_env_ready!(jw.runtime, true) + + diags = get_diagnostic(jw, URI("file:///uncacheddep/src/UncachedDep.jl")) + + # Declared dependency: message attributes the failure to indexing/caching + @test any(d -> d.message == "`DeclaredButUncached` is a declared dependency but its symbols could not be indexed. Anything imported through this statement is assumed to exist and will not be checked.", diags) + # Undeclared name: keeps the generic "Failed to resolve" wording + @test any(d -> d.message == "Failed to resolve `TotallyUnknownPkg`. Missing-reference checks are disabled in this scope and all nested scopes.", diags) +end + @testitem "missing-refs: default is all (getfield refs checked)" begin using JuliaWorkspaces.URIs2: URI