@@ -19,8 +19,6 @@ | |||
*/ | |||
package org.sonar.batch.scan; | |||
import org.sonar.api.batch.measure.Metric; | |||
import org.sonar.api.batch.DependedUpon; | |||
import org.sonar.api.batch.DependsUpon; | |||
import org.sonar.api.batch.Sensor; | |||
@@ -29,6 +27,7 @@ import org.sonar.api.batch.analyzer.Analyzer; | |||
import org.sonar.api.batch.analyzer.AnalyzerContext; | |||
import org.sonar.api.batch.analyzer.internal.DefaultAnalyzerDescriptor; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.measure.Metric; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.scan2.AnalyzerOptimizer; | |||
@@ -53,12 +52,12 @@ public class SensorWrapper implements Sensor { | |||
} | |||
@DependedUpon | |||
public List<Metric<?>> provides() { | |||
public List<Metric> provides() { | |||
return Arrays.asList(descriptor.provides()); | |||
} | |||
@DependsUpon | |||
public List<Metric<?>> depends() { | |||
public List<Metric> depends() { | |||
return Arrays.asList(descriptor.dependsOn()); | |||
} | |||
@@ -23,27 +23,54 @@ import org.sonar.api.BatchComponent; | |||
import org.sonar.api.batch.analyzer.internal.DefaultAnalyzerDescriptor; | |||
import org.sonar.api.batch.fs.FilePredicate; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
public class AnalyzerOptimizer implements BatchComponent { | |||
private FileSystem fs; | |||
private final FileSystem fs; | |||
private final ActiveRules activeRules; | |||
public AnalyzerOptimizer(FileSystem fs) { | |||
public AnalyzerOptimizer(FileSystem fs, ActiveRules activeRules) { | |||
this.fs = fs; | |||
this.activeRules = activeRules; | |||
} | |||
/** | |||
* Decide if the given Analyzer should be executed. | |||
*/ | |||
public boolean shouldExecute(DefaultAnalyzerDescriptor descriptor) { | |||
FilePredicate predicate = fs.predicates().hasLanguages(descriptor.languages()); | |||
if (descriptor.types().size() == 1) { | |||
// Size = 0 or Size = 2 means both main and test type | |||
predicate = fs.predicates().and( | |||
predicate, | |||
fs.predicates().hasType(descriptor.types().iterator().next())); | |||
// FS Conditions | |||
boolean fsCondition = fsCondition(descriptor); | |||
boolean activeRulesCondition = activeRulesCondition(descriptor); | |||
return fsCondition && activeRulesCondition; | |||
} | |||
private boolean activeRulesCondition(DefaultAnalyzerDescriptor descriptor) { | |||
if (!descriptor.ruleRepositories().isEmpty()) { | |||
for (String repoKey : descriptor.ruleRepositories()) { | |||
if (!activeRules.findByRepository(repoKey).isEmpty()) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
return true; | |||
} | |||
private boolean fsCondition(DefaultAnalyzerDescriptor descriptor) { | |||
if (!descriptor.languages().isEmpty() || !descriptor.types().isEmpty()) { | |||
FilePredicate langPredicate = descriptor.languages().isEmpty() ? fs.predicates().all() : fs.predicates().hasLanguages(descriptor.languages()); | |||
FilePredicate typePredicate = descriptor.types().isEmpty() ? fs.predicates().all() : fs.predicates().none(); | |||
for (InputFile.Type type : descriptor.types()) { | |||
typePredicate = fs.predicates().or( | |||
typePredicate, | |||
fs.predicates().hasType(type)); | |||
} | |||
return fs.hasFiles(fs.predicates().and(langPredicate, typePredicate)); | |||
} | |||
return fs.hasFiles(predicate); | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,117 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* SonarQube is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.batch.scan2; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.batch.analyzer.internal.DefaultAnalyzerDescriptor; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.fs.internal.DefaultFileSystem; | |||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; | |||
import org.sonar.api.rule.RuleKey; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class AnalyzerOptimizerTest { | |||
DefaultFileSystem fs = new DefaultFileSystem(); | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
private AnalyzerOptimizer optimizer; | |||
@Before | |||
public void prepare() { | |||
optimizer = new AnalyzerOptimizer(fs, new ActiveRulesBuilder().build()); | |||
} | |||
@Test | |||
public void should_run_analyzer_with_no_metadata() throws Exception { | |||
DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor(); | |||
assertThat(optimizer.shouldExecute(descriptor)).isTrue(); | |||
} | |||
@Test | |||
public void should_optimize_on_language() throws Exception { | |||
DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor() | |||
.workOnLanguages("java", "php"); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
fs.add(new DefaultInputFile("src/Foo.java").setLanguage("java")); | |||
assertThat(optimizer.shouldExecute(descriptor)).isTrue(); | |||
} | |||
@Test | |||
public void should_optimize_on_type() throws Exception { | |||
DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor() | |||
.workOnFileTypes(InputFile.Type.MAIN); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
fs.add(new DefaultInputFile("tests/FooTest.java").setType(InputFile.Type.TEST)); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
fs.add(new DefaultInputFile("src/Foo.java").setType(InputFile.Type.MAIN)); | |||
assertThat(optimizer.shouldExecute(descriptor)).isTrue(); | |||
} | |||
@Test | |||
public void should_optimize_on_both_type_and_language() throws Exception { | |||
DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor() | |||
.workOnLanguages("java", "php") | |||
.workOnFileTypes(InputFile.Type.MAIN); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
fs.add(new DefaultInputFile("tests/FooTest.java").setLanguage("java").setType(InputFile.Type.TEST)); | |||
fs.add(new DefaultInputFile("src/Foo.cbl").setLanguage("cobol").setType(InputFile.Type.MAIN)); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
fs.add(new DefaultInputFile("src/Foo.java").setLanguage("java").setType(InputFile.Type.MAIN)); | |||
assertThat(optimizer.shouldExecute(descriptor)).isTrue(); | |||
} | |||
@Test | |||
public void should_optimize_on_repository() throws Exception { | |||
DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor() | |||
.createIssuesForRuleRepositories("squid"); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
ActiveRules activeRules = new ActiveRulesBuilder() | |||
.activate(RuleKey.of("repo1", "foo")) | |||
.end() | |||
.build(); | |||
optimizer = new AnalyzerOptimizer(fs, activeRules); | |||
assertThat(optimizer.shouldExecute(descriptor)).isFalse(); | |||
activeRules = new ActiveRulesBuilder() | |||
.activate(RuleKey.of("repo1", "foo")) | |||
.end() | |||
.activate(RuleKey.of("squid", "rule")) | |||
.end() | |||
.build(); | |||
optimizer = new AnalyzerOptimizer(fs, activeRules); | |||
assertThat(optimizer.shouldExecute(descriptor)).isTrue(); | |||
} | |||
} |
@@ -28,7 +28,7 @@ import java.util.List; | |||
/** | |||
* @since 2.3 | |||
* @deprecated in 4.2. Replaced by org.sonar.api.rule.RuleDefinitions | |||
* @deprecated in 4.2. Replaced by org.sonar.api.server.rule.RuleDefinition | |||
*/ | |||
@Deprecated | |||
public abstract class RuleRepository implements ServerExtension { | |||
@@ -71,9 +71,9 @@ public abstract class RuleRepository implements ServerExtension { | |||
@Override | |||
public String toString() { | |||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) | |||
.append("key", key) | |||
.append("language", language) | |||
.append("name", name) | |||
.toString(); | |||
.append("key", key) | |||
.append("language", language) | |||
.append("name", name) | |||
.toString(); | |||
} | |||
} |
@@ -51,13 +51,20 @@ public interface AnalyzerDescriptor { | |||
* no file for given languages are present in the project. | |||
* If no language is provided then it will be executed for all languages. | |||
*/ | |||
AnalyzerDescriptor runOnLanguages(String... languageKeys); | |||
AnalyzerDescriptor workOnLanguages(String... languageKeys); | |||
/** | |||
* List {@link InputFile.Type} this {@link Analyzer} work on. May be used by the platform to skip execution of the {@link Analyzer} when | |||
* no file for given type are present in the project. | |||
* If not type is provided then it will be executed for all types. | |||
*/ | |||
AnalyzerDescriptor runOnTypes(InputFile.Type... types); | |||
AnalyzerDescriptor workOnFileTypes(InputFile.Type... types); | |||
/** | |||
* List {@link InputFile.Type} this {@link Analyzer} work on. May be used by the platform to skip execution of the {@link Analyzer} when | |||
* no file for given type are present in the project. | |||
* If not type is provided then it will be executed for all types. | |||
*/ | |||
AnalyzerDescriptor createIssuesForRuleRepositories(String... repositoryKeys); | |||
} |
@@ -19,10 +19,9 @@ | |||
*/ | |||
package org.sonar.api.batch.analyzer.internal; | |||
import org.sonar.api.batch.measure.Metric; | |||
import org.sonar.api.batch.analyzer.AnalyzerDescriptor; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.measure.Metric; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
@@ -34,16 +33,17 @@ public class DefaultAnalyzerDescriptor implements AnalyzerDescriptor { | |||
private Metric<?>[] provides = new Metric<?>[0]; | |||
private String[] languages = new String[0]; | |||
private InputFile.Type[] types = new InputFile.Type[0]; | |||
private String[] ruleRepositories = new String[0]; | |||
public String name() { | |||
return name; | |||
} | |||
public Metric<?>[] dependsOn() { | |||
public Metric[] dependsOn() { | |||
return dependsOn; | |||
} | |||
public Metric<?>[] provides() { | |||
public Metric[] provides() { | |||
return provides; | |||
} | |||
@@ -55,6 +55,10 @@ public class DefaultAnalyzerDescriptor implements AnalyzerDescriptor { | |||
return Arrays.asList(types); | |||
} | |||
public Collection<String> ruleRepositories() { | |||
return Arrays.asList(ruleRepositories); | |||
} | |||
@Override | |||
public DefaultAnalyzerDescriptor name(String name) { | |||
this.name = name; | |||
@@ -74,15 +78,21 @@ public class DefaultAnalyzerDescriptor implements AnalyzerDescriptor { | |||
} | |||
@Override | |||
public DefaultAnalyzerDescriptor runOnLanguages(String... languageKeys) { | |||
public DefaultAnalyzerDescriptor workOnLanguages(String... languageKeys) { | |||
this.languages = languageKeys; | |||
return this; | |||
} | |||
@Override | |||
public DefaultAnalyzerDescriptor runOnTypes(InputFile.Type... types) { | |||
public DefaultAnalyzerDescriptor workOnFileTypes(InputFile.Type... types) { | |||
this.types = types; | |||
return this; | |||
} | |||
@Override | |||
public DefaultAnalyzerDescriptor createIssuesForRuleRepositories(String... repositoryKeys) { | |||
this.ruleRepositories = repositoryKeys; | |||
return this; | |||
} | |||
} |
@@ -39,7 +39,7 @@ public class ActiveRulesBuilder { | |||
if (map.containsKey(ruleKey)) { | |||
throw new IllegalStateException(String.format("Rule '%s' is already activated", ruleKey)); | |||
} | |||
NewActiveRule newActiveRule = new NewActiveRule(ruleKey); | |||
NewActiveRule newActiveRule = new NewActiveRule(this, ruleKey); | |||
map.put(ruleKey, newActiveRule); | |||
return newActiveRule; | |||
} |
@@ -24,6 +24,7 @@ import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
import javax.annotation.Nullable; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@@ -35,8 +36,10 @@ public class NewActiveRule { | |||
String severity = Severity.defaultSeverity(); | |||
Map<String, String> params = new HashMap<String, String>(); | |||
String internalKey, language; | |||
private final ActiveRulesBuilder builder; | |||
NewActiveRule(RuleKey ruleKey) { | |||
NewActiveRule(ActiveRulesBuilder builder, RuleKey ruleKey) { | |||
this.builder = builder; | |||
this.ruleKey = ruleKey; | |||
} | |||
@@ -68,4 +71,8 @@ public class NewActiveRule { | |||
public Map<String, String> params() { | |||
return params; | |||
} | |||
public ActiveRulesBuilder end() { | |||
return builder; | |||
} | |||
} |
@@ -34,8 +34,8 @@ public class DefaultAnalyzerDescriptorTest { | |||
.name("Foo") | |||
.dependsOn(CoreMetrics.NCLOC) | |||
.provides(CoreMetrics.BLOCKER_VIOLATIONS) | |||
.runOnLanguages("java", "php") | |||
.runOnTypes(InputFile.Type.MAIN); | |||
.workOnLanguages("java", "php") | |||
.workOnFileTypes(InputFile.Type.MAIN); | |||
assertThat(descriptor.name()).isEqualTo("Foo"); | |||
assertThat(descriptor.dependsOn()).containsOnly(CoreMetrics.NCLOC); |
@@ -38,16 +38,16 @@ public class ActiveRulesBuilderTest { | |||
@Test | |||
public void build_rules() throws Exception { | |||
ActiveRulesBuilder builder = new ActiveRulesBuilder(); | |||
NewActiveRule newSquid1 = builder.activate(RuleKey.of("squid", "S0001")); | |||
newSquid1.setSeverity(Severity.CRITICAL); | |||
newSquid1.setInternalKey("__S0001__"); | |||
newSquid1.setParam("min", "20"); | |||
// most simple rule | |||
builder.activate(RuleKey.of("squid", "S0002")); | |||
builder.activate(RuleKey.of("findbugs", "NPE")).setInternalKey(null).setSeverity(null).setParam("foo", null); | |||
ActiveRules activeRules = builder.build(); | |||
ActiveRules activeRules = new ActiveRulesBuilder() | |||
.activate(RuleKey.of("squid", "S0001")) | |||
.setSeverity(Severity.CRITICAL) | |||
.setInternalKey("__S0001__") | |||
.setParam("min", "20") | |||
.end() | |||
// most simple rule | |||
.activate(RuleKey.of("squid", "S0002")).end() | |||
.activate(RuleKey.of("findbugs", "NPE")).setInternalKey(null).setSeverity(null).setParam("foo", null).end() | |||
.build(); | |||
assertThat(activeRules.findAll()).hasSize(3); | |||
assertThat(activeRules.findByRepository("squid")).hasSize(2); |