From 169c647b6af7566a4a884d58312e1b76b9f584c6 Mon Sep 17 00:00:00 2001 From: Filipe Esperandio Date: Thu, 21 Jun 2018 18:45:02 -0300 Subject: [PATCH 1/2] Add support to scala --- lib/cc/engine/analyzers/scala/main.rb | 37 +++ lib/cc/engine/duplication.rb | 2 + .../cc/engine/analyzers/engine_config_spec.rb | 1 + spec/cc/engine/analyzers/scala/scala_spec.rb | 255 ++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 lib/cc/engine/analyzers/scala/main.rb create mode 100644 spec/cc/engine/analyzers/scala/scala_spec.rb diff --git a/lib/cc/engine/analyzers/scala/main.rb b/lib/cc/engine/analyzers/scala/main.rb new file mode 100644 index 00000000..154ff88f --- /dev/null +++ b/lib/cc/engine/analyzers/scala/main.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "flay" +require "json" +require "cc/engine/analyzers/reporter" +require "cc/engine/analyzers/analyzer_base" + +module CC + module Engine + module Analyzers + module Scala + class Main < CC::Engine::Analyzers::Base + LANGUAGE = "scala".freeze + PATTERNS = ["**/*.sc", "**/*.scala"].freeze + DEFAULT_MASS_THRESHOLD = 60 + DEFAULT_FILTERS = [].freeze + POINTS_PER_OVERAGE = 10_000 + REQUEST_PATH = "/scala".freeze + + def use_sexp_lines? + false + end + + private + + def process_file(file) + parse(file, REQUEST_PATH) + end + + def default_filters + DEFAULT_FILTERS.map { |filter| Sexp::Matcher.parse filter } + end + end + end + end + end +end diff --git a/lib/cc/engine/duplication.rb b/lib/cc/engine/duplication.rb index 76ef5161..763d7d9f 100644 --- a/lib/cc/engine/duplication.rb +++ b/lib/cc/engine/duplication.rb @@ -10,6 +10,7 @@ require "cc/engine/analyzers/php/main" require "cc/engine/analyzers/python/main" require "cc/engine/analyzers/reporter" +require "cc/engine/analyzers/scala/main" require "cc/engine/analyzers/swift/main" require "cc/engine/analyzers/typescript/main" require "cc/engine/analyzers/engine_config" @@ -29,6 +30,7 @@ class Duplication "python" => ::CC::Engine::Analyzers::Python::Main, "typescript" => ::CC::Engine::Analyzers::TypeScript::Main, "go" => ::CC::Engine::Analyzers::Go::Main, + "scala" => ::CC::Engine::Analyzers::Scala::Main, "swift" => ::CC::Engine::Analyzers::Swift::Main, }.freeze diff --git a/spec/cc/engine/analyzers/engine_config_spec.rb b/spec/cc/engine/analyzers/engine_config_spec.rb index 0a7ec6c9..37b3a966 100644 --- a/spec/cc/engine/analyzers/engine_config_spec.rb +++ b/spec/cc/engine/analyzers/engine_config_spec.rb @@ -51,6 +51,7 @@ "python" => {}, "typescript" => {}, "go" => {}, + "scala" => {}, "swift" => {}, }) end diff --git a/spec/cc/engine/analyzers/scala/scala_spec.rb b/spec/cc/engine/analyzers/scala/scala_spec.rb new file mode 100644 index 00000000..442a56f4 --- /dev/null +++ b/spec/cc/engine/analyzers/scala/scala_spec.rb @@ -0,0 +1,255 @@ +require "spec_helper" +require "cc/engine/analyzers/scala/main" +require "cc/engine/analyzers/engine_config" + +module CC::Engine::Analyzers + RSpec.describe Scala::Main, in_tmpdir: true do + include AnalyzerSpecHelpers + + describe "#run" do + let(:engine_conf) { EngineConfig.new({}) } + + it "prints an issue for similar code" do + create_source_file("foo.scala", <<-EOF) + class Foo { + def foo() { + val anArray = new Array[Int](10) + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i)) + } + + println("") + } + + def bar() { + val anArray = new Array[Int](10) + + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i)) + } + + println("") + } + } + EOF + + issues = run_engine(engine_conf).strip.split("\0") + + result = issues.first.strip + json = JSON.parse(result) + + expect(json["type"]).to eq("issue") + expect(json["check_name"]).to eq("similar-code") + expect(json["description"]).to eq("Similar blocks of code found in 2 locations. Consider refactoring.") + expect(json["categories"]).to eq(["Duplication"]) + expect(json["location"]).to eq({ + "path" => "foo.scala", + "lines" => { "begin" => 2, "end" => 13 }, + }) + expect(json["other_locations"]).to eq([ + {"path" => "foo.scala", "lines" => { "begin" => 15, "end" => 27 } }, + ]) + expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR) + end + + it "prints an issue for identical code" do + create_source_file("foo.scala", <<-EOF) + class Foo { + def foo() { + val anArray = new Array[Int](10) + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i)) + } + + println("") + } + + def foo() { + val anArray = new Array[Int](10) + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i)) + } + + println("") + } + } + EOF + + issues = run_engine(engine_conf).strip.split("\0") + result = issues.first.strip + json = JSON.parse(result) + + expect(json["type"]).to eq("issue") + expect(json["check_name"]).to eq("identical-code") + expect(json["description"]).to eq("Identical blocks of code found in 2 locations. Consider refactoring.") + expect(json["categories"]).to eq(["Duplication"]) + expect(json["location"]).to eq({ + "path" => "foo.scala", + "lines" => { "begin" => 2, "end" => 13 }, + }) + expect(json["other_locations"]).to eq([ + {"path" => "foo.scala", "lines" => { "begin" => 15, "end" => 26 } }, + ]) + expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR) + end + + it "outputs a warning for unprocessable errors" do + create_source_file("foo.scala", <<-EOF) + --- + EOF + + expect(CC.logger).to receive(:warn).with(/Response status: 422/) + expect(CC.logger).to receive(:warn).with(/Skipping/) + run_engine(engine_conf) + end + + it "ignores import and package declarations" do + create_source_file("foo.scala", <<-EOF) + package org.springframework.rules.constraint; + + import java.util.Comparator; + + import org.springframework.rules.constraint.Constraint; + import org.springframework.rules.closure.BinaryConstraint; + EOF + + create_source_file("bar.scala", <<-EOF) + package org.springframework.rules.constraint; + + import java.util.Comparator; + + import org.springframework.rules.constraint.Constraint; + import org.springframework.rules.closure.BinaryConstraint; + EOF + + issues = run_engine(engine_conf).strip.split("\0") + expect(issues).to be_empty + end + + it "prints an issue for similar code when the only difference is the value of a literal" do + create_source_file("foo.sc", <<-EOF) + class ArrayDemo { + def foo() { + val scott = arrayOfInt( + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F + ) + + val anArray: Array[Int] = Array(10) + + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i) + " ") + } + + println() + } + + def foo() { + val scott = arrayOfInt( + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7 + ) + + val anArray: Array[Int] = Array(10) + + for (i <- 0 to 10) { + anArray(i) = i + } + + for (i <- 0 to 10) { + println(anArray(i) + " ") + } + + println() + } + } + EOF + + issues = run_engine(engine_conf).strip.split("\0") + expect(issues.length).to be > 0 + result = issues.first.strip + json = JSON.parse(result) + + expect(json["type"]).to eq("issue") + expect(json["check_name"]).to eq("similar-code") + + expect(json["description"]).to eq("Similar blocks of code found in 2 locations. Consider refactoring.") + expect(json["categories"]).to eq(["Duplication"]) + expect(json["location"]).to eq({ + "path" => "foo.sc", + "lines" => { "begin" => 2, "end" => 18 }, + }) + expect(json["other_locations"]).to eq([ + {"path" => "foo.sc", "lines" => { "begin" => 20, "end" => 36 } }, + ]) + expect(json["severity"]).to eq(CC::Engine::Analyzers::Base::MAJOR) + end + + it "ignores comment docs and comments" do + create_source_file("foo.sc", <<-EOF) + /******************************************************************** + * Copyright (C) 2017 by Max Lv + *******************************************************************/ + + package com.github.shadowsocks.acl + // Comment here + + import org.junit.Assert + import org.junit.Test + + class AclTest { + // Comment here + companion object { + private const val INPUT1 = """[proxy_all] + [bypass_list] + 1.0.1.0/24 + (^|\.)4tern\.com${'$'} + """ + } + + def parse() { + Assert.assertEquals(INPUT1, Acl().fromReader(INPUT1.reader()).toString()); + } + } + EOF + + create_source_file("bar.sc", <<-EOF) + /********************************************************************* + * Copyright (C) 2017 by Max Lv + ********************************************************************/ + + package com.evernote.android.job + // Comment here + + object JobConstants { + // Comment here + const val DATABASE_NAME = JobStorage.DATABASE_NAME + const val PREF_FILE_NAME = JobStorage.PREF_FILE_NAME + } + EOF + + issues = run_engine(engine_conf).strip.split("\0") + expect(issues).to be_empty + end + + end + end +end From 00f6f49da6b1938a1561b1b77db3db0fa53e12d1 Mon Sep 17 00:00:00 2001 From: Filipe Esperandio Date: Fri, 22 Jun 2018 16:15:46 -0300 Subject: [PATCH 2/2] Filter imports by default --- lib/cc/engine/analyzers/scala/main.rb | 4 +++- spec/cc/engine/analyzers/scala/scala_spec.rb | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/cc/engine/analyzers/scala/main.rb b/lib/cc/engine/analyzers/scala/main.rb index 154ff88f..671a6b90 100644 --- a/lib/cc/engine/analyzers/scala/main.rb +++ b/lib/cc/engine/analyzers/scala/main.rb @@ -13,7 +13,9 @@ class Main < CC::Engine::Analyzers::Base LANGUAGE = "scala".freeze PATTERNS = ["**/*.sc", "**/*.scala"].freeze DEFAULT_MASS_THRESHOLD = 60 - DEFAULT_FILTERS = [].freeze + DEFAULT_FILTERS = [ + "(Import ___)".freeze, + ].freeze POINTS_PER_OVERAGE = 10_000 REQUEST_PATH = "/scala".freeze diff --git a/spec/cc/engine/analyzers/scala/scala_spec.rb b/spec/cc/engine/analyzers/scala/scala_spec.rb index 442a56f4..9576d9c8 100644 --- a/spec/cc/engine/analyzers/scala/scala_spec.rb +++ b/spec/cc/engine/analyzers/scala/scala_spec.rb @@ -138,7 +138,19 @@ def foo() { import org.springframework.rules.closure.BinaryConstraint; EOF - issues = run_engine(engine_conf).strip.split("\0") + conf = CC::Engine::Analyzers::EngineConfig.new({ + 'config' => { + 'checks' => { + 'similar-code' => { 'enabled' => true }, + 'identical-code' => { 'enabled' => true }, + }, + 'languages' => { + 'scala' => { 'mass_threshold' => 9 }, + }, + }, + }) + + issues = run_engine(conf).strip.split("\0") expect(issues).to be_empty end