Skip to content

Commit 0646b13

Browse files
Add support to scala (#324)
* Add support to scala * Filter imports by default
1 parent e48e3aa commit 0646b13

File tree

4 files changed

+309
-0
lines changed

4 files changed

+309
-0
lines changed

lib/cc/engine/analyzers/scala/main.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
8+
module CC
9+
module Engine
10+
module Analyzers
11+
module Scala
12+
class Main < CC::Engine::Analyzers::Base
13+
LANGUAGE = "scala".freeze
14+
PATTERNS = ["**/*.sc", "**/*.scala"].freeze
15+
DEFAULT_MASS_THRESHOLD = 60
16+
DEFAULT_FILTERS = [
17+
"(Import ___)".freeze,
18+
].freeze
19+
POINTS_PER_OVERAGE = 10_000
20+
REQUEST_PATH = "/scala".freeze
21+
22+
def use_sexp_lines?
23+
false
24+
end
25+
26+
private
27+
28+
def process_file(file)
29+
parse(file, REQUEST_PATH)
30+
end
31+
32+
def default_filters
33+
DEFAULT_FILTERS.map { |filter| Sexp::Matcher.parse filter }
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end

lib/cc/engine/duplication.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require "cc/engine/analyzers/php/main"
1111
require "cc/engine/analyzers/python/main"
1212
require "cc/engine/analyzers/reporter"
13+
require "cc/engine/analyzers/scala/main"
1314
require "cc/engine/analyzers/swift/main"
1415
require "cc/engine/analyzers/typescript/main"
1516
require "cc/engine/analyzers/engine_config"
@@ -29,6 +30,7 @@ class Duplication
2930
"python" => ::CC::Engine::Analyzers::Python::Main,
3031
"typescript" => ::CC::Engine::Analyzers::TypeScript::Main,
3132
"go" => ::CC::Engine::Analyzers::Go::Main,
33+
"scala" => ::CC::Engine::Analyzers::Scala::Main,
3234
"swift" => ::CC::Engine::Analyzers::Swift::Main,
3335
}.freeze
3436

spec/cc/engine/analyzers/engine_config_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"python" => {},
5252
"typescript" => {},
5353
"go" => {},
54+
"scala" => {},
5455
"swift" => {},
5556
})
5657
end
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
require "spec_helper"
2+
require "cc/engine/analyzers/scala/main"
3+
require "cc/engine/analyzers/engine_config"
4+
5+
module CC::Engine::Analyzers
6+
RSpec.describe Scala::Main, in_tmpdir: true do
7+
include AnalyzerSpecHelpers
8+
9+
describe "#run" do
10+
let(:engine_conf) { EngineConfig.new({}) }
11+
12+
it "prints an issue for similar code" do
13+
create_source_file("foo.scala", <<-EOF)
14+
class Foo {
15+
def foo() {
16+
val anArray = new Array[Int](10)
17+
for (i <- 0 to 10) {
18+
anArray(i) = i
19+
}
20+
21+
for (i <- 0 to 10) {
22+
println(anArray(i))
23+
}
24+
25+
println("")
26+
}
27+
28+
def bar() {
29+
val anArray = new Array[Int](10)
30+
31+
for (i <- 0 to 10) {
32+
anArray(i) = i
33+
}
34+
35+
for (i <- 0 to 10) {
36+
println(anArray(i))
37+
}
38+
39+
println("")
40+
}
41+
}
42+
EOF
43+
44+
issues = run_engine(engine_conf).strip.split("\0")
45+
46+
result = issues.first.strip
47+
json = JSON.parse(result)
48+
49+
expect(json["type"]).to eq("issue")
50+
expect(json["check_name"]).to eq("similar-code")
51+
expect(json["description"]).to eq("Similar blocks of code found in 2 locations. Consider refactoring.")
52+
expect(json["categories"]).to eq(["Duplication"])
53+
expect(json["location"]).to eq({
54+
"path" => "foo.scala",
55+
"lines" => { "begin" => 2, "end" => 13 },
56+
})
57+
expect(json["other_locations"]).to eq([
58+
{"path" => "foo.scala", "lines" => { "begin" => 15, "end" => 27 } },
59+
])
60+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
61+
end
62+
63+
it "prints an issue for identical code" do
64+
create_source_file("foo.scala", <<-EOF)
65+
class Foo {
66+
def foo() {
67+
val anArray = new Array[Int](10)
68+
for (i <- 0 to 10) {
69+
anArray(i) = i
70+
}
71+
72+
for (i <- 0 to 10) {
73+
println(anArray(i))
74+
}
75+
76+
println("")
77+
}
78+
79+
def foo() {
80+
val anArray = new Array[Int](10)
81+
for (i <- 0 to 10) {
82+
anArray(i) = i
83+
}
84+
85+
for (i <- 0 to 10) {
86+
println(anArray(i))
87+
}
88+
89+
println("")
90+
}
91+
}
92+
EOF
93+
94+
issues = run_engine(engine_conf).strip.split("\0")
95+
result = issues.first.strip
96+
json = JSON.parse(result)
97+
98+
expect(json["type"]).to eq("issue")
99+
expect(json["check_name"]).to eq("identical-code")
100+
expect(json["description"]).to eq("Identical blocks of code found in 2 locations. Consider refactoring.")
101+
expect(json["categories"]).to eq(["Duplication"])
102+
expect(json["location"]).to eq({
103+
"path" => "foo.scala",
104+
"lines" => { "begin" => 2, "end" => 13 },
105+
})
106+
expect(json["other_locations"]).to eq([
107+
{"path" => "foo.scala", "lines" => { "begin" => 15, "end" => 26 } },
108+
])
109+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
110+
end
111+
112+
it "outputs a warning for unprocessable errors" do
113+
create_source_file("foo.scala", <<-EOF)
114+
---
115+
EOF
116+
117+
expect(CC.logger).to receive(:warn).with(/Response status: 422/)
118+
expect(CC.logger).to receive(:warn).with(/Skipping/)
119+
run_engine(engine_conf)
120+
end
121+
122+
it "ignores import and package declarations" do
123+
create_source_file("foo.scala", <<-EOF)
124+
package org.springframework.rules.constraint;
125+
126+
import java.util.Comparator;
127+
128+
import org.springframework.rules.constraint.Constraint;
129+
import org.springframework.rules.closure.BinaryConstraint;
130+
EOF
131+
132+
create_source_file("bar.scala", <<-EOF)
133+
package org.springframework.rules.constraint;
134+
135+
import java.util.Comparator;
136+
137+
import org.springframework.rules.constraint.Constraint;
138+
import org.springframework.rules.closure.BinaryConstraint;
139+
EOF
140+
141+
conf = CC::Engine::Analyzers::EngineConfig.new({
142+
'config' => {
143+
'checks' => {
144+
'similar-code' => { 'enabled' => true },
145+
'identical-code' => { 'enabled' => true },
146+
},
147+
'languages' => {
148+
'scala' => { 'mass_threshold' => 9 },
149+
},
150+
},
151+
})
152+
153+
issues = run_engine(conf).strip.split("\0")
154+
expect(issues).to be_empty
155+
end
156+
157+
it "prints an issue for similar code when the only difference is the value of a literal" do
158+
create_source_file("foo.sc", <<-EOF)
159+
class ArrayDemo {
160+
def foo() {
161+
val scott = arrayOfInt(
162+
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F
163+
)
164+
165+
val anArray: Array[Int] = Array(10)
166+
167+
for (i <- 0 to 10) {
168+
anArray(i) = i
169+
}
170+
171+
for (i <- 0 to 10) {
172+
println(anArray(i) + " ")
173+
}
174+
175+
println()
176+
}
177+
178+
def foo() {
179+
val scott = arrayOfInt(
180+
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7
181+
)
182+
183+
val anArray: Array[Int] = Array(10)
184+
185+
for (i <- 0 to 10) {
186+
anArray(i) = i
187+
}
188+
189+
for (i <- 0 to 10) {
190+
println(anArray(i) + " ")
191+
}
192+
193+
println()
194+
}
195+
}
196+
EOF
197+
198+
issues = run_engine(engine_conf).strip.split("\0")
199+
expect(issues.length).to be > 0
200+
result = issues.first.strip
201+
json = JSON.parse(result)
202+
203+
expect(json["type"]).to eq("issue")
204+
expect(json["check_name"]).to eq("similar-code")
205+
206+
expect(json["description"]).to eq("Similar blocks of code found in 2 locations. Consider refactoring.")
207+
expect(json["categories"]).to eq(["Duplication"])
208+
expect(json["location"]).to eq({
209+
"path" => "foo.sc",
210+
"lines" => { "begin" => 2, "end" => 18 },
211+
})
212+
expect(json["other_locations"]).to eq([
213+
{"path" => "foo.sc", "lines" => { "begin" => 20, "end" => 36 } },
214+
])
215+
expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR)
216+
end
217+
218+
it "ignores comment docs and comments" do
219+
create_source_file("foo.sc", <<-EOF)
220+
/********************************************************************
221+
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com>
222+
*******************************************************************/
223+
224+
package com.github.shadowsocks.acl
225+
// Comment here
226+
227+
import org.junit.Assert
228+
import org.junit.Test
229+
230+
class AclTest {
231+
// Comment here
232+
companion object {
233+
private const val INPUT1 = """[proxy_all]
234+
[bypass_list]
235+
1.0.1.0/24
236+
(^|\.)4tern\.com${'$'}
237+
"""
238+
}
239+
240+
def parse() {
241+
Assert.assertEquals(INPUT1, Acl().fromReader(INPUT1.reader()).toString());
242+
}
243+
}
244+
EOF
245+
246+
create_source_file("bar.sc", <<-EOF)
247+
/*********************************************************************
248+
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com>
249+
********************************************************************/
250+
251+
package com.evernote.android.job
252+
// Comment here
253+
254+
object JobConstants {
255+
// Comment here
256+
const val DATABASE_NAME = JobStorage.DATABASE_NAME
257+
const val PREF_FILE_NAME = JobStorage.PREF_FILE_NAME
258+
}
259+
EOF
260+
261+
issues = run_engine(engine_conf).strip.split("\0")
262+
expect(issues).to be_empty
263+
end
264+
265+
end
266+
end
267+
end

0 commit comments

Comments
 (0)