Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion lib/typeprof/core/ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,44 @@ def self.parse_rb(path, src)
cref = CRef::Toplevel
lenv = LocalEnv.new(file_context, cref, {}, [])

ProgramNode.new(raw_scope, lenv)
ignore_ranges = collect_ignore_ranges(result)
ProgramNode.new(raw_scope, lenv, ignore_ranges: ignore_ranges)
end

# Collect line ranges marked with `# typeprof:ignore` comments.
# Each range is suppressed in ProgramNode#each_diagnostic.
#
# Inline form (suppresses the line containing the comment):
# foo(1, 2) # typeprof:ignore
#
# Block form (suppresses lines between :start and :end):
# # typeprof:ignore:start
# foo(1, 2)
# # typeprof:ignore:end
#
# An unmatched `:start` extends to the end of the file.
IGNORE_RE = /\A#\s*typeprof:ignore\s*\z/
IGNORE_START_RE = /\A#\s*typeprof:ignore:start\s*\z/
IGNORE_END_RE = /\A#\s*typeprof:ignore:end\s*\z/
def self.collect_ignore_ranges(prism_result)
ranges = []
start_line = nil
prism_result.comments.each do |c|
text = c.location.slice
line = c.location.start_line
if text.match?(IGNORE_START_RE)
start_line ||= line
elsif text.match?(IGNORE_END_RE)
if start_line
ranges << (start_line..line)
start_line = nil
end
elsif text.match?(IGNORE_RE)
ranges << (line..line)
end
end
ranges << (start_line..Float::INFINITY) if start_line
ranges
end

#: (untyped, TypeProf::Core::LocalEnv, ?bool, ?bool) -> TypeProf::Core::AST::Node
Expand Down
14 changes: 12 additions & 2 deletions lib/typeprof/core/ast/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,20 +211,30 @@ def pretty_print_instance_variables
end

class ProgramNode < Node
def initialize(raw_node, lenv)
def initialize(raw_node, lenv, ignore_ranges: [])
super(raw_node, lenv)

@tbl = raw_node.locals
@ignore_ranges = ignore_ranges
raw_body = raw_node.statements

@body = AST.create_node(raw_body, lenv, false)
end

attr_reader :tbl, :body
attr_reader :tbl, :ignore_ranges, :body

def subnodes = { body: }
def attrs = { tbl: }

def each_diagnostic(genv, &blk)
return super if @ignore_ranges.empty?
super(genv) do |diag|
line = diag.code_range&.first&.lineno
next if line && @ignore_ranges.any? { |r| r.cover?(line) }
blk.call(diag)
end
end

def install0(genv)
@tbl.each {|var| @lenv.locals[var] = Source.new(genv.nil_type) }
@lenv.locals[:"*self"] = lenv.cref.get_self(genv)
Expand Down
22 changes: 22 additions & 0 deletions test/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ def check: -> :ok
END
end

def test_e2e_ignore_directive
assert_equal(<<~END, test_run("ignore_directive", ["--show-error", "."]))
# TypeProf #{ TypeProf::VERSION }

# ./ignore_directive.rb
class Object
def check: -> :ok
end
END
end

def test_e2e_ignore_directive_block
assert_equal(<<~END, test_run("ignore_directive_block", ["--show-error", "."]))
# TypeProf #{ TypeProf::VERSION }

# ./ignore_directive_block.rb
class Object
def check: -> :ok
end
END
end

def test_e2e_syntax_error
assert_equal(<<~END, test_run("syntax_error", ["."]))
# TypeProf #{ TypeProf::VERSION }
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/ignore_directive/ignore_directive.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def check
Foo.new.accept_int("str") # typeprof:ignore
end
3 changes: 3 additions & 0 deletions test/fixtures/ignore_directive/ignore_directive.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo
def accept_int: (Integer) -> :ok
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def check
# typeprof:ignore:start
Foo.new.accept_int("str")
Foo.new.accept_int("str")
# typeprof:ignore:end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo
def accept_int: (Integer) -> :ok
end
83 changes: 83 additions & 0 deletions test/lsp/lsp_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,89 @@ def foo(nnn)
end
end

def test_diagnostics_ignore_directive
init("basic")

notify(
"textDocument/didOpen",
textDocument: { uri: @folder + "basic.rb", version: 0, text: <<-END },
def foo(nnn)
nnn
end

foo(1, 2)
foo(1, 2) # typeprof:ignore
foo(1, 2)
END
)

expect_notification("typeprof.enableToggleButton") {|json| }
expect_request("workspace/codeLens/refresh") {|json| }
expect_notification("textDocument/publishDiagnostics") do |json|
assert_equal({
uri: @folder + "basic.rb",
diagnostics: [
{
message: "wrong number of arguments (2 for 1)",
range: {
start: { line: 4, character: 0 },
end: { line: 4, character: 3 },
},
severity: 1,
source: "TypeProf",
},
{
message: "wrong number of arguments (2 for 1)",
range: {
start: { line: 6, character: 0 },
end: { line: 6, character: 3 },
},
severity: 1,
source: "TypeProf",
}
],
}, json)
end
end

def test_diagnostics_ignore_directive_block
init("basic")

notify(
"textDocument/didOpen",
textDocument: { uri: @folder + "basic.rb", version: 0, text: <<-END },
def foo(nnn)
nnn
end

# typeprof:ignore:start
foo(1, 2)
foo(1, 2)
# typeprof:ignore:end
foo(1, 2)
END
)

expect_notification("typeprof.enableToggleButton") {|json| }
expect_request("workspace/codeLens/refresh") {|json| }
expect_notification("textDocument/publishDiagnostics") do |json|
assert_equal({
uri: @folder + "basic.rb",
diagnostics: [
{
message: "wrong number of arguments (2 for 1)",
range: {
start: { line: 8, character: 0 },
end: { line: 8, character: 3 },
},
severity: 1,
source: "TypeProf",
}
],
}, json)
end
end

def test_diagnostics2
init("basic")

Expand Down
Loading