diff --git a/src/StaticLint.jl b/src/StaticLint.jl index c13028e1..8dad80de 100644 --- a/src/StaticLint.jl +++ b/src/StaticLint.jl @@ -64,10 +64,11 @@ mutable struct Toplevel{T} <: State env::ExternalEnv server flags::Int + forced_references::Set{String} end Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server) = - Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server, 0) + Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server, 0, Set{String}()) function (state::Toplevel)(x::EXPR) resolve_import(x, state) @@ -105,9 +106,10 @@ mutable struct Delayed <: State env::ExternalEnv server flags::Int + forced_references::Set{String} end -Delayed(scope, env, server) = Delayed(scope, env, server, 0) +Delayed(scope, env, server) = Delayed(scope, env, server, 0, Set{String}()) function (state::Delayed)(x::EXPR) mark_bindings!(x, state) @@ -134,8 +136,11 @@ mutable struct ResolveOnly <: State scope::Scope env::ExternalEnv server + forced_references::Set{String} end +ResolveOnly(scope,env,server) = ResolveOnly(scope,env,server,Set{String}()) + function (state::ResolveOnly)(x::EXPR) if hasscope(x) s0 = state.scope @@ -163,6 +168,26 @@ function flag!(state, x::EXPR) return old end +function add_forced_references!(file, current::Set{String}) + + if :_text_document in propertynames(file) # 1.56.2 + source = file._text_document._content + elseif :_content in propertynames(file) # 1.38.2 + source = file._content + elseif :source in propertynames(file) + source = file.source + else + source = "" + end + + linter_rows = collect(eachmatch(r"(^|\n)#@linter_refs (?.*)", source)) + for r in linter_rows + for ref in split(r[:refs], ","; keepempty=false) + push!(current, strip(ref)) + end + end +end + """ semantic_pass(file, modified_expr=nothing) @@ -171,26 +196,30 @@ Performs a semantic pass across a project from the entry point `file`. A first p function semantic_pass(file, modified_expr = nothing) server = file.server env = getenv(file, server) + + forced_references = Set{String}() + add_forced_references!(file, forced_references) + setscope!(getcst(file), Scope(nothing, getcst(file), Dict(), Dict{Symbol,Any}(:Base => env.symbols[:Base], :Core => env.symbols[:Core]), nothing)) - state = Toplevel(file, [getpath(file)], scopeof(getcst(file)), modified_expr === nothing, modified_expr, EXPR[], EXPR[], env, server) + state = Toplevel(file, [getpath(file)], scopeof(getcst(file)), modified_expr === nothing, modified_expr, EXPR[], EXPR[], env, server, 0, forced_references) state(getcst(file)) for x in state.delayed if hasscope(x) - traverse(x, Delayed(scopeof(x), env, server)) + traverse(x, Delayed(scopeof(x), env, server, 0, state.forced_references)) for (k, b) in scopeof(x).names infer_type_by_use(b, env) check_unused_binding(b, scopeof(x)) end else - traverse(x, Delayed(retrieve_delayed_scope(x), env, server)) + traverse(x, Delayed(retrieve_delayed_scope(x), env, server, 0, state.forced_references)) end end if state.resolveonly !== nothing for x in state.resolveonly if hasscope(x) - traverse(x, ResolveOnly(scopeof(x), env, server)) + traverse(x, ResolveOnly(scopeof(x), env, server, state.forced_references)) else - traverse(x, ResolveOnly(retrieve_delayed_scope(x), env, server)) + traverse(x, ResolveOnly(retrieve_delayed_scope(x), env, server, state.forced_references)) end end end @@ -331,6 +360,7 @@ function followinclude(x, state::State) push!(state.included_files, getpath(state.file)) setroot(state.file, getroot(oldfile)) setscope!(getcst(state.file), nothing) + add_forced_references!(state.file, state.forced_references) state(getcst(state.file)) state.file = oldfile pop!(state.included_files) diff --git a/src/references.jl b/src/references.jl index 582242be..97a2ae55 100644 --- a/src/references.jl +++ b/src/references.jl @@ -80,6 +80,12 @@ function resolve_ref(x::EXPR, scope::Scope, state::State)::Bool if !resolved && !CSTParser.defines_module(scope.expr) && parentof(scope) isa Scope return resolve_ref(x, parentof(scope), state) end + + if valof(x) in state.forced_references + setref!(x, Binding(noname, nothing, nothing, [])) + return true + end + return resolved end