Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 7d0c2f7

Browse files
dblandinwfleming
authored andcommitted
Add initial Java duplication support
Adds a new Java language strategy, utilizing the new parser backend. Kudos and credit to @ABaldwinHunter and @nporteschaikin on the `SexpBuilder` implementation!! 👍
1 parent 709ad35 commit 7d0c2f7

File tree

8 files changed

+334
-3
lines changed

8 files changed

+334
-3
lines changed

‎Dockerfile‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ RUN chown -R app:app ./
2525

2626
USER app
2727

28-
CMD ["/usr/src/app/bin/duplication"]
28+
ENTRYPOINT ["/usr/src/app/entrypoint"]
29+
CMD ["/usr/src/app/bin/duplication", "/code", "/config.json"]

‎bin/duplication‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib")))
55
require "cc/logger"
66
require "cc/engine/duplication"
77

8+
config_path = ARGV[1] || "/config.json"
9+
810
config =
9-
if File.exist?("/config.json")
10-
JSON.parse(File.read("/config.json"))
11+
if File.exist?(config_path)
12+
JSON.parse(File.read(config_path))
1113
else
1214
{}
1315
end

‎lib/cc/engine/analyzers/java/main.rb‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require "flay"
4+
require "json"
5+
require "cc/engine/analyzers/reporter"
6+
require "cc/engine/analyzers/analyzer_base"
7+
require "cc/engine/processed_source"
8+
require "cc/engine/sexp_builder"
9+
10+
module CC
11+
module Engine
12+
module Analyzers
13+
module Java
14+
class Main < CC::Engine::Analyzers::Base
15+
LANGUAGE = "java".freeze
16+
PATTERNS = ["**/*.java"].freeze
17+
DEFAULT_MASS_THRESHOLD = 40
18+
POINTS_PER_OVERAGE = 10_000
19+
REQUEST_PATH = "/java".freeze
20+
TIMEOUT = 300
21+
22+
private
23+
24+
def process_file(file)
25+
node = ProcessedSource.new(file, REQUEST_PATH).ast
26+
27+
SexpBuilder.new(node, file).build
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end

‎lib/cc/engine/duplication.rb‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "bundler/setup"
44
require "cc/engine/analyzers/ruby/main"
5+
require "cc/engine/analyzers/java/main"
56
require "cc/engine/analyzers/javascript/main"
67
require "cc/engine/analyzers/php/main"
78
require "cc/engine/analyzers/python/main"
@@ -16,6 +17,7 @@ module Engine
1617
class Duplication
1718
LANGUAGES = {
1819
"ruby" => ::CC::Engine::Analyzers::Ruby::Main,
20+
"java" => ::CC::Engine::Analyzers::Java::Main,
1921
"javascript" => ::CC::Engine::Analyzers::Javascript::Main,
2022
"php" => ::CC::Engine::Analyzers::Php::Main,
2123
"python" => ::CC::Engine::Analyzers::Python::Main,

‎lib/cc/engine/processed_source.rb‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require "cc/parser"
2+
3+
module CC
4+
module Engine
5+
class ProcessedSource
6+
attr_reader :path
7+
8+
def initialize(path, request_path)
9+
@path = path
10+
@request_path = request_path
11+
end
12+
13+
def raw_source
14+
@raw_source ||= File.binread(path)
15+
end
16+
17+
def ast
18+
@ast ||= CC::Parser.parse(raw_source, request_path)
19+
end
20+
21+
private
22+
23+
attr_reader :request_path
24+
end
25+
end
26+
end

‎lib/cc/engine/sexp_builder.rb‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module CC
2+
module Engine
3+
class SexpBuilder
4+
def initialize(input, path)
5+
@input = input
6+
@path = path
7+
end
8+
9+
def build
10+
if input.is_a?(CC::Parser::Node)
11+
sexp(input.type.to_sym, *build_properties(input))
12+
elsif input.is_a?(Array)
13+
input.map do |node|
14+
self.class.new(node, path).build
15+
end
16+
end
17+
end
18+
19+
private
20+
21+
attr_reader :input, :path
22+
23+
def build_properties(node)
24+
node.properties.map do |key, property|
25+
if property.is_a?(CC::Parser::Node)
26+
sexp(key.to_sym, self.class.new(property, path).build)
27+
elsif property.is_a?(Array)
28+
sexp(key.to_sym, *self.class.new(property, path).build)
29+
else
30+
property.to_s.to_sym
31+
end
32+
end
33+
end
34+
35+
def sexp(*args)
36+
Sexp.new(*args).tap do |sexp|
37+
sexp.file = path
38+
sexp.line = input.location.first_line
39+
sexp.end_line = input.location.last_line
40+
end
41+
end
42+
end
43+
end
44+
end
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
require "spec_helper"
2+
require "cc/engine/analyzers/java/main"
3+
require "cc/engine/analyzers/engine_config"
4+
require "cc/engine/analyzers/file_list"
5+
6+
module CC::Engine::Analyzers
7+
RSpec.describe Java::Main, in_tmpdir: true do
8+
include AnalyzerSpecHelpers
9+
10+
describe "#run" do
11+
let(:engine_conf) { EngineConfig.new({}) }
12+
13+
it "prints an issue for similar code" do
14+
create_source_file("foo.java", <<-EOF)
15+
public class ArrayDemo {
16+
public static void foo() {
17+
int[] anArray;
18+
19+
anArray = new int[10];
20+
21+
for (int i = 0; i < anArray.length; i++) {
22+
anArray[i] = i;
23+
}
24+
25+
for (int i = 0; i < anArray.length; i++) {
26+
System.out.print(anArray[i] + " ");
27+
}
28+
29+
System.out.println();
30+
}
31+
32+
public static void bar() {
33+
int[] anArray;
34+
35+
anArray = new int[10];
36+
37+
for (int i = 0; i < anArray.length; i++) {
38+
anArray[i] = i;
39+
}
40+
41+
for (int i = 0; i < anArray.length; i++) {
42+
System.out.print(anArray[i] + " ");
43+
}
44+
45+
System.out.println();
46+
}
47+
}
48+
EOF
49+
50+
issues = run_engine(engine_conf).strip.split("0円")
51+
result = issues.first.strip
52+
json = JSON.parse(result)
53+
54+
expect(json["type"]).to eq("issue")
55+
expect(json["check_name"]).to eq("Similar code")
56+
expect(json["description"]).to eq("Similar blocks of code found in 2 locations. Consider refactoring.")
57+
expect(json["categories"]).to eq(["Duplication"])
58+
expect(json["location"]).to eq({
59+
"path" => "foo.java",
60+
"lines" => { "begin" => 2, "end" => 16 },
61+
})
62+
expect(json["remediation_points"]).to eq(930_000)
63+
expect(json["other_locations"]).to eq([
64+
{"path" => "foo.java", "lines" => { "begin" => 18, "end" => 32 } },
65+
])
66+
expect(json["content"]["body"]).to match /This issue has a mass of 103/
67+
expect(json["fingerprint"]).to eq("48eb151dc29634f90a86ffabf9d3c4b5")
68+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
69+
end
70+
71+
it "prints an issue for identical code" do
72+
create_source_file("foo.java", <<-EOF)
73+
public class ArrayDemo {
74+
public static void foo(int[] anArray) {
75+
for (int i = 0; i < anArray.length; i++) {
76+
System.out.print(anArray[i] + " ");
77+
}
78+
79+
System.out.println();
80+
}
81+
82+
public static void foo(int[] anArray) {
83+
for (int i = 0; i < anArray.length; i++) {
84+
System.out.print(anArray[i] + " ");
85+
}
86+
87+
System.out.println();
88+
}
89+
}
90+
EOF
91+
92+
issues = run_engine(engine_conf).strip.split("0円")
93+
result = issues.first.strip
94+
json = JSON.parse(result)
95+
96+
expect(json["type"]).to eq("issue")
97+
expect(json["check_name"]).to eq("Identical code")
98+
expect(json["description"]).to eq("Identical blocks of code found in 2 locations. Consider refactoring.")
99+
expect(json["categories"]).to eq(["Duplication"])
100+
expect(json["location"]).to eq({
101+
"path" => "foo.java",
102+
"lines" => { "begin" => 2, "end" => 8 },
103+
})
104+
expect(json["remediation_points"]).to eq(420_000)
105+
expect(json["other_locations"]).to eq([
106+
{"path" => "foo.java", "lines" => { "begin" => 10, "end" => 16 } },
107+
])
108+
expect(json["content"]["body"]).to match /This issue has a mass of 52/
109+
expect(json["fingerprint"]).to eq("dbb957b34f7b5312538235c0aa3f52a0")
110+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MINOR)
111+
end
112+
end
113+
end
114+
end

‎spec/cc/engine/sexp_builder_spec.rb‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
require "spec_helper"
2+
require "cc/engine/sexp_builder"
3+
require "cc/parser"
4+
5+
RSpec.describe(CC::Engine::SexpBuilder) do
6+
include AnalyzerSpecHelpers
7+
8+
describe "#build" do
9+
it "converts a node to sexp with accurate location information" do
10+
node = CC::Parser.parse(<<-EOPHP, "/php")
11+
<?php
12+
function hello($name) {
13+
if (empty($name)) {
14+
echo "Hello World!";
15+
} else {
16+
echo "Hello $name!";
17+
}
18+
}
19+
20+
function hello($name) {
21+
if (empty($name)) {
22+
echo "Hello World!";
23+
} else {
24+
echo "Hello $name!";
25+
}
26+
}
27+
EOPHP
28+
29+
sexp = described_class.new(node, "foo.php").build
30+
31+
_, statements = *sexp
32+
_, _, hello_one, hello_two = *statements
33+
expect(statements.line).to eq(1)
34+
expect(statements.end_line).to eq(16)
35+
expect(hello_one.line).to eq(2)
36+
expect(hello_one.end_line).to eq(8)
37+
expect(hello_two.line).to eq(10)
38+
expect(hello_two.end_line).to eq(16)
39+
end
40+
41+
it "returns similar sexps for similar nodes" do
42+
node0 = CC::Parser.parse(<<-EORUBY, "/ruby")
43+
def self.from_level(level)
44+
if level >= 4
45+
new("A")
46+
elsif level >= 2
47+
new("E")
48+
elsif level >= 1
49+
new("I")
50+
elsif level >= 0
51+
new("O")
52+
else
53+
new("U")
54+
end
55+
end
56+
EORUBY
57+
58+
node1 = CC::Parser.parse(<<-EORUBY, "/ruby")
59+
def self.from_foo(foo)
60+
if foo <= 20
61+
new("A")
62+
elsif foo <= 40
63+
new("E")
64+
elsif foo <= 80
65+
new("I")
66+
elsif foo <= 160
67+
new("O")
68+
else
69+
new("U")
70+
end
71+
end
72+
EORUBY
73+
74+
sexp0 = described_class.new(node0, "foo0.rb").build
75+
sexp1 = described_class.new(node1, "foo1.rb").build
76+
expect(sexp0.deep_each.map(&:first)).to eq(sexp1.deep_each.map(&:first))
77+
end
78+
79+
it "correctly builds sexps with conditionals" do
80+
node = CC::Parser.parse(<<-EORUBY, "/ruby")
81+
def self.from_level(level)
82+
if level >= 4
83+
new("A")
84+
elsif level >= 2
85+
new("E")
86+
elsif level >= 1
87+
new("I")
88+
elsif level >= 0
89+
new("O")
90+
else
91+
new("U")
92+
end
93+
end
94+
EORUBY
95+
96+
sexp = described_class.new(node, "file.rb").build
97+
98+
defs, _, _, args, condition_body = *sexp
99+
_, if_condition = *condition_body
100+
101+
expect(sexp.line).to eq(1)
102+
expect(sexp.end_line).to eq(13)
103+
expect(if_condition.line).to eq(2)
104+
expect(if_condition.end_line).to eq(12)
105+
expect([*if_condition].map {|sexp| (sexp.is_a? Symbol) ? sexp : sexp.first }).
106+
to eq([:if, :condition, :then, :else])
107+
end
108+
end
109+
end

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /