diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-07-25 16:31:45 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-07-30 18:03:36 +0200 |
commit | 6074164392edd3db2dfdfd21d05cd56c19e2b0e6 (patch) | |
tree | b9314796d68c4c396dcf45a1ab689b06490fd4a2 /plugins | |
parent | 12f243728f42a5eb1e714ff15f0240109193f1d8 (diff) | |
download | sonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.tar.gz sonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.zip |
SONAR-5389 New duplication API
Diffstat (limited to 'plugins')
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 & Reds/SubsRedsDelivery.cs\"/>" + + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs & 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 & Reds/SubsRedsDelivery.cs\"/>" - + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs & 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); } } |