aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2014-07-25 16:31:45 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2014-07-30 18:03:36 +0200
commit6074164392edd3db2dfdfd21d05cd56c19e2b0e6 (patch)
treeb9314796d68c4c396dcf45a1ab689b06490fd4a2 /plugins
parent12f243728f42a5eb1e714ff15f0240109193f1d8 (diff)
downloadsonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.tar.gz
sonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.zip
SONAR-5389 New duplication API
Diffstat (limited to 'plugins')
-rw-r--r--plugins/sonar-cpd-plugin/pom.xml5
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java5
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java51
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java7
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java26
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java (renamed from plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java)70
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java (renamed from plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java)81
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java33
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java2
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java9
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java (renamed from plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java)34
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java155
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java160
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java22
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java119
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java36
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java22
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java119
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java112
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java98
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java95
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java84
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java61
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java62
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java76
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java2
26 files changed, 1253 insertions, 293 deletions
diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml
index 2903ae5a4be..334122c4b91 100644
--- a/plugins/sonar-cpd-plugin/pom.xml
+++ b/plugins/sonar-cpd-plugin/pom.xml
@@ -54,6 +54,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-xoo-plugin</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
index 7548282c87b..2f2a7633f40 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
@@ -21,14 +21,13 @@ package org.sonar.plugins.cpd;
import org.slf4j.Logger;
import org.sonar.api.BatchExtension;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.resources.Project;
+import org.sonar.api.batch.sensor.SensorContext;
public abstract class CpdEngine implements BatchExtension {
abstract boolean isLanguageSupported(String language);
- abstract void analyse(Project project, String language, SensorContext context);
+ abstract void analyse(String language, SensorContext context);
protected void logExclusions(String[] exclusions, Logger logger) {
if (exclusions.length > 0) {
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java
new file mode 100644
index 00000000000..3f0f9d83b8c
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java
@@ -0,0 +1,51 @@
+/*
+ * 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.plugins.cpd;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.CpdMapping;
+
+import javax.annotation.CheckForNull;
+
+public class CpdMappings implements BatchComponent {
+
+ private final CpdMapping[] mappings;
+
+ public CpdMappings(CpdMapping[] mappings) {
+ this.mappings = mappings;
+ }
+
+ public CpdMappings() {
+ this(new CpdMapping[0]);
+ }
+
+ @CheckForNull
+ public CpdMapping getMapping(String language) {
+ if (mappings != null) {
+ for (CpdMapping cpdMapping : mappings) {
+ if (cpdMapping.getLanguage().getKey().equals(language)) {
+ return cpdMapping;
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
index 5f9df9621e1..defa9fe260e 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
@@ -35,7 +35,7 @@ public final class CpdPlugin extends SonarPlugin {
public List getExtensions() {
return ImmutableList.of(
- PropertyDefinition.builder(CoreProperties.CPD_CROSS_RPOJECT)
+ PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT)
.defaultValue(CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "")
.name("Cross project duplication detection")
.description("SonarQube supports the detection of cross project duplications. Activating this property will slightly increase each SonarQube analysis time.")
@@ -65,11 +65,12 @@ public final class CpdPlugin extends SonarPlugin {
.build(),
CpdSensor.class,
+ CpdMappings.class,
SumDuplicationsDecorator.class,
DuplicationDensityDecorator.class,
IndexFactory.class,
- SonarEngine.class,
- SonarBridgeEngine.class);
+ JavaCpdEngine.class,
+ DefaultCpdEngine.class);
}
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
index 53a45416cb7..dfeabd65635 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
@@ -23,12 +23,14 @@ import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.Sensor;
-import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.Phase;
import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.config.Settings;
-import org.sonar.api.resources.Project;
+@Phase(name = Phase.Name.POST)
public class CpdSensor implements Sensor {
private static final Logger LOG = LoggerFactory.getLogger(CpdSensor.class);
@@ -38,15 +40,17 @@ public class CpdSensor implements Sensor {
private Settings settings;
private FileSystem fs;
- public CpdSensor(SonarEngine sonarEngine, SonarBridgeEngine sonarBridgeEngine, Settings settings, FileSystem fs) {
+ public CpdSensor(JavaCpdEngine sonarEngine, DefaultCpdEngine sonarBridgeEngine, Settings settings, FileSystem fs) {
this.sonarEngine = sonarEngine;
this.sonarBridgeEngine = sonarBridgeEngine;
this.settings = settings;
this.fs = fs;
}
- public boolean shouldExecuteOnProject(Project project) {
- return true;
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("CPD Sensor");
+
}
@VisibleForTesting
@@ -66,7 +70,8 @@ public class CpdSensor implements Sensor {
return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY);
}
- public void analyse(Project project, SensorContext context) {
+ @Override
+ public void execute(SensorContext context) {
for (String language : fs.languages()) {
if (isSkipped(language)) {
LOG.info("Detection of duplicated code is skipped for {}", language);
@@ -79,13 +84,8 @@ public class CpdSensor implements Sensor {
continue;
}
LOG.info("{} is used for {}", engine, language);
- engine.analyse(project, language, context);
+ engine.analyse(language, context);
}
}
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java
index df4bc036115..33558ec898a 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java
@@ -27,22 +27,25 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.CpdMapping;
-import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.duplication.BlockCache;
import org.sonar.duplications.DuplicationPredicates;
import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.FileBlocks;
import org.sonar.duplications.index.CloneGroup;
import org.sonar.duplications.internal.pmd.TokenizerBridge;
import org.sonar.plugins.cpd.index.IndexFactory;
import org.sonar.plugins.cpd.index.SonarDuplicationsIndex;
-import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -51,9 +54,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-public class SonarBridgeEngine extends CpdEngine {
+public class DefaultCpdEngine extends CpdEngine {
- private static final Logger LOG = LoggerFactory.getLogger(SonarBridgeEngine.class);
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdEngine.class);
/**
* Limit of time to analyse one file (in seconds).
@@ -61,28 +64,32 @@ public class SonarBridgeEngine extends CpdEngine {
private static final int TIMEOUT = 5 * 60;
private final IndexFactory indexFactory;
- private final CpdMapping[] mappings;
+ private final CpdMappings mappings;
private final FileSystem fs;
private final Settings settings;
+ private final BlockCache duplicationCache;
+ private final Project project;
- public SonarBridgeEngine(IndexFactory indexFactory, CpdMapping[] mappings, FileSystem fs, Settings settings) {
+ public DefaultCpdEngine(@Nullable Project project, IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) {
+ this.project = project;
this.indexFactory = indexFactory;
this.mappings = mappings;
this.fs = fs;
this.settings = settings;
+ this.duplicationCache = duplicationCache;
}
- public SonarBridgeEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
- this(indexFactory, new CpdMapping[0], fs, settings);
+ public DefaultCpdEngine(IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) {
+ this(null, indexFactory, mappings, fs, settings, duplicationCache);
}
@Override
public boolean isLanguageSupported(String language) {
- return getMapping(language) != null;
+ return true;
}
@Override
- public void analyse(Project project, String languageKey, SensorContext context) {
+ public void analyse(String languageKey, SensorContext context) {
String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS);
logExclusions(cpdExclusions, LOG);
FilePredicates p = fs.predicates();
@@ -90,29 +97,34 @@ public class SonarBridgeEngine extends CpdEngine {
p.hasType(InputFile.Type.MAIN),
p.hasLanguage(languageKey),
p.doesNotMatchPathPatterns(cpdExclusions)
- )));
+ )));
if (sourceFiles.isEmpty()) {
return;
}
- CpdMapping mapping = getMapping(languageKey);
- if (mapping == null) {
- return;
- }
+ CpdMapping mapping = mappings.getMapping(languageKey);
// Create index
SonarDuplicationsIndex index = indexFactory.create(project, languageKey);
- TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(project, languageKey));
+ TokenizerBridge bridge = null;
+ if (mapping != null) {
+ bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey));
+ }
for (InputFile inputFile : sourceFiles) {
LOG.debug("Populating index from {}", inputFile);
String resourceEffectiveKey = ((DeprecatedDefaultInputFile) inputFile).key();
- List<Block> blocks = bridge.chunk(resourceEffectiveKey, inputFile.file());
- index.insert(inputFile, blocks);
+ FileBlocks fileBlocks = duplicationCache.byComponent(resourceEffectiveKey);
+ if (fileBlocks != null) {
+ index.insert(inputFile, fileBlocks.blocks());
+ } else if (bridge != null) {
+ List<Block> blocks2 = bridge.chunk(resourceEffectiveKey, inputFile.file());
+ index.insert(inputFile, blocks2);
+ }
}
// Detect
- Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(project, languageKey));
+ Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(languageKey));
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
@@ -123,7 +135,7 @@ public class SonarBridgeEngine extends CpdEngine {
Iterable<CloneGroup> filtered;
try {
- List<CloneGroup> duplications = executorService.submit(new SonarEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS);
+ List<CloneGroup> duplications = executorService.submit(new JavaCpdEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS);
filtered = Iterables.filter(duplications, minimumTokensPredicate);
} catch (TimeoutException e) {
filtered = null;
@@ -134,7 +146,7 @@ public class SonarBridgeEngine extends CpdEngine {
throw new SonarException("Fail during detection of duplication for " + inputFile, e);
}
- SonarEngine.save(context, inputFile, filtered);
+ JavaCpdEngine.save(context, inputFile, filtered);
}
} finally {
executorService.shutdown();
@@ -142,7 +154,7 @@ public class SonarBridgeEngine extends CpdEngine {
}
@VisibleForTesting
- int getBlockSize(Project project, String languageKey) {
+ int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
if (blockSize == 0) {
blockSize = getDefaultBlockSize(languageKey);
@@ -162,7 +174,7 @@ public class SonarBridgeEngine extends CpdEngine {
}
@VisibleForTesting
- int getMinimumTokens(Project project, String languageKey) {
+ int getMinimumTokens(String languageKey) {
int minimumTokens = settings.getInt("sonar.cpd." + languageKey + ".minimumTokens");
if (minimumTokens == 0) {
minimumTokens = settings.getInt(CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY);
@@ -174,16 +186,4 @@ public class SonarBridgeEngine extends CpdEngine {
return minimumTokens;
}
- @CheckForNull
- private CpdMapping getMapping(String language) {
- if (mappings != null) {
- for (CpdMapping cpdMapping : mappings) {
- if (cpdMapping.getLanguage().getKey().equals(language)) {
- return cpdMapping;
- }
- }
- }
- return null;
- }
-
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
index 130206427fc..2ea908dbb27 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
@@ -23,21 +23,20 @@ package org.sonar.plugins.cpd;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
import org.sonar.api.config.Settings;
import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.duplication.DefaultDuplicationBuilder;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.block.BlockChunker;
import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
@@ -53,6 +52,7 @@ import org.sonar.plugins.cpd.index.IndexFactory;
import org.sonar.plugins.cpd.index.SonarDuplicationsIndex;
import javax.annotation.Nullable;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
@@ -61,11 +61,16 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
-public class SonarEngine extends CpdEngine {
+public class JavaCpdEngine extends CpdEngine {
- private static final Logger LOG = LoggerFactory.getLogger(SonarEngine.class);
+ private static final Logger LOG = LoggerFactory.getLogger(JavaCpdEngine.class);
private static final int BLOCK_SIZE = 10;
@@ -77,20 +82,26 @@ public class SonarEngine extends CpdEngine {
private final IndexFactory indexFactory;
private final FileSystem fs;
private final Settings settings;
+ private final Project project;
- public SonarEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ public JavaCpdEngine(@Nullable Project project, IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ this.project = project;
this.indexFactory = indexFactory;
this.fs = fs;
this.settings = settings;
}
+ public JavaCpdEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ this(null, indexFactory, fs, settings);
+ }
+
@Override
public boolean isLanguageSupported(String language) {
return "java".equals(language);
}
@Override
- public void analyse(Project project, String languageKey, SensorContext context) {
+ public void analyse(String languageKey, SensorContext context) {
String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS);
logExclusions(cpdExclusions, LOG);
FilePredicates p = fs.predicates();
@@ -98,7 +109,7 @@ public class SonarEngine extends CpdEngine {
p.hasType(InputFile.Type.MAIN),
p.hasLanguage(languageKey),
p.doesNotMatchPathPatterns(cpdExclusions)
- )));
+ )));
if (sourceFiles.isEmpty()) {
return;
}
@@ -106,7 +117,7 @@ public class SonarEngine extends CpdEngine {
detect(index, context, sourceFiles);
}
- private SonarDuplicationsIndex createIndex(Project project, String language, Iterable<InputFile> sourceFiles) {
+ private SonarDuplicationsIndex createIndex(@Nullable Project project, String language, Iterable<InputFile> sourceFiles) {
final SonarDuplicationsIndex index = indexFactory.create(project, language);
TokenChunker tokenChunker = JavaTokenProducer.build();
@@ -136,7 +147,7 @@ public class SonarEngine extends CpdEngine {
return index;
}
- private void detect(SonarDuplicationsIndex index, SensorContext context, List<InputFile> sourceFiles) {
+ private void detect(SonarDuplicationsIndex index, org.sonar.api.batch.sensor.SensorContext context, List<InputFile> sourceFiles) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (InputFile inputFile : sourceFiles) {
@@ -178,13 +189,13 @@ public class SonarEngine extends CpdEngine {
}
}
- static void save(SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) {
+ static void save(org.sonar.api.batch.sensor.SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) {
if (duplications == null || Iterables.isEmpty(duplications)) {
return;
}
// Calculate number of lines and blocks
Set<Integer> duplicatedLines = new HashSet<Integer>();
- double duplicatedBlocks = 0;
+ int duplicatedBlocks = 0;
for (CloneGroup clone : duplications) {
ClonePart origin = clone.getOriginPart();
for (ClonePart part : clone.getCloneParts()) {
@@ -197,30 +208,32 @@ public class SonarEngine extends CpdEngine {
}
}
// Save
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1.0);
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size());
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks);
-
- Measure data = new Measure(CoreMetrics.DUPLICATIONS_DATA, toXml(duplications))
- .setPersistenceMode(PersistenceMode.DATABASE);
- context.saveMeasure(inputFile, data);
- }
-
- private static String toXml(Iterable<CloneGroup> duplications) {
- StringBuilder xml = new StringBuilder();
- xml.append("<duplications>");
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_FILES)
+ .onFile(inputFile)
+ .withValue(1)
+ .build());
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_LINES)
+ .onFile(inputFile)
+ .withValue(duplicatedLines.size())
+ .build());
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_BLOCKS)
+ .onFile(inputFile)
+ .withValue(duplicatedBlocks)
+ .build());
+
+ DuplicationBuilder builder = context.duplicationBuilder(inputFile);
for (CloneGroup duplication : duplications) {
- xml.append("<g>");
+ builder.originBlock(duplication.getOriginPart().getStartLine(), duplication.getOriginPart().getEndLine());
for (ClonePart part : duplication.getCloneParts()) {
- xml.append("<b s=\"").append(part.getStartLine())
- .append("\" l=\"").append(part.getLines())
- .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.getResourceId()))
- .append("\"/>");
+ if (!part.equals(duplication.getOriginPart())) {
+ ((DefaultDuplicationBuilder) builder).isDuplicatedBy(part.getResourceId(), part.getStartLine(), part.getEndLine());
+ }
}
- xml.append("</g>");
}
- xml.append("</duplications>");
- return xml.toString();
+ builder.done();
}
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
index f6f8981f6c1..6d5a7f1201d 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
@@ -23,29 +23,41 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchExtension;
+import org.sonar.api.BatchComponent;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.index.ResourcePersister;
import org.sonar.core.duplication.DuplicationDao;
-public class IndexFactory implements BatchExtension {
+import javax.annotation.Nullable;
+
+public class IndexFactory implements BatchComponent {
private static final Logger LOG = LoggerFactory.getLogger(IndexFactory.class);
private final Settings settings;
private final ResourcePersister resourcePersister;
private final DuplicationDao dao;
+ private final AnalysisMode mode;
- public IndexFactory(Settings settings, ResourcePersister resourcePersister, DuplicationDao dao) {
+ public IndexFactory(AnalysisMode mode, Settings settings, @Nullable ResourcePersister resourcePersister, @Nullable DuplicationDao dao) {
+ this.mode = mode;
this.settings = settings;
this.resourcePersister = resourcePersister;
this.dao = dao;
}
- public SonarDuplicationsIndex create(Project project, String languageKey) {
- if (verifyCrossProject(project, LOG)) {
+ /**
+ * Used by new sensor mode
+ */
+ public IndexFactory(AnalysisMode mode, Settings settings) {
+ this(mode, settings, null, null);
+ }
+
+ public SonarDuplicationsIndex create(@Nullable Project project, String languageKey) {
+ if (verifyCrossProject(project, LOG) && dao != null && resourcePersister != null) {
return new SonarDuplicationsIndex(new DbDuplicationsIndex(resourcePersister, project, dao, languageKey));
}
return new SonarDuplicationsIndex();
@@ -55,11 +67,14 @@ public class IndexFactory implements BatchExtension {
boolean verifyCrossProject(Project project, Logger logger) {
boolean crossProject = false;
- if (settings.getBoolean(CoreProperties.CPD_CROSS_RPOJECT)) {
- if (settings.getBoolean(CoreProperties.DRY_RUN)) {
- logger.info("Cross-project analysis disabled. Not supported on dry runs.");
- } else if (StringUtils.isNotBlank(project.getBranch())) {
+ if (settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT)) {
+ if (mode.isPreview()) {
+ logger.info("Cross-project analysis disabled. Not supported in preview mode.");
+ } else if (StringUtils.isNotBlank(settings.getString(CoreProperties.PROJECT_BRANCH_PROPERTY))) {
logger.info("Cross-project analysis disabled. Not supported on project branches.");
+ } else if (project == null) {
+ // New sensor mode
+ logger.info("Cross-project analysis disabled. Not supported in new sensor mode.");
} else {
logger.info("Cross-project analysis enabled");
crossProject = true;
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java
index 74cb7f5fa5b..7d1bbeb4a5c 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java
@@ -27,6 +27,6 @@ public class CpdPluginTest {
@Test
public void getExtensions() {
- assertThat(new CpdPlugin().getExtensions()).hasSize(9);
+ assertThat(new CpdPlugin().getExtensions()).hasSize(10);
}
}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
index 89128b77a8a..7724fa6f725 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
@@ -25,6 +25,7 @@ import org.sonar.api.batch.fs.internal.DefaultFileSystem;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Java;
+import org.sonar.batch.duplication.BlockCache;
import org.sonar.plugins.cpd.index.IndexFactory;
import static org.fest.assertions.Assertions.assertThat;
@@ -32,16 +33,16 @@ import static org.mockito.Mockito.mock;
public class CpdSensorTest {
- SonarEngine sonarEngine;
- SonarBridgeEngine sonarBridgeEngine;
+ JavaCpdEngine sonarEngine;
+ DefaultCpdEngine sonarBridgeEngine;
CpdSensor sensor;
Settings settings;
@Before
public void setUp() {
IndexFactory indexFactory = mock(IndexFactory.class);
- sonarEngine = new SonarEngine(indexFactory, null, null);
- sonarBridgeEngine = new SonarBridgeEngine(indexFactory, null, null);
+ sonarEngine = new JavaCpdEngine(indexFactory, null, null);
+ sonarBridgeEngine = new DefaultCpdEngine(indexFactory, new CpdMappings(), null, null, mock(BlockCache.class));
settings = new Settings(new PropertyDefinitions(CpdPlugin.class));
DefaultFileSystem fs = new DefaultFileSystem();
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java
index 3923b53fe21..924aa9193c8 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.duplication.BlockCache;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
@@ -33,15 +34,15 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-public class SonarBridgeEngineTest {
+public class DefaultCpdEngineTest {
- private SonarBridgeEngine engine;
+ private DefaultCpdEngine engine;
private Settings settings;
@Before
public void init() {
settings = new Settings();
- engine = new SonarBridgeEngine(null, null, null, settings);
+ engine = new DefaultCpdEngine(null, null, null, settings, mock(BlockCache.class));
}
@Test
@@ -61,53 +62,46 @@ public class SonarBridgeEngineTest {
@Test
public void shouldReturnDefaultBlockSize() {
- assertThat(SonarBridgeEngine.getDefaultBlockSize("cobol")).isEqualTo(30);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("natur")).isEqualTo(20);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("abap")).isEqualTo(20);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("other")).isEqualTo(10);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("cobol")).isEqualTo(30);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("natur")).isEqualTo(20);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("abap")).isEqualTo(20);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("other")).isEqualTo(10);
}
@Test
public void defaultBlockSize() {
- Project project = newProject("foo");
- assertThat(engine.getBlockSize(project, "java")).isEqualTo(10);
+ assertThat(engine.getBlockSize("java")).isEqualTo(10);
}
@Test
public void blockSizeForCobol() {
- Project project = newProject("foo");
settings.setProperty("sonar.cpd.cobol.minimumLines", "42");
- assertThat(engine.getBlockSize(project, "cobol")).isEqualTo(42);
+ assertThat(engine.getBlockSize("cobol")).isEqualTo(42);
}
@Test
public void defaultMinimumTokens() {
- Project project = newProject("foo");
-
- assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE);
}
@Test
public void generalMinimumTokens() {
- Project project = newProject("foo");
settings.setProperty("sonar.cpd.minimumTokens", 33);
- assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(33);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(33);
}
@Test
public void minimumTokensByLanguage() {
- Project javaProject = newProject("foo");
settings.setProperty("sonar.cpd.java.minimumTokens", "42");
settings.setProperty("sonar.cpd.php.minimumTokens", "33");
- assertThat(engine.getMinimumTokens(javaProject, "java")).isEqualTo(42);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(42);
- Project phpProject = newProject("foo");
settings.setProperty("sonar.cpd.java.minimumTokens", "42");
settings.setProperty("sonar.cpd.php.minimumTokens", "33");
- assertThat(engine.getMinimumTokens(phpProject, "php")).isEqualTo(33);
+ assertThat(engine.getMinimumTokens("php")).isEqualTo(33);
}
private static Project newProject(String key) {
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java
new file mode 100644
index 00000000000..d0c50a99b4c
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.plugins.cpd;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.duplications.index.CloneGroup;
+import org.sonar.duplications.index.ClonePart;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class JavaCpdEngineTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ SensorContext context = mock(SensorContext.class);
+ DeprecatedDefaultInputFile inputFile;
+
+ @Before
+ public void before() throws IOException {
+ when(context.measureBuilder()).thenReturn(new DefaultMeasureBuilder());
+ inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java");
+ inputFile.setFile(temp.newFile("Foo.java"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNothingToSave() {
+ JavaCpdEngine.save(context, inputFile, null);
+ JavaCpdEngine.save(context, inputFile, Collections.EMPTY_LIST);
+
+ verifyZeroInteractions(context);
+ }
+
+ @Test
+ public void testOneSimpleDuplicationBetweenTwoFiles() {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "</g></duplications>").build());
+ }
+
+ @Test
+ public void testDuplicationOnSameFile() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(400).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"215\" l=\"200\" r=\"key1\"/>"
+ + "</g></duplications>").build());
+
+ }
+
+ @Test
+ public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "<b s=\"25\" l=\"200\" r=\"key3\"/>"
+ + "</g></duplications>").build());
+
+ }
+
+ @Test
+ public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(
+ newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)),
+ newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(210).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications>"
+ + "<g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "</g>"
+ + "<g>"
+ + "<b s=\"15\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key3\"/>"
+ + "</g>"
+ + "</duplications>").build());
+
+ }
+
+ @Test
+ public void shouldEscapeXmlEntities() throws IOException {
+ InputFile csharpFile = new DeprecatedDefaultInputFile("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs")
+ .setFile(temp.newFile("SubsRedsDelivery.cs"));
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(
+ new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs", 0, 5, 204),
+ new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs", 0, 15, 214)));
+ JavaCpdEngine.save(context, csharpFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(csharpFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery.cs\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery2.cs\"/>"
+ + "</g></duplications>").build());
+ }
+
+ private CloneGroup newCloneGroup(ClonePart... parts) {
+ return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build();
+ }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java
deleted file mode 100644
index c8551a74166..00000000000
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.plugins.cpd;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.test.IsMeasure;
-import org.sonar.duplications.index.CloneGroup;
-import org.sonar.duplications.index.ClonePart;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-public class SonarEngineTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- SensorContext context = mock(SensorContext.class);
- DeprecatedDefaultInputFile inputFile;
-
- @Before
- public void before() throws IOException {
- inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java");
- inputFile.setFile(temp.newFile("Foo.java"));
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void testNothingToSave() {
- SonarEngine.save(context, inputFile, null);
- SonarEngine.save(context, inputFile, Collections.EMPTY_LIST);
-
- verifyZeroInteractions(context);
- }
-
- @Test
- public void testOneSimpleDuplicationBetweenTwoFiles() {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testDuplicationOnSameFile() throws Exception {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 400d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"215\" l=\"200\" r=\"key1\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "<b s=\"25\" l=\"200\" r=\"key3\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception {
- List<CloneGroup> groups = Arrays.asList(
- newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)),
- newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 210d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
- + "<g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "</g>"
- + "<g>"
- + "<b s=\"15\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key3\"/>"
- + "</g>"
- + "</duplications>")));
- }
-
- @Test
- public void shouldEscapeXmlEntities() throws IOException {
- InputFile csharpFile = new DeprecatedDefaultInputFile("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs")
- .setFile(temp.newFile("SubsRedsDelivery.cs"));
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(
- new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs", 0, 5, 204),
- new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs", 0, 15, 214)));
- SonarEngine.save(context, csharpFile, groups);
-
- verify(context).saveMeasure(
- eq(csharpFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery.cs\"/>"
- + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery2.cs\"/>"
- + "</g></duplications>")));
- }
-
- private CloneGroup newCloneGroup(ClonePart... parts) {
- return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build();
- }
-
-}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
index 45510dc40ac..7b9fa524fec 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
@@ -25,12 +25,14 @@ import org.slf4j.Logger;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.index.ResourcePersister;
import org.sonar.core.duplication.DuplicationDao;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class IndexFactoryTest {
@@ -38,41 +40,43 @@ public class IndexFactoryTest {
Settings settings;
IndexFactory factory;
Logger logger;
+ private AnalysisMode analysisMode;
@Before
public void setUp() {
project = new Project("foo");
settings = new Settings();
- factory = new IndexFactory(settings, mock(ResourcePersister.class), mock(DuplicationDao.class));
+ analysisMode = mock(AnalysisMode.class);
+ factory = new IndexFactory(analysisMode, settings, mock(ResourcePersister.class), mock(DuplicationDao.class));
logger = mock(Logger.class);
}
@Test
public void crossProjectEnabled() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
assertThat(factory.verifyCrossProject(project, logger)).isTrue();
verify(logger).info("Cross-project analysis enabled");
}
@Test
public void noCrossProjectWithBranch() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
- project.setBranch("branch");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
+ settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "branch");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
verify(logger).info("Cross-project analysis disabled. Not supported on project branches.");
}
@Test
- public void cross_project_should_be_disabled_on_dry_run() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
- settings.setProperty(CoreProperties.DRY_RUN, "true");
+ public void cross_project_should_be_disabled_on_preview() {
+ when(analysisMode.isPreview()).thenReturn(true);
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
- verify(logger).info("Cross-project analysis disabled. Not supported on dry runs.");
+ verify(logger).info("Cross-project analysis disabled. Not supported in preview mode.");
}
@Test
public void crossProjectDisabled() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "false");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "false");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
verify(logger).info("Cross-project analysis disabled");
}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java
new file mode 100644
index 00000000000..21876b8f3d5
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.plugins.cpd.medium;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.batch.duplication.DuplicationGroup;
+import org.sonar.batch.mediumtest.BatchMediumTester;
+import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
+import org.sonar.plugins.cpd.CpdPlugin;
+import org.sonar.xoo.XooPlugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class CpdMediumTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ public BatchMediumTester tester = BatchMediumTester.builder()
+ .registerPlugin("xoo", new XooPlugin())
+ .registerPlugin("cpd", new CpdPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor"))
+ .build();
+
+ private File baseDir;
+
+ private ImmutableMap.Builder<String, String> builder;
+
+ @Before
+ public void prepare() throws IOException {
+ tester.start();
+
+ baseDir = temp.newFolder();
+
+ builder = ImmutableMap.<String, String>builder()
+ .put("sonar.task", "scan")
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.projectName", "Foo Project")
+ .put("sonar.projectVersion", "1.0-SNAPSHOT")
+ .put("sonar.projectDescription", "Description of Foo Project");
+ }
+
+ @After
+ public void stop() {
+ tester.stop();
+ }
+
+ @Test
+ public void testDuplications() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti";
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ FileUtils.write(xooFile1, duplicatedStuff);
+
+ File xooFile2 = new File(srcDir, "sample2.xoo");
+ FileUtils.write(xooFile2, duplicatedStuff);
+
+ TaskResult result = tester.newTask()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.verbose", "true")
+ .build())
+ .start();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ // 4 measures per file
+ assertThat(result.measures()).hasSize(6);
+
+ InputFile inputFile = result.inputFiles().get(0);
+ // One clone group
+ List<DuplicationGroup> duplicationGroups = result.duplicationsFor(inputFile);
+ assertThat(duplicationGroups).hasSize(1);
+
+ DuplicationGroup cloneGroup = duplicationGroups.get(0);
+ assertThat(cloneGroup.duplicates()).hasSize(1);
+ assertThat(cloneGroup.originBlock().startLine()).isEqualTo(1);
+ assertThat(cloneGroup.originBlock().length()).isEqualTo(17);
+ }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java
new file mode 100644
index 00000000000..62f7d2ba782
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java
@@ -0,0 +1,36 @@
+/*
+ * 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.xoo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public interface XooConstants {
+
+ String PLUGIN_KEY = "xoo";
+ String PLUGIN_NAME = "Xoo";
+
+ String REPOSITORY_KEY = PLUGIN_KEY;
+ String REPOSITORY_NAME = PLUGIN_NAME;
+
+ String[] FILE_SUFFIXES = {"xoo"};
+
+ Logger LOG = LoggerFactory.getLogger("xoo");
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index 95b65dd31a3..3477fab825c 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -20,6 +20,14 @@
package org.sonar.xoo;
import org.sonar.api.SonarPlugin;
+import org.sonar.xoo.lang.MeasureSensor;
+import org.sonar.xoo.lang.ScmActivitySensor;
+import org.sonar.xoo.lang.SymbolReferencesSensor;
+import org.sonar.xoo.lang.SyntaxHighlightingSensor;
+import org.sonar.xoo.lang.XooTokenizerSensor;
+import org.sonar.xoo.rule.CreateIssueByInternalKeySensor;
+import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
+import org.sonar.xoo.rule.OneIssuePerLineSensor;
import org.sonar.xoo.rule.XooQualityProfile;
import org.sonar.xoo.rule.XooRulesDefinition;
@@ -39,7 +47,19 @@ public class XooPlugin extends SonarPlugin {
return Arrays.asList(
Xoo.class,
XooRulesDefinition.class,
- XooQualityProfile.class);
+ XooQualityProfile.class,
+
+ // sensors
+ MeasureSensor.class,
+ ScmActivitySensor.class,
+ SyntaxHighlightingSensor.class,
+ SymbolReferencesSensor.class,
+ XooTokenizerSensor.class,
+
+ OneIssuePerLineSensor.class,
+ OneIssueOnDirPerFileSensor.class,
+ CreateIssueByInternalKeySensor.class
+ );
}
}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java
new file mode 100644
index 00000000000..56e013b3716
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java
@@ -0,0 +1,119 @@
+/*
+ * 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.xoo.lang;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.measure.MetricFinder;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.measure.MeasureBuilder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Parse files *.xoo.measures
+ */
+public class MeasureSensor implements Sensor {
+
+ private static final String MEASURES_EXTENSION = ".measures";
+
+ private MetricFinder metricFinder;
+
+ public MeasureSensor(MetricFinder metricFinder) {
+ this.metricFinder = metricFinder;
+ }
+
+ private void processFileMeasures(InputFile inputFile, SensorContext context) {
+ File ioFile = inputFile.file();
+ File measureFile = new File(ioFile.getParentFile(), ioFile.getName() + MEASURES_EXTENSION);
+ if (measureFile.exists()) {
+ XooConstants.LOG.debug("Processing " + measureFile.getAbsolutePath());
+ try {
+ List<String> lines = FileUtils.readLines(measureFile, context.fileSystem().encoding().name());
+ int lineNumber = 0;
+ for (String line : lines) {
+ lineNumber++;
+ if (StringUtils.isBlank(line)) {
+ continue;
+ }
+ if (line.startsWith("#")) {
+ continue;
+ }
+ try {
+ String metricKey = StringUtils.substringBefore(line, ":");
+ String value = line.substring(metricKey.length() + 1);
+ context.addMeasure(createMeasure(context, inputFile, metricKey, value));
+ } catch (Exception e) {
+ throw new IllegalStateException("Error processing line " + lineNumber + " of file " + measureFile.getAbsolutePath(), e);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private Measure<?> createMeasure(SensorContext context, InputFile xooFile, String metricKey, String value) {
+ org.sonar.api.batch.measure.Metric<Serializable> metric = metricFinder.findByKey(metricKey);
+ MeasureBuilder<Serializable> builder = context.measureBuilder()
+ .forMetric(metric)
+ .onFile(xooFile);
+ if (Boolean.class.equals(metric.valueType())) {
+ builder.withValue(Boolean.parseBoolean(value));
+ } else if (Integer.class.equals(metric.valueType())) {
+ builder.withValue(Integer.valueOf(value));
+ } else if (Double.class.equals(metric.valueType())) {
+ builder.withValue(Double.valueOf(value));
+ } else if (String.class.equals(metric.valueType())) {
+ builder.withValue(value);
+ } else if (Long.class.equals(metric.valueType())) {
+ builder.withValue(Long.valueOf(value));
+ } else {
+ throw new UnsupportedOperationException("Unsupported type :" + metric.valueType());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("Xoo Measure Sensor")
+ .provides(CoreMetrics.LINES)
+ .workOnLanguages(Xoo.KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ processFileMeasures(file, context);
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java
new file mode 100644
index 00000000000..12663b0b697
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java
@@ -0,0 +1,112 @@
+/*
+ * 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.xoo.lang;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.FileLinesContext;
+import org.sonar.api.measures.FileLinesContextFactory;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.xoo.Xoo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+public class ScmActivitySensor implements Sensor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ScmActivitySensor.class);
+
+ private static final String SCM_EXTENSION = ".scm";
+
+ private final FileSystem fs;
+ private final FileLinesContextFactory fileLinesContextFactory;
+
+ public ScmActivitySensor(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem) {
+ this.fs = fileSystem;
+ this.fileLinesContextFactory = fileLinesContextFactory;
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name(this.getClass().getSimpleName())
+ .provides(CoreMetrics.SCM_AUTHORS_BY_LINE,
+ CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE,
+ CoreMetrics.SCM_REVISIONS_BY_LINE)
+ .workOnLanguages(Xoo.KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(Xoo.KEY))) {
+ processFile(inputFile);
+ }
+
+ }
+
+ @VisibleForTesting
+ protected void processFile(InputFile inputFile) {
+ File ioFile = inputFile.file();
+ File scmDataFile = new java.io.File(ioFile.getParentFile(), ioFile.getName() + SCM_EXTENSION);
+ if (!scmDataFile.exists()) {
+ LOG.debug("Skipping SCM data injection for " + inputFile.relativePath());
+ return;
+ }
+
+ FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);
+ try {
+ List<String> lines = FileUtils.readLines(scmDataFile, Charsets.UTF_8.name());
+ int lineNumber = 0;
+ for (String line : lines) {
+ lineNumber++;
+ if (StringUtils.isNotBlank(line)) {
+ // revision,author,dateTime
+ String[] fields = StringUtils.split(line, ',');
+ if (fields.length < 3) {
+ throw new IllegalStateException("Not enough fields on line " + lineNumber);
+ }
+ String revision = fields[0];
+ String author = fields[1];
+ // Will throw an exception, when date is not in format "yyyy-MM-dd"
+ Date date = DateUtils.parseDate(fields[2]);
+
+ fileLinesContext.setStringValue(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY, lineNumber, revision);
+ fileLinesContext.setStringValue(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY, lineNumber, author);
+ fileLinesContext.setStringValue(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, lineNumber, DateUtils.formatDateTime(date));
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ fileLinesContext.save();
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java
new file mode 100644
index 00000000000..c8cafc2d705
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java
@@ -0,0 +1,98 @@
+/*
+ * 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.xoo.lang;
+
+import com.google.common.base.Splitter;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.symbol.Symbol;
+import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Parse files *.xoo.symbol
+ */
+public class SymbolReferencesSensor implements Sensor {
+
+ private static final String SYMBOL_EXTENSION = ".symbol";
+
+ private void processFileHighlighting(InputFile inputFile, SensorContext context) {
+ File ioFile = inputFile.file();
+ File symbolFile = new File(ioFile.getParentFile(), ioFile.getName() + SYMBOL_EXTENSION);
+ if (symbolFile.exists()) {
+ XooConstants.LOG.debug("Processing " + symbolFile.getAbsolutePath());
+ try {
+ List<String> lines = FileUtils.readLines(symbolFile, context.fileSystem().encoding().name());
+ int lineNumber = 0;
+ SymbolTableBuilder symbolTableBuilder = context.symbolTableBuilder(inputFile);
+ for (String line : lines) {
+ lineNumber++;
+ if (StringUtils.isBlank(line)) {
+ continue;
+ }
+ if (line.startsWith("#")) {
+ continue;
+ }
+ try {
+ Iterator<String> split = Splitter.on(",").split(line).iterator();
+ int startOffset = Integer.parseInt(split.next());
+ int endOffset = Integer.parseInt(split.next());
+ Symbol s = symbolTableBuilder.newSymbol(startOffset, endOffset);
+ while (split.hasNext()) {
+ symbolTableBuilder.newReference(s, Integer.parseInt(split.next()));
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Error processing line " + lineNumber + " of file " + symbolFile.getAbsolutePath(), e);
+ }
+ }
+ symbolTableBuilder.done();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("Xoo Symbol Reference Sensor")
+ .provides(CoreMetrics.LINES)
+ .workOnLanguages(Xoo.KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ processFileHighlighting(file, context);
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java
new file mode 100644
index 00000000000..0ae23954442
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.xoo.lang;
+
+import com.google.common.base.Splitter;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Parse files *.xoo.highlighting
+ */
+public class SyntaxHighlightingSensor implements Sensor {
+
+ private static final String HIGHLIGHTING_EXTENSION = ".highlighting";
+
+ private void processFileHighlighting(InputFile inputFile, SensorContext context) {
+ File ioFile = inputFile.file();
+ File highlightingFile = new File(ioFile.getParentFile(), ioFile.getName() + HIGHLIGHTING_EXTENSION);
+ if (highlightingFile.exists()) {
+ XooConstants.LOG.debug("Processing " + highlightingFile.getAbsolutePath());
+ try {
+ List<String> lines = FileUtils.readLines(highlightingFile, context.fileSystem().encoding().name());
+ int lineNumber = 0;
+ HighlightingBuilder highlightingBuilder = context.highlightingBuilder(inputFile);
+ for (String line : lines) {
+ lineNumber++;
+ if (StringUtils.isBlank(line)) {
+ continue;
+ }
+ if (line.startsWith("#")) {
+ continue;
+ }
+ try {
+ Iterator<String> split = Splitter.on(":").split(line).iterator();
+ int startOffset = Integer.parseInt(split.next());
+ int endOffset = Integer.parseInt(split.next());
+ HighlightingBuilder.TypeOfText type = HighlightingBuilder.TypeOfText.forCssClass(split.next());
+ highlightingBuilder.highlight(startOffset, endOffset, type);
+ } catch (Exception e) {
+ throw new IllegalStateException("Error processing line " + lineNumber + " of file " + highlightingFile.getAbsolutePath(), e);
+ }
+ }
+ highlightingBuilder.done();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("Xoo Highlighting Sensor")
+ .provides(CoreMetrics.LINES)
+ .workOnLanguages(Xoo.KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ processFileHighlighting(file, context);
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java
new file mode 100644
index 00000000000..1ee33e560ef
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java
@@ -0,0 +1,84 @@
+/*
+ * 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.xoo.lang;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
+import org.sonar.xoo.Xoo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Parse files *.xoo.highlighting
+ */
+public class XooTokenizerSensor implements Sensor {
+
+ private void computeTokens(InputFile inputFile, SensorContext context) {
+ TokenBuilder tokenBuilder = context.tokenBuilder(inputFile);
+ File ioFile = inputFile.file();
+ int lineId = 0;
+ try {
+ for (String line : FileUtils.readLines(ioFile)) {
+ lineId++;
+ for (String token : Splitter.on(" ").split(line)) {
+ tokenBuilder.addToken(lineId, token);
+ }
+ }
+ tokenBuilder.done();
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to read file " + ioFile, e);
+ }
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("Xoo Tokenizer Sensor")
+ .workOnLanguages(Xoo.KEY)
+ .workOnFileTypes(InputFile.Type.MAIN);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ String[] cpdExclusions = context.settings().getStringArray(CoreProperties.CPD_EXCLUSIONS);
+ FilePredicates p = context.fileSystem().predicates();
+ List<InputFile> sourceFiles = Lists.newArrayList(context.fileSystem().inputFiles(p.and(
+ p.hasType(InputFile.Type.MAIN),
+ p.hasLanguage(Xoo.KEY),
+ p.doesNotMatchPathPatterns(cpdExclusions)
+ )));
+ if (sourceFiles.isEmpty()) {
+ return;
+ }
+ for (InputFile file : sourceFiles) {
+ computeTokens(file, context);
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
new file mode 100644
index 00000000000..c3e7d2641e6
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.xoo.rule;
+
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+public class CreateIssueByInternalKeySensor implements Sensor {
+
+ private static final String INTERNAL_KEY_PROPERTY = "sonar.xoo.internalKey";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("CreateIssueByInternalKeySensor")
+ .workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ createIssues(file, context);
+ }
+ }
+
+ private void createIssues(InputFile file, SensorContext context) {
+ ActiveRule rule = context.activeRules().findByInternalKey(XooConstants.REPOSITORY_KEY,
+ context.settings().getString(INTERNAL_KEY_PROPERTY));
+ if (rule != null) {
+ context.addIssue(context.issueBuilder()
+ .ruleKey(rule.ruleKey())
+ .onFile(file)
+ .message("This issue is generated on each file")
+ .build());
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
new file mode 100644
index 00000000000..c2478830975
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.xoo.rule;
+
+import org.sonar.api.batch.fs.InputDir;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+public class OneIssueOnDirPerFileSensor implements Sensor {
+
+ public static final String RULE_KEY = "OneIssueOnDirPerFile";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("One Issue On Dir Per File")
+ .workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ createIssues(file, context);
+ }
+ }
+
+ private void createIssues(InputFile file, SensorContext context) {
+ RuleKey ruleKey = RuleKey.of(XooConstants.REPOSITORY_KEY, RULE_KEY);
+ InputDir inputDir = context.fileSystem().inputDir(file.file().getParentFile());
+ if (inputDir != null) {
+ context.addIssue(context.issueBuilder()
+ .ruleKey(ruleKey)
+ .onDir(inputDir)
+ .message("This issue is generated for file " + file.relativePath())
+ .build());
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
new file mode 100644
index 00000000000..bc0697b64b4
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
@@ -0,0 +1,76 @@
+/*
+ * 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.xoo.rule;
+
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.IssueBuilder;
+import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
+
+public class OneIssuePerLineSensor implements Sensor {
+
+ public static final String RULE_KEY = "OneIssuePerLine";
+ private static final String EFFORT_TO_FIX_PROPERTY = "sonar.oneIssuePerLine.effortToFix";
+ private static final String FORCE_SEVERITY_PROPERTY = "sonar.oneIssuePerLine.forceSeverity";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("One Issue Per Line")
+ .dependsOn(CoreMetrics.LINES)
+ .workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
+ .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ createIssues(file, context);
+ }
+ }
+
+ private void createIssues(InputFile file, SensorContext context) {
+ RuleKey ruleKey = RuleKey.of(XooConstants.REPOSITORY_KEY, RULE_KEY);
+ Measure<Integer> linesMeasure = context.getMeasure(file, CoreMetrics.LINES);
+ if (linesMeasure == null) {
+ LoggerFactory.getLogger(getClass()).warn("Missing measure " + CoreMetrics.LINES_KEY + " on " + file);
+ } else {
+ IssueBuilder issueBuilder = context.issueBuilder();
+ for (int line = 1; line <= (Integer) linesMeasure.value(); line++) {
+ context.addIssue(issueBuilder
+ .ruleKey(ruleKey)
+ .onFile(file)
+ .atLine(line)
+ .effortToFix(context.settings().getDouble(EFFORT_TO_FIX_PROPERTY))
+ .severity(context.settings().getString(FORCE_SEVERITY_PROPERTY))
+ .message("This issue is generated on each line")
+ .build());
+ }
+ }
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
index b65ad573daa..bfe1fd99afa 100644
--- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
@@ -27,6 +27,6 @@ public class XooPluginTest {
@Test
public void provide_extensions() {
- assertThat(new XooPlugin().getExtensions()).hasSize(3);
+ assertThat(new XooPlugin().getExtensions()).hasSize(11);
}
}