Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 42a8ec7

Browse files
committed
improve derivation of lines from Sexp
This started as a hunt into why an issue was reported for just a single line saying `end`, with the only other instance of the duplication being the same line: the source code in the spec is a reduced instance of the triggering code. The poor behavior results from how RubyParser/Sexp handle certain structures: the "line" of the `rescue..end` expression is parsed as the location of the `end`. This seems wrong, but I can't figure out a quick fix within those libraries. The way child sexprs get lines seems generally sane: at the very small level, lines seem pretty reliable. Strange behavior emerges at the higher level. The way we were calculating the end line of an sexpr already implicitly acknowledged this by walking the whole tree to find the highest line. This change adapts that previous algorithm to apply the same principle to finding both the start & end lines: the start line is basically the smallest line of the sexpr or any of its children, and the end line is the maximum.
1 parent a04e138 commit 42a8ec7

File tree

4 files changed

+82
-14
lines changed

4 files changed

+82
-14
lines changed

lib/cc/engine/analyzers/sexp_lines.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module CC
2+
module Engine
3+
module Analyzers
4+
class SexpLines
5+
attr_reader :begin_line, :end_line
6+
7+
def initialize(root_sexp)
8+
@root_sexp = root_sexp
9+
calculate
10+
end
11+
12+
def to_h
13+
{"begin": begin_line, "end": end_line}
14+
end
15+
16+
private
17+
18+
attr_reader :root_sexp
19+
20+
def calculate
21+
@begin_line = root_sexp.line
22+
@end_line = root_sexp.end_line || root_sexp.line
23+
24+
root_sexp.deep_each do |sexp|
25+
@begin_line = [@begin_line, sexp.line].min
26+
@end_line = [@end_line, sexp.end_line || sexp.line].max
27+
end
28+
end
29+
end
30+
end
31+
end
32+
end

lib/cc/engine/analyzers/violation.rb

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,10 @@ def format_other_locations
7171
def format_sexp(sexp)
7272
{
7373
"path": sexp.file.gsub(%r(^./), ""),
74-
"lines": {
75-
"begin": sexp.line,
76-
"end": sexp.end_line || sexp_max_line(sexp, sexp.line)
77-
}
74+
"lines": SexpLines.new(sexp).to_h,
7875
}
7976
end
8077

81-
def sexp_max_line(sexp_tree, default)
82-
max = default
83-
84-
sexp_tree.deep_each do |sexp|
85-
max = sexp.line if sexp.line > max
86-
end
87-
88-
max
89-
end
90-
9178
def content_body
9279
@_content_body ||= { "body": File.read(read_up_path) }
9380
end

lib/cc/engine/duplication.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'cc/engine/analyzers/reporter'
77
require 'cc/engine/analyzers/engine_config'
88
require 'cc/engine/analyzers/sexp'
9+
require 'cc/engine/analyzers/sexp_lines'
910
require 'flay'
1011
require 'json'
1112

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require "spec_helper"
2+
require "cc/engine/duplication"
3+
4+
module CC::Engine::Analyzers
5+
RSpec.describe SexpLines do
6+
let(:source) do
7+
<<-SOURCE
8+
begin
9+
foo
10+
rescue SyntaxError => e
11+
Jekyll.logger.warn "YAML Exception reading \#{File.join(base, name)}: \#{e.message}"
12+
rescue Exception => e
13+
Jekyll.logger.warn "Error reading file \#{File.join(base, name)}: \#{e.message}"
14+
end
15+
SOURCE
16+
end
17+
let(:flay) do
18+
Flay.new({
19+
diff: false,
20+
mass: CC::Engine::Analyzers::Ruby::Main::DEFAULT_MASS_THRESHOLD,
21+
summary: false,
22+
verbose: false,
23+
number: true,
24+
timeout: 10,
25+
liberal: false,
26+
fuzzy: false,
27+
only: nil,
28+
})
29+
end
30+
31+
describe "violation location" do
32+
it "gets appropriate locations for rescue blocks" do
33+
sexp = RubyParser.new.process(source, "file.rb")
34+
flay.process_sexp(sexp)
35+
36+
report = flay.analyze[0]
37+
sexps = flay.hashes[report.structural_hash]
38+
39+
locations = sexps.map { |sexp| SexpLines.new(sexp) }
40+
41+
expect(locations.count).to eq 2
42+
expect(locations[0].to_h).not_to eq locations[1].to_h
43+
expect(locations[0].to_h).to eq({:begin => 3, :end => 7})
44+
expect(locations[1].to_h).to eq({:begin => 5, :end => 7})
45+
end
46+
end
47+
end
48+
end

0 commit comments

Comments
 (0)