import org.sonar.plugins.core.issue.IssueTrackingDecorator;
import org.sonar.plugins.core.issue.IssuesDensityDecorator;
import org.sonar.plugins.core.issue.WeightedIssuesDecorator;
+import org.sonar.plugins.core.issue.ignore.IgnoreIssuesPlugin;
import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
})
public final class CorePlugin extends SonarPlugin {
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
public List getExtensions() {
- return ImmutableList.of(
+ ImmutableList.Builder<Object> extensions = ImmutableList.builder();
+
+ extensions.add(
DefaultResourceTypes.class,
UserManagedMetrics.class,
Periods.class,
// Notify alerts on my favourite projects
NewAlerts.class,
NewAlerts.newMetadata());
+
+ extensions.addAll(IgnoreIssuesPlugin.getExtensions());
+
+ return extensions.build();
}
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+public interface Constants {
+ // New Properties
+ String SUB_CATEGORY_IGNORE_ISSUES = "Ignore Issues";
+
+ String CORE_KEY_PREFIX = "sonar.issue.ignore";
+ String DEPRECATED_PLUGIN_KEY_PREFIX = "sonar.switchoffviolations";
+
+ String MULTICRITERIA_SUFFIX = ".multicriteria";
+ String PATTERNS_MULTICRITERIA_KEY = CORE_KEY_PREFIX + MULTICRITERIA_SUFFIX;
+ String DEPRECATED_MULTICRITERIA_KEY = DEPRECATED_PLUGIN_KEY_PREFIX + MULTICRITERIA_SUFFIX;
+ String RESOURCE_KEY = "resourceKey";
+ String RULE_KEY = "ruleKey";
+ String LINE_RANGE_KEY = "lineRange";
+
+ String BLOCK_SUFFIX = ".block";
+ String PATTERNS_BLOCK_KEY = CORE_KEY_PREFIX + BLOCK_SUFFIX;
+ String DEPRECATED_BLOCK_KEY = DEPRECATED_PLUGIN_KEY_PREFIX + BLOCK_SUFFIX;
+ String BEGIN_BLOCK_REGEXP = "beginBlockRegexp";
+ String END_BLOCK_REGEXP = "endBlockRegexp";
+
+ String ALLFILE_SUFFIX = ".allfile";
+ String PATTERNS_ALLFILE_KEY = CORE_KEY_PREFIX + ALLFILE_SUFFIX;
+ String DEPRECATED_ALLFILE_KEY = DEPRECATED_PLUGIN_KEY_PREFIX + ALLFILE_SUFFIX;
+ String FILE_REGEXP = "fileRegexp";
+}
--- /dev/null
+// POLOP_IS_GREAT
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+import org.sonar.api.PropertyType;
+
+import org.sonar.api.config.PropertyFieldDefinition;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.CoreProperties;
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.config.PropertyDefinition;
+
+import java.util.List;
+
+public class IgnoreIssuesConfiguration {
+
+
+ static final int LARGE_SIZE = 20;
+ static final int SMALL_SIZE = 10;
+
+ public static List<PropertyDefinition> getPropertyDefinitions() {
+ return ImmutableList.of(
+ PropertyDefinition.builder(Constants.PATTERNS_MULTICRITERIA_KEY)
+ .category(CoreProperties.CATEGORY_EXCLUSIONS)
+ .subCategory(Constants.SUB_CATEGORY_IGNORE_ISSUES)
+ .deprecatedKey(Constants.DEPRECATED_MULTICRITERIA_KEY)
+ .name("Resource Key Pattern")
+ .description("Patterns used to identify which violations to switch off.<br/>" +
+ "More information on the <a href=\"http://docs.codehaus.org/display/SONAR/Project+Administration#ProjectAdministration-IgnoringIssues\">Project Administration page</a>.<br/>")
+ .onQualifiers(Qualifiers.PROJECT)
+ .fields(
+ PropertyFieldDefinition.build(Constants.RESOURCE_KEY)
+ .name("Resource Key Pattern")
+ .description("Pattern used to match resources which should be ignored")
+ .type(PropertyType.STRING)
+ .indicativeSize(LARGE_SIZE)
+ .build(),
+ PropertyFieldDefinition.build(Constants.RULE_KEY)
+ .name("Rule Key Pattern")
+ .description("Pattern used to match rules which should be ignored")
+ .type(PropertyType.STRING)
+ .indicativeSize(LARGE_SIZE)
+ .build(),
+ PropertyFieldDefinition.build(Constants.LINE_RANGE_KEY)
+ .name("Line Range")
+ .description("Range of lines that should be ignored.")
+ .type(PropertyType.STRING)
+ .indicativeSize(SMALL_SIZE)
+ .build())
+ .build(),
+ PropertyDefinition.builder(Constants.PATTERNS_BLOCK_KEY)
+ .category(CoreProperties.CATEGORY_EXCLUSIONS)
+ .subCategory(Constants.SUB_CATEGORY_IGNORE_ISSUES)
+ .deprecatedKey(Constants.DEPRECATED_BLOCK_KEY)
+ .name("Block exclusion patterns")
+ .description("Patterns used to identify blocks in which violations are switched off.<br/>" +
+ "More information on the <a href=\"http://docs.codehaus.org/display/SONAR/Project+Administration#ProjectAdministration-IgnoringIssues\">Project Administration page</a>.<br/>")
+ .onQualifiers(Qualifiers.PROJECT)
+ .fields(
+ PropertyFieldDefinition.build(Constants.BEGIN_BLOCK_REGEXP)
+ .name("Regular expression for start of block")
+ .description("If this regular expression is found in a resource, then following lines are ignored until end of block.")
+ .type(PropertyType.STRING)
+ .indicativeSize(LARGE_SIZE)
+ .build(),
+ PropertyFieldDefinition.build(Constants.END_BLOCK_REGEXP)
+ .name("Regular expression for end of block")
+ .description("If specified, this regular expression is used to determine the end of code blocks to ignore. If not, then block ends at the end of file.")
+ .type(PropertyType.STRING)
+ .indicativeSize(LARGE_SIZE)
+ .build())
+ .build(),
+ PropertyDefinition.builder(Constants.PATTERNS_ALLFILE_KEY)
+ .category(CoreProperties.CATEGORY_EXCLUSIONS)
+ .subCategory(Constants.SUB_CATEGORY_IGNORE_ISSUES)
+ .deprecatedKey(Constants.DEPRECATED_ALLFILE_KEY)
+ .name("File exclusion patterns")
+ .description("Patterns used to identify files in which violations are switched off.<br/>" +
+ "More information on the <a href=\"http://docs.codehaus.org/display/SONAR/Project+Administration#ProjectAdministration-IgnoringIssues\">Project Administration page</a>.<br/>")
+ .onQualifiers(Qualifiers.PROJECT)
+ .fields(
+ PropertyFieldDefinition.build(Constants.FILE_REGEXP)
+ .name("Regular expression")
+ .description("If this regular expression is found in a resource, then following lines are ignored.")
+ .type(PropertyType.STRING)
+ .indicativeSize(LARGE_SIZE)
+ .build())
+ .build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueFilter;
+import org.sonar.plugins.core.issue.ignore.pattern.Pattern;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+
+import java.util.List;
+
+public final class IgnoreIssuesFilter implements IssueFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IgnoreIssuesFilter.class);
+
+ private PatternsInitializer patternsInitializer;
+
+ public IgnoreIssuesFilter(PatternsInitializer patternsInitializer) {
+ this.patternsInitializer = patternsInitializer;
+ }
+
+ public boolean accept(Issue issue) {
+ Pattern extraPattern = patternsInitializer.getExtraPattern(issue.componentKey());
+ LOG.debug("Extra pattern for resource {}: {}", issue.componentKey(), extraPattern);
+ if (extraPattern != null && extraPattern.match(issue)) {
+ logExclusion(issue, extraPattern);
+ return false;
+ }
+
+ List<Pattern> patterns = patternsInitializer.getMulticriteriaPatterns();
+ for (Pattern pattern : patterns) {
+ if (pattern.match(issue)) {
+ logExclusion(issue, pattern);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void logExclusion(Issue issue, Pattern pattern) {
+ LOG.debug("Issue {} ignored by {}", issue, pattern);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+import org.sonar.plugins.core.issue.ignore.scanner.RegexpScanner;
+import org.sonar.plugins.core.issue.ignore.scanner.SourceScanner;
+
+import java.util.List;
+
+public final class IgnoreIssuesPlugin {
+
+ public static List<?> getExtensions() {
+ ImmutableList.Builder<Object> extensions = ImmutableList.builder();
+
+ extensions.addAll(IgnoreIssuesConfiguration.getPropertyDefinitions());
+ extensions.add(
+ PatternsInitializer.class,
+ RegexpScanner.class,
+ SourceScanner.class,
+ IgnoreIssuesFilter.class);
+
+ return extensions.build();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+public class LineRange {
+ private int from;
+ private int to;
+
+ public LineRange(int from, int to) {
+ Preconditions.checkArgument(from <= to, "Line range is not valid: %s must be greater than %s", from, to);
+
+ this.from = from;
+ this.to = to;
+ }
+
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + from + "-" + to + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + from;
+ result = prime * result + to;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ if (fieldsDiffer((LineRange) obj)) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean fieldsDiffer(LineRange other) {
+ if (from != other.from) {
+ return true;
+ }
+ if (to != other.to) {
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.WildcardPattern;
+
+import java.util.Set;
+
+public class Pattern {
+
+ private WildcardPattern resourcePattern;
+ private WildcardPattern rulePattern;
+ private Set<Integer> lines = Sets.newLinkedHashSet();
+ private Set<LineRange> lineRanges = Sets.newLinkedHashSet();
+ private String beginBlockRegexp;
+ private String endBlockRegexp;
+ private String allFileRegexp;
+ private boolean checkLines = true;
+
+ public Pattern() {
+ }
+
+ public Pattern(String resourcePattern, String rulePattern) {
+ this.resourcePattern = WildcardPattern.create(resourcePattern);
+ this.rulePattern = WildcardPattern.create(rulePattern);
+ }
+
+ public Pattern(String resourcePattern, String rulePattern, Set<LineRange> lineRanges) {
+ this(resourcePattern, rulePattern);
+ this.lineRanges = lineRanges;
+ }
+
+ public WildcardPattern getResourcePattern() {
+ return resourcePattern;
+ }
+
+ public WildcardPattern getRulePattern() {
+ return rulePattern;
+ }
+
+ public String getBeginBlockRegexp() {
+ return beginBlockRegexp;
+ }
+
+ public String getEndBlockRegexp() {
+ return endBlockRegexp;
+ }
+
+ public String getAllFileRegexp() {
+ return allFileRegexp;
+ }
+
+ Pattern addLineRange(int fromLineId, int toLineId) {
+ lineRanges.add(new LineRange(fromLineId, toLineId));
+ return this;
+ }
+
+ Pattern addLine(int lineId) {
+ lines.add(lineId);
+ return this;
+ }
+
+ boolean isCheckLines() {
+ return checkLines;
+ }
+
+ Pattern setCheckLines(boolean b) {
+ this.checkLines = b;
+ return this;
+ }
+
+ Pattern setBeginBlockRegexp(String beginBlockRegexp) {
+ this.beginBlockRegexp = beginBlockRegexp;
+ return this;
+ }
+
+ Pattern setEndBlockRegexp(String endBlockRegexp) {
+ this.endBlockRegexp = endBlockRegexp;
+ return this;
+ }
+
+ Pattern setAllFileRegexp(String allFileRegexp) {
+ this.allFileRegexp = allFileRegexp;
+ return this;
+ }
+
+ Set<Integer> getAllLines() {
+ Set<Integer> allLines = Sets.newLinkedHashSet(lines);
+ for (LineRange lineRange : lineRanges) {
+ allLines.addAll(lineRange.toLines());
+ }
+ return allLines;
+ }
+
+ public boolean match(Issue violation) {
+ boolean match = matchResource(violation.componentKey()) && matchRule(violation.ruleKey());
+ if (checkLines && violation.line() != null) {
+ match = match && matchLine(violation.line());
+ }
+ return match;
+ }
+
+ boolean matchLine(int lineId) {
+ if (lines.contains(lineId)) {
+ return true;
+ }
+
+ for (LineRange range : lineRanges) {
+ if (range.in(lineId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ boolean matchRule(RuleKey rule) {
+ System.out.printf("Matching rule {} against pattern {}", rule, rulePattern);
+ if (rule == null) {
+ return false;
+ }
+
+ String key = new StringBuilder().append(rule.repository()).append(':').append(rule.rule()).toString();
+ return rulePattern.match(key);
+ }
+
+ boolean matchResource(String resource) {
+ System.out.printf("Matching resource {} against pattern {}", resource, resourcePattern);
+ return resource != null && resourcePattern.match(resource);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class PatternDecoder {
+
+ private static final int THREE_FIELDS_PER_LINE = 3;
+ private static final String LINE_RANGE_REGEXP = "\\[((\\d+|\\d+-\\d+),?)*\\]";
+
+ public List<Pattern> decode(String patternsList) {
+ List<Pattern> patterns = Lists.newLinkedList();
+ String[] patternsLines = StringUtils.split(patternsList, "\n");
+ for (String patternLine : patternsLines) {
+ Pattern pattern = decodeLine(patternLine.trim());
+ if (pattern != null) {
+ patterns.add(pattern);
+ }
+ }
+ return patterns;
+ }
+
+ public List<Pattern> decode(File file) {
+ try {
+ List<String> lines = FileUtils.readLines(file);
+ List<Pattern> patterns = Lists.newLinkedList();
+ for (String line : lines) {
+ Pattern pattern = decodeLine(line);
+ if (pattern != null) {
+ patterns.add(pattern);
+ }
+ }
+ return patterns;
+
+ } catch (IOException e) {
+ throw new SonarException("Fail to load the file: " + file.getAbsolutePath(), e);
+ }
+ }
+
+ /**
+ * Main method that decodes a line which defines a pattern
+ */
+ public Pattern decodeLine(String line) {
+ if (isBlankOrComment(line)) {
+ return null;
+ }
+
+ String[] fields = StringUtils.splitPreserveAllTokens(line, ';');
+ if (fields.length > THREE_FIELDS_PER_LINE) {
+ throw new SonarException("Invalid format. The following line has more than 3 fields separated by comma: " + line);
+ }
+
+ Pattern pattern;
+ if (fields.length == THREE_FIELDS_PER_LINE) {
+ checkRegularLineConstraints(line, fields);
+ pattern = new Pattern(StringUtils.trim(fields[0]), StringUtils.trim(fields[1]));
+ decodeRangeOfLines(pattern, fields[2]);
+ } else if (fields.length == 2) {
+ checkDoubleRegexpLineConstraints(line, fields);
+ pattern = new Pattern().setBeginBlockRegexp(fields[0]).setEndBlockRegexp(fields[1]);
+ } else {
+ pattern = new Pattern().setAllFileRegexp(fields[0]);
+ }
+
+ return pattern;
+ }
+
+ private void checkRegularLineConstraints(String line, String[] fields) {
+ if (!isResource(fields[0])) {
+ throw new SonarException("Invalid format. The first field does not define a resource pattern: " + line);
+ }
+ if (!isRule(fields[1])) {
+ throw new SonarException("Invalid format. The second field does not define a rule pattern: " + line);
+ }
+ if (!isLinesRange(fields[2])) {
+ throw new SonarException("Invalid format. The third field does not define a range of lines: " + line);
+ }
+ }
+
+ private void checkDoubleRegexpLineConstraints(String line, String[] fields) {
+ if (!isRegexp(fields[0])) {
+ throw new SonarException("Invalid format. The first field does not define a regular expression: " + line);
+ }
+ if (!isRegexp(fields[1])) {
+ throw new SonarException("Invalid format. The second field does not define a regular expression: " + line);
+ }
+ }
+
+ public static void decodeRangeOfLines(Pattern pattern, String field) {
+ if (StringUtils.equals(field, "*")) {
+ pattern.setCheckLines(false);
+ } else {
+ pattern.setCheckLines(true);
+ String s = StringUtils.substringBetween(StringUtils.trim(field), "[", "]");
+ String[] parts = StringUtils.split(s, ',');
+ for (String part : parts) {
+ if (StringUtils.contains(part, '-')) {
+ String[] range = StringUtils.split(part, '-');
+ pattern.addLineRange(Integer.valueOf(range[0]), Integer.valueOf(range[1]));
+ } else {
+ pattern.addLine(Integer.valueOf(part));
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ boolean isLinesRange(String field) {
+ return StringUtils.equals(field, "*") || java.util.regex.Pattern.matches(LINE_RANGE_REGEXP, field);
+ }
+
+ @VisibleForTesting
+ boolean isBlankOrComment(String line) {
+ return StringUtils.isBlank(line) || StringUtils.startsWith(line, "#");
+ }
+
+ @VisibleForTesting
+ boolean isResource(String field) {
+ return StringUtils.isNotBlank(field);
+ }
+
+ @VisibleForTesting
+ boolean isRule(String field) {
+ return StringUtils.isNotBlank(field);
+ }
+
+ @VisibleForTesting
+ boolean isRegexp(String field) {
+ return StringUtils.isNotBlank(field);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.config.Settings;
+import org.sonar.plugins.core.issue.ignore.Constants;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+public class PatternsInitializer implements BatchExtension {
+
+ private final Settings settings;
+
+ private List<Pattern> multicriteriaPatterns;
+ private List<Pattern> blockPatterns;
+ private List<Pattern> allFilePatterns;
+ private Map<String, Pattern> extraPatternByResource = Maps.newHashMap();
+
+ public PatternsInitializer(Settings settings) {
+ this.settings = settings;
+ initPatterns();
+ }
+
+ public List<Pattern> getMulticriteriaPatterns() {
+ return multicriteriaPatterns;
+ }
+
+ public List<Pattern> getBlockPatterns() {
+ return blockPatterns;
+ }
+
+ public List<Pattern> getAllFilePatterns() {
+ return allFilePatterns;
+ }
+
+ public Pattern getExtraPattern(String resource) {
+ return extraPatternByResource.get(resource.substring(resource.lastIndexOf(":") + 1));
+ }
+
+ @VisibleForTesting
+ protected final void initPatterns() {
+ multicriteriaPatterns = Lists.newArrayList();
+ blockPatterns = Lists.newArrayList();
+ allFilePatterns = Lists.newArrayList();
+
+ loadPatternsFromNewProperties();
+ }
+
+ private void loadPatternsFromNewProperties() {
+ // Patterns Multicriteria
+ String patternConf = StringUtils.defaultIfBlank(settings.getString(Constants.PATTERNS_MULTICRITERIA_KEY), "");
+ for (String id : StringUtils.split(patternConf, ',')) {
+ String propPrefix = Constants.PATTERNS_MULTICRITERIA_KEY + "." + id + ".";
+ String resourceKeyPattern = settings.getString(propPrefix + Constants.RESOURCE_KEY);
+ String ruleKeyPattern = settings.getString(propPrefix + Constants.RULE_KEY);
+ Pattern pattern = new Pattern(firstNonNull(resourceKeyPattern, "*"), firstNonNull(ruleKeyPattern, "*"));
+ String lineRange = settings.getString(propPrefix + Constants.LINE_RANGE_KEY);
+ PatternDecoder.decodeRangeOfLines(pattern, firstNonNull(lineRange, "*"));
+ multicriteriaPatterns.add(pattern);
+ }
+
+ // Patterns Block
+ patternConf = StringUtils.defaultIfBlank(settings.getString(Constants.PATTERNS_BLOCK_KEY), "");
+ for (String id : StringUtils.split(patternConf, ',')) {
+ String propPrefix = Constants.PATTERNS_BLOCK_KEY + "." + id + ".";
+ String beginBlockRegexp = settings.getString(propPrefix + Constants.BEGIN_BLOCK_REGEXP);
+ String endBlockRegexp = settings.getString(propPrefix + Constants.END_BLOCK_REGEXP);
+ Pattern pattern = new Pattern().setBeginBlockRegexp(nullToEmpty(beginBlockRegexp)).setEndBlockRegexp(nullToEmpty(endBlockRegexp));
+ blockPatterns.add(pattern);
+ }
+
+ // Patterns All File
+ patternConf = StringUtils.defaultIfBlank(settings.getString(Constants.PATTERNS_ALLFILE_KEY), "");
+ for (String id : StringUtils.split(patternConf, ',')) {
+ String propPrefix = Constants.PATTERNS_ALLFILE_KEY + "." + id + ".";
+ String allFileRegexp = settings.getString(propPrefix + Constants.FILE_REGEXP);
+ Pattern pattern = new Pattern().setAllFileRegexp(nullToEmpty(allFileRegexp));
+ allFilePatterns.add(pattern);
+ }
+ }
+
+ public void addPatternToExcludeResource(String resource) {
+ extraPatternByResource.put(resource, new Pattern(resource, "*").setCheckLines(false));
+ }
+
+ public void addPatternToExcludeLines(String resource, Set<LineRange> lineRanges) {
+ extraPatternByResource.put(resource, new Pattern(resource, "*", lineRanges));
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.scanner;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.plugins.core.issue.ignore.pattern.LineRange;
+import org.sonar.plugins.core.issue.ignore.pattern.Pattern;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Set;
+
+public class RegexpScanner implements BatchExtension {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RegexpScanner.class);
+
+ private PatternsInitializer patternsInitializer;
+ private List<java.util.regex.Pattern> allFilePatterns;
+ private List<DoubleRegexpMatcher> blockMatchers;
+
+ // fields to be reset at every new scan
+ private DoubleRegexpMatcher currentMatcher;
+ private int fileLength;
+ private List<LineExclusion> lineExclusions;
+ private LineExclusion currentLineExclusion;
+
+ public RegexpScanner(PatternsInitializer patternsInitializer) {
+ this.patternsInitializer = patternsInitializer;
+
+ lineExclusions = Lists.newArrayList();
+ allFilePatterns = Lists.newArrayList();
+ blockMatchers = Lists.newArrayList();
+
+ for (Pattern pattern : this.patternsInitializer.getAllFilePatterns()) {
+ allFilePatterns.add(java.util.regex.Pattern.compile(pattern.getAllFileRegexp()));
+ }
+ for (Pattern pattern : this.patternsInitializer.getBlockPatterns()) {
+ blockMatchers.add(new DoubleRegexpMatcher(
+ java.util.regex.Pattern.compile(pattern.getBeginBlockRegexp()),
+ java.util.regex.Pattern.compile(pattern.getEndBlockRegexp())));
+ }
+
+ init();
+ }
+
+ private void init() {
+ currentMatcher = null;
+ fileLength = 0;
+ lineExclusions.clear();
+ currentLineExclusion = null;
+ }
+
+ public void scan(String resource, File file, Charset sourcesEncoding) throws IOException {
+ LOG.debug("Scanning {}", resource);
+ init();
+
+ List<String> lines = FileUtils.readLines(file, sourcesEncoding.name());
+ int lineIndex = 0;
+ for (String line : lines) {
+ lineIndex++;
+ if (line.trim().length() == 0) {
+ continue;
+ }
+
+ // first check the single regexp patterns that can be used to totally exclude a file
+ for (java.util.regex.Pattern pattern : allFilePatterns) {
+ if (pattern.matcher(line).find()) {
+ patternsInitializer.addPatternToExcludeResource(resource);
+ // nothing more to do on this file
+ LOG.debug("- Exclusion pattern '{}': every violation in this file will be ignored.", pattern);
+ return;
+ }
+ }
+
+ // then check the double regexps if we're still here
+ checkDoubleRegexps(line, lineIndex);
+ }
+
+ // now create the new line-based pattern for this file if there are exclusions
+ fileLength = lineIndex;
+ if (!lineExclusions.isEmpty()) {
+ Set<LineRange> lineRanges = convertLineExclusionsToLineRanges();
+ LOG.debug("- Line exclusions found: {}", lineRanges);
+ patternsInitializer.addPatternToExcludeLines(resource, lineRanges);
+ }
+ }
+
+ private Set<LineRange> convertLineExclusionsToLineRanges() {
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ for (LineExclusion lineExclusion : lineExclusions) {
+ lineRanges.add(lineExclusion.toLineRange());
+ }
+ return lineRanges;
+ }
+
+ private void checkDoubleRegexps(String line, int lineIndex) {
+ if (currentMatcher == null) {
+ for (DoubleRegexpMatcher matcher : blockMatchers) {
+ if (matcher.matchesFirstPattern(line)) {
+ startExclusion(lineIndex);
+ currentMatcher = matcher;
+ break;
+ }
+ }
+ } else {
+ if (currentMatcher.matchesSecondPattern(line)) {
+ endExclusion(lineIndex);
+ currentMatcher = null;
+ }
+ }
+ }
+
+ private void startExclusion(int lineIndex) {
+ currentLineExclusion = new LineExclusion(lineIndex);
+ lineExclusions.add(currentLineExclusion);
+ }
+
+ private void endExclusion(int lineIndex) {
+ currentLineExclusion.setEnd(lineIndex);
+ currentLineExclusion = null;
+ }
+
+ private class LineExclusion {
+
+ private int start;
+ private int end;
+
+ LineExclusion(int start) {
+ this.start = start;
+ this.end = -1;
+ }
+
+ void setEnd(int end) {
+ this.end = end;
+ }
+
+ public LineRange toLineRange() {
+ return new LineRange(start, (end == -1 ? fileLength : end));
+ }
+
+ }
+
+ private static class DoubleRegexpMatcher {
+
+ private java.util.regex.Pattern firstPattern;
+ private java.util.regex.Pattern secondPattern;
+
+ DoubleRegexpMatcher(java.util.regex.Pattern firstPattern, java.util.regex.Pattern secondPattern) {
+ this.firstPattern = firstPattern;
+ this.secondPattern = secondPattern;
+ }
+
+ boolean matchesFirstPattern(String line) {
+ return firstPattern.matcher(line).find();
+ }
+
+ boolean matchesSecondPattern(String line) {
+ return secondPattern.matcher(line).find();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.scanner;
+
+import org.sonar.api.batch.Phase;
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.scan.filesystem.FileQuery;
+import org.sonar.api.scan.filesystem.ModuleFileSystem;
+import org.sonar.api.utils.SonarException;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.List;
+
+@Phase(name = Phase.Name.PRE)
+public final class SourceScanner implements Sensor {
+
+ private final RegexpScanner regexpScanner;
+ private final PatternsInitializer patternsInitializer;
+ private final ModuleFileSystem fileSystem;
+
+ public SourceScanner(RegexpScanner regexpScanner, PatternsInitializer patternsInitializer, ModuleFileSystem fileSystem) {
+ this.regexpScanner = regexpScanner;
+ this.patternsInitializer = patternsInitializer;
+ this.fileSystem = fileSystem;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return patternsInitializer.getAllFilePatterns().size() > 0 || patternsInitializer.getBlockPatterns().size() > 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void analyse(Project project, SensorContext context) {
+ parseDirs(project, false);
+ parseDirs(project, true);
+ }
+
+ protected void parseDirs(Project project, boolean isTest) {
+ Charset sourcesEncoding = fileSystem.sourceCharset();
+
+ List<File> files;
+ if (isTest) {
+ files = fileSystem.files(FileQuery.onTest().onLanguage(project.getLanguageKey()));
+ } else {
+ files = fileSystem.files(FileQuery.onSource().onLanguage(project.getLanguageKey()));
+ }
+
+ for (File inputFile : files) {
+ try {
+ String resource = defineResource(inputFile, fileSystem, project, isTest);
+ if (resource != null) {
+ regexpScanner.scan(resource, inputFile, sourcesEncoding);
+ }
+ } catch (Exception e) {
+ throw new SonarException("Unable to read the source file : '" + inputFile.getAbsolutePath() + "' with the charset : '"
+ + sourcesEncoding.name() + "'.", e);
+ }
+ }
+ }
+
+ /*
+ * This method is necessary because Java resources are not treated as every other resource...
+ */
+ private String defineResource(File inputFile, ModuleFileSystem fileSystem, Project project, boolean isTest) {
+ if (Java.KEY.equals(project.getLanguageKey()) && Java.isJavaFile(inputFile)) {
+ List<File> sourceDirs = null;
+ if (isTest) {
+ sourceDirs = fileSystem.testDirs();
+ } else {
+ sourceDirs = fileSystem.sourceDirs();
+ }
+ JavaFile file = JavaFile.fromIOFile(inputFile, sourceDirs, isTest);
+ if (file == null) {
+ return null;
+ } else {
+ return file.getKey();
+ }
+ }
+ return inputFile.getPath();
+ }
+
+ @Override
+ public String toString() {
+ return "Ignore Issues - Source Scanner";
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.plugins.core.issue.ignore.pattern.Pattern;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternDecoder;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class IgnoreIssuesFilterTest {
+
+ public static final Rule CHECKSTYLE_RULE = Rule.create("checkstyle", "MagicNumber", "");
+ public static final String JAVA_FILE = "org.foo.Hello";
+
+ private PatternsInitializer patternsInitializer;
+ private IgnoreIssuesFilter filter;
+
+ @Before
+ public void init() {
+ patternsInitializer = mock(PatternsInitializer.class);
+ when(patternsInitializer.getMulticriteriaPatterns()).thenReturn(Collections.<Pattern> emptyList());
+
+ filter = new IgnoreIssuesFilter(patternsInitializer);
+ }
+
+ @Test
+ public void shouldBeDeactivatedWhenPropertyIsMissing() {
+ assertThat(filter.accept(create(CHECKSTYLE_RULE, JAVA_FILE, null))).isTrue();
+ }
+
+ @Test
+ public void shouldBeIgnoredWithStandardPatterns() throws IOException {
+ when(patternsInitializer.getMulticriteriaPatterns()).thenReturn(createPatterns("org.foo.Bar;*;*\norg.foo.Hello;checkstyle:MagicNumber;[15-200]"));
+
+ assertThat(filter.accept(create(CHECKSTYLE_RULE, JAVA_FILE, 150))).isFalse();
+ }
+
+ @Test
+ public void shouldNotBeIgnoredWithStandardPatterns() throws IOException {
+ when(patternsInitializer.getMulticriteriaPatterns()).thenReturn(createPatterns("org.foo.Bar;*;*\norg.foo.Hello;checkstyle:MagicNumber;[15-200]"));
+
+ assertThat(filter.accept(create(CHECKSTYLE_RULE, JAVA_FILE, 5))).isTrue();
+ }
+
+ @Test
+ public void shouldBeIgnoredWithExtraPattern() throws IOException {
+ when(patternsInitializer.getExtraPattern(JAVA_FILE)).thenReturn(createPatterns("org.foo.Hello;*;[15-200]").get(0));
+
+ assertThat(filter.accept(create(CHECKSTYLE_RULE, JAVA_FILE, 150))).isFalse();
+ }
+
+ @Test
+ public void shouldNotBeIgnoredWithExtraPattern() throws IOException {
+ when(patternsInitializer.getExtraPattern(JAVA_FILE)).thenReturn(createPatterns("org.foo.Hello;*;[15-200]").get(0));
+
+ assertThat(filter.accept(create(CHECKSTYLE_RULE, JAVA_FILE, 5))).isTrue();
+ }
+
+ private Issue create(Rule rule, String component, Integer line) {
+ Issue mockIssue = mock(Issue.class);
+ RuleKey ruleKey = null;
+ if (rule != null) {
+ ruleKey = rule.ruleKey();
+ }
+ when(mockIssue.ruleKey()).thenReturn(ruleKey);
+ when(mockIssue.componentKey()).thenReturn(component);
+ when(mockIssue.line()).thenReturn(line);
+ return mockIssue;
+ }
+
+ private List<Pattern> createPatterns(String line) {
+ return new PatternDecoder().decode(line);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class IgnoreIssuesPluginTest {
+ @Test
+ public void justForCoverage() {
+ assertThat(new IgnoreIssuesPlugin().getExtensions()).hasSize(3 /* properties */ + 4 /* extensions */);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class LineRangeTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void lineRangeShouldBeOrdered() {
+ new LineRange(25, 12);
+ }
+
+ @Test
+ public void shouldConvertLineRangeToLines() {
+ LineRange range = new LineRange(12, 15);
+
+ assertThat(range.toLines()).containsOnly(12, 13, 14, 15);
+ }
+
+ @Test
+ public void shouldTestInclusionInRangeOfLines() {
+ LineRange range = new LineRange(12, 15);
+
+ assertThat(range.in(3)).isFalse();
+ assertThat(range.in(12)).isTrue();
+ assertThat(range.in(13)).isTrue();
+ assertThat(range.in(14)).isTrue();
+ assertThat(range.in(15)).isTrue();
+ assertThat(range.in(16)).isFalse();
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(new LineRange(12, 15).toString()).isEqualTo("[12-15]");
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ LineRange range = new LineRange(12, 15);
+ assertThat(range).isEqualTo(range);
+ assertThat(range).isEqualTo(new LineRange(12, 15));
+ assertThat(range).isNotEqualTo(new LineRange(12, 2000));
+ assertThat(range).isNotEqualTo(new LineRange(1000, 2000));
+ assertThat(range).isNotEqualTo(null);
+ assertThat(range).isNotEqualTo(new StringBuffer());
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(new LineRange(12, 15).hashCode()).isEqualTo(new LineRange(12, 15).hashCode());
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class PatternDecoderTest {
+
+ private PatternDecoder decoder = new PatternDecoder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void shouldReadString() {
+ String patternsList = "# a comment followed by a blank line\n\n" +
+ "# suppress all violations\n" +
+ "*;*;*\n\n" +
+ "# exclude a Java file\n" +
+ "com.foo.Bar;*;*\n\n" +
+ "# exclude a Java package\n" +
+ "com.foo.*;*;*\n\n" +
+ "# exclude a specific rule\n" +
+ "*;checkstyle:IllegalRegexp;*\n\n" +
+ "# exclude a specific rule on a specific file\n" +
+ "com.foo.Bar;checkstyle:IllegalRegexp;*\n";
+ List<Pattern> patterns = decoder.decode(patternsList);
+
+ assertThat(patterns).hasSize(5);
+ }
+
+ @Test
+ public void shouldCheckFormatOfResource() {
+ assertThat(decoder.isResource("")).isFalse();
+ assertThat(decoder.isResource("*")).isTrue();
+ assertThat(decoder.isResource("com.foo.*")).isTrue();
+ }
+
+ @Test
+ public void shouldCheckFormatOfRule() {
+ assertThat(decoder.isRule("")).isFalse();
+ assertThat(decoder.isRule("*")).isTrue();
+ assertThat(decoder.isRule("com.foo.*")).isTrue();
+ }
+
+ @Test
+ public void shouldCheckFormatOfLinesRange() {
+ assertThat(decoder.isLinesRange("")).isFalse();
+ assertThat(decoder.isLinesRange(" ")).isFalse();
+ assertThat(decoder.isLinesRange("12")).isFalse();
+ assertThat(decoder.isLinesRange("12,212")).isFalse();
+
+ assertThat(decoder.isLinesRange("*")).isTrue();
+ assertThat(decoder.isLinesRange("[]")).isTrue();
+ assertThat(decoder.isLinesRange("[13]")).isTrue();
+ assertThat(decoder.isLinesRange("[13,24]")).isTrue();
+ assertThat(decoder.isLinesRange("[13,24,25-500]")).isTrue();
+ assertThat(decoder.isLinesRange("[24-65]")).isTrue();
+ assertThat(decoder.isLinesRange("[13,24-65,84-89,122]")).isTrue();
+ }
+
+ @Test
+ public void shouldReadStarPatterns() {
+ Pattern pattern = decoder.decodeLine("*;*;*");
+
+ assertThat(pattern.getResourcePattern().toString()).isEqualTo("*");
+ assertThat(pattern.getRulePattern().toString()).isEqualTo("*");
+ assertThat(pattern.isCheckLines()).isFalse();
+ }
+
+ @Test
+ public void shouldReadLineIds() {
+ Pattern pattern = decoder.decodeLine("*;*;[10,25,98]");
+
+ assertThat(pattern.isCheckLines()).isTrue();
+ assertThat(pattern.getAllLines()).containsOnly(10, 25, 98);
+ }
+
+ @Test
+ public void shouldReadRangeOfLineIds() {
+ Pattern pattern = decoder.decodeLine("*;*;[10-12,25,97-100]");
+
+ assertThat(pattern.isCheckLines()).isTrue();
+ assertThat(pattern.getAllLines()).containsOnly(10, 11, 12, 25, 97, 98, 99, 100);
+ }
+
+ @Test
+ public void shouldNotExcludeLines() {
+ // [] is different than *
+ // - all violations are excluded on *
+ // * no violations are excluded on []
+ Pattern pattern = decoder.decodeLine("*;*;[]");
+
+ assertThat(pattern.isCheckLines()).isTrue();
+ assertThat(pattern.getAllLines()).isEmpty();
+ }
+
+ @Test
+ public void shouldReadBlockPattern() {
+ Pattern pattern = decoder.decodeLine("SONAR-OFF;SONAR-ON");
+
+ assertThat(pattern.getResourcePattern()).isNull();
+ assertThat(pattern.getBeginBlockRegexp()).isEqualTo("SONAR-OFF");
+ assertThat(pattern.getEndBlockRegexp()).isEqualTo("SONAR-ON");
+ }
+
+ @Test
+ public void shouldReadAllFilePattern() {
+ Pattern pattern = decoder.decodeLine("SONAR-ALL-OFF");
+
+ assertThat(pattern.getResourcePattern()).isNull();
+ assertThat(pattern.getAllFileRegexp()).isEqualTo("SONAR-ALL-OFF");
+ }
+
+ @Test
+ public void shouldFailIfUnexistingFile() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Fail to load the file");
+
+ decoder.decode(new File("foo"));
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine1() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The following line has more than 3 fields separated by comma");
+
+ decoder.decode(";;;;");
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine3() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The first field does not define a resource pattern");
+
+ decoder.decode(";*;*");
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine4() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The second field does not define a rule pattern");
+
+ decoder.decode("*;;*");
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine5() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The third field does not define a range of lines");
+
+ decoder.decode("*;*;blabla");
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine6() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The first field does not define a regular expression");
+
+ decoder.decode(";ON");
+ }
+
+ @Test
+ public void shouldFailToReadUncorrectLine7() {
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Invalid format. The second field does not define a regular expression");
+
+ decoder.decode("OFF;");
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PatternTest {
+
+ @Test
+ public void shouldMatchLines() {
+ Pattern pattern = new Pattern("*", "*");
+ pattern.addLine(12).addLine(15).addLineRange(20, 25);
+
+ assertThat(pattern.matchLine(3)).isFalse();
+ assertThat(pattern.matchLine(12)).isTrue();
+ assertThat(pattern.matchLine(14)).isFalse();
+ assertThat(pattern.matchLine(21)).isTrue();
+ assertThat(pattern.matchLine(6599)).isFalse();
+ }
+
+ @Test
+ public void shouldMatchJavaFile() {
+ String javaFile = "org.foo.Bar";
+ assertThat(new Pattern("org.foo.Bar", "*").matchResource(javaFile)).isTrue();
+ assertThat(new Pattern("org.foo.*", "*").matchResource(javaFile)).isTrue();
+ assertThat(new Pattern("*Bar", "*").matchResource(javaFile)).isTrue();
+ assertThat(new Pattern("*", "*").matchResource(javaFile)).isTrue();
+ assertThat(new Pattern("org.*.?ar", "*").matchResource(javaFile)).isTrue();
+
+ assertThat(new Pattern("org.other.Hello", "*").matchResource(javaFile)).isFalse();
+ assertThat(new Pattern("org.foo.Hello", "*").matchResource(javaFile)).isFalse();
+ assertThat(new Pattern("org.*.??ar", "*").matchResource(javaFile)).isFalse();
+ assertThat(new Pattern("org.*.??ar", "*").matchResource(null)).isFalse();
+ assertThat(new Pattern("org.*.??ar", "*").matchResource("plop")).isFalse();
+ }
+
+ @Test
+ public void shouldMatchRule() {
+ RuleKey rule = Rule.create("checkstyle", "IllegalRegexp", "").ruleKey();
+ assertThat(new Pattern("*", "*").matchRule(rule)).isTrue();
+ assertThat(new Pattern("*", "checkstyle:*").matchRule(rule)).isTrue();
+ assertThat(new Pattern("*", "checkstyle:IllegalRegexp").matchRule(rule)).isTrue();
+ assertThat(new Pattern("*", "checkstyle:Illegal*").matchRule(rule)).isTrue();
+ assertThat(new Pattern("*", "*:*Illegal*").matchRule(rule)).isTrue();
+
+ assertThat(new Pattern("*", "pmd:IllegalRegexp").matchRule(rule)).isFalse();
+ assertThat(new Pattern("*", "pmd:*").matchRule(rule)).isFalse();
+ assertThat(new Pattern("*", "*:Foo*IllegalRegexp").matchRule(rule)).isFalse();
+ }
+
+ @Test
+ public void shouldMatchViolation() {
+ Rule rule = Rule.create("checkstyle", "IllegalRegexp", "");
+ String javaFile = "org.foo.Bar";
+
+ Pattern pattern = new Pattern("*", "*");
+ pattern.addLine(12);
+
+ assertThat(pattern.match(create(rule, javaFile, null))).isTrue();
+ assertThat(pattern.match(create(rule, javaFile, 12))).isTrue();
+ assertThat(pattern.match(create((Rule) null, javaFile, 5))).isFalse();
+ assertThat(pattern.match(create(rule, null, null))).isFalse();
+ assertThat(pattern.match(create((Rule) null, null, null))).isFalse();
+ }
+
+ private Issue create(Rule rule, String component, Integer line) {
+ Issue mockIssue = mock(Issue.class);
+ RuleKey ruleKey = null;
+ if (rule != null) {
+ ruleKey = rule.ruleKey();
+ }
+ when(mockIssue.ruleKey()).thenReturn(ruleKey);
+ when(mockIssue.componentKey()).thenReturn(component);
+ when(mockIssue.line()).thenReturn(line);
+ return mockIssue;
+ }
+
+ @Test
+ public void shouldNotMatchNullRule() {
+ assertThat(new Pattern("*", "*").matchRule(null)).isFalse();
+ }
+
+ @Test
+ public void shouldPrintPatternToString() {
+ Pattern pattern = new Pattern("*", "checkstyle:*");
+
+ assertThat(pattern.toString()).isEqualTo("Pattern[resourcePattern=*,rulePattern=checkstyle:*,lines=[],lineRanges=[],beginBlockRegexp=<null>,endBlockRegexp=<null>,allFileRegexp=<null>,checkLines=true]");
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.pattern;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.Settings;
+import org.sonar.plugins.core.issue.ignore.Constants;
+import org.sonar.plugins.core.issue.ignore.IgnoreIssuesPlugin;
+
+import java.util.Set;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class PatternsInitializerTest {
+
+ private PatternsInitializer patternsInitializer;
+
+ private Settings settings;
+
+ @Before
+ public void init() {
+ settings = new Settings(new PropertyDefinitions(new IgnoreIssuesPlugin()));
+ patternsInitializer = new PatternsInitializer(settings);
+ }
+
+ @Test
+ public void testNoConfiguration() {
+ patternsInitializer.initPatterns();
+ assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnExtraPatternForResource() {
+ String file = "foo";
+ patternsInitializer.addPatternToExcludeResource(file);
+
+ Pattern extraPattern = patternsInitializer.getExtraPattern(file);
+ assertThat(extraPattern.matchResource(file)).isTrue();
+ assertThat(extraPattern.isCheckLines()).isFalse();
+ }
+
+ @Test
+ public void shouldReturnExtraPatternForLinesOfResource() {
+ String file = "foo";
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(25, 28));
+ patternsInitializer.addPatternToExcludeLines(file, lineRanges);
+
+ Pattern extraPattern = patternsInitializer.getExtraPattern(file);
+ assertThat(extraPattern.matchResource(file)).isTrue();
+ assertThat(extraPattern.getAllLines()).isEqualTo(Sets.newHashSet(25, 26, 27, 28));
+ }
+
+ @Test
+ public void shouldReturnMulticriteriaPattern() {
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY, "1,2");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".1." + Constants.RESOURCE_KEY, "org.foo.Bar");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".1." + Constants.RULE_KEY, "*");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".1." + Constants.RESOURCE_KEY, "*");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".2." + Constants.LINE_RANGE_KEY, "org.foo.Hello");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".2." + Constants.RULE_KEY, "checkstyle:MagicNumber");
+ settings.setProperty(Constants.PATTERNS_MULTICRITERIA_KEY + ".2." + Constants.LINE_RANGE_KEY, "[15-200]");
+ patternsInitializer.initPatterns();
+
+ assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(2);
+ assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(0);
+ assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnBlockPattern() {
+ settings.setProperty(Constants.PATTERNS_BLOCK_KEY, "1,2");
+ settings.setProperty(Constants.PATTERNS_BLOCK_KEY + ".1." + Constants.BEGIN_BLOCK_REGEXP, "// SONAR-OFF");
+ settings.setProperty(Constants.PATTERNS_BLOCK_KEY + ".1." + Constants.END_BLOCK_REGEXP, "// SONAR-ON");
+ settings.setProperty(Constants.PATTERNS_BLOCK_KEY + ".2." + Constants.BEGIN_BLOCK_REGEXP, "// FOO-OFF");
+ settings.setProperty(Constants.PATTERNS_BLOCK_KEY + ".2." + Constants.END_BLOCK_REGEXP, "// FOO-ON");
+ patternsInitializer.initPatterns();
+
+ assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0);
+ assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(2);
+ assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void shouldReturnAllFilePattern() {
+ settings.setProperty(Constants.PATTERNS_ALLFILE_KEY, "1,2");
+ settings.setProperty(Constants.PATTERNS_ALLFILE_KEY + ".1." + Constants.FILE_REGEXP, "@SONAR-IGNORE-ALL");
+ settings.setProperty(Constants.PATTERNS_ALLFILE_KEY + ".2." + Constants.FILE_REGEXP, "//FOO-IGNORE-ALL");
+ patternsInitializer.initPatterns();
+
+ assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0);
+ assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(0);
+ assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(2);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.scanner;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.plugins.core.issue.ignore.pattern.LineRange;
+import org.sonar.plugins.core.issue.ignore.pattern.Pattern;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+import org.sonar.test.TestUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class RegexpScannerTest {
+
+ private RegexpScanner regexpScanner;
+
+ private String javaFile;
+ @Mock
+ private PatternsInitializer patternsInitializer;
+ @Mock
+ private Pattern allFilePattern;
+ @Mock
+ private Pattern blockPattern1;
+ @Mock
+ private Pattern blockPattern2;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+
+ when(allFilePattern.getAllFileRegexp()).thenReturn("@SONAR-IGNORE-ALL");
+ when(blockPattern1.getBeginBlockRegexp()).thenReturn("// SONAR-OFF");
+ when(blockPattern1.getEndBlockRegexp()).thenReturn("// SONAR-ON");
+ when(blockPattern2.getBeginBlockRegexp()).thenReturn("// FOO-OFF");
+ when(blockPattern2.getEndBlockRegexp()).thenReturn("// FOO-ON");
+ when(patternsInitializer.getAllFilePatterns()).thenReturn(Arrays.asList(allFilePattern));
+ when(patternsInitializer.getBlockPatterns()).thenReturn(Arrays.asList(blockPattern1, blockPattern2));
+
+ regexpScanner = new RegexpScanner(patternsInitializer);
+ verify(patternsInitializer, times(1)).getAllFilePatterns();
+ verify(patternsInitializer, times(1)).getBlockPatterns();
+
+ javaFile = "org.sonar.test.MyFile";
+ }
+
+ @Test
+ public void shouldDoNothing() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-no-regexp.txt"), UTF_8);
+
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeFile() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-single-regexp.txt"), UTF_8);
+
+ verify(patternsInitializer, times(1)).addPatternToExcludeResource(javaFile);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeFileEvenIfAlsoDoubleRegexps() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-single-regexp-and-double-regexp.txt"), UTF_8);
+
+ verify(patternsInitializer, times(1)).addPatternToExcludeResource(javaFile);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeLines() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-double-regexp.txt"), UTF_8);
+
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(21, 25));
+ verify(patternsInitializer, times(1)).addPatternToExcludeLines(javaFile, lineRanges);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeLinesTillTheEnd() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-double-regexp-unfinished.txt"), UTF_8);
+
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(21, 34));
+ verify(patternsInitializer, times(1)).addPatternToExcludeLines(javaFile, lineRanges);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeSeveralLineRanges() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-double-regexp-twice.txt"), UTF_8);
+
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(21, 25));
+ lineRanges.add(new LineRange(29, 33));
+ verify(patternsInitializer, times(1)).addPatternToExcludeLines(javaFile, lineRanges);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeLinesWithWrongOrder() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-double-regexp-wrong-order.txt"), UTF_8);
+
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(25, 35));
+ verify(patternsInitializer, times(1)).addPatternToExcludeLines(javaFile, lineRanges);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+ @Test
+ public void shouldAddPatternToExcludeLinesWithMess() throws IOException {
+ regexpScanner.scan(javaFile, TestUtils.getResource(getClass(), "file-with-double-regexp-mess.txt"), UTF_8);
+
+ Set<LineRange> lineRanges = Sets.newHashSet();
+ lineRanges.add(new LineRange(21, 29));
+ verify(patternsInitializer, times(1)).addPatternToExcludeLines(javaFile, lineRanges);
+ verifyNoMoreInteractions(patternsInitializer);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.issue.ignore.scanner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.resources.Project;
+import org.sonar.api.scan.filesystem.FileQuery;
+import org.sonar.api.scan.filesystem.ModuleFileSystem;
+import org.sonar.api.utils.SonarException;
+import org.sonar.plugins.core.issue.ignore.pattern.Pattern;
+import org.sonar.plugins.core.issue.ignore.pattern.PatternsInitializer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SourceScannerTest {
+
+ private SourceScanner scanner;
+
+ @Mock
+ private RegexpScanner regexpScanner;
+ @Mock
+ private PatternsInitializer patternsInitializer;
+ @Mock
+ private Project project;
+ @Mock
+ private ModuleFileSystem fileSystem;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+
+ when(fileSystem.sourceCharset()).thenReturn(UTF_8);
+
+ scanner = new SourceScanner(regexpScanner, patternsInitializer, fileSystem);
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(scanner.toString()).isEqualTo("Ignore Issues - Source Scanner");
+ }
+
+ @Test
+ public void shouldExecute() throws IOException {
+ when(patternsInitializer.getAllFilePatterns()).thenReturn(Arrays.asList(new Pattern(), new Pattern()));
+ assertThat(scanner.shouldExecuteOnProject(null)).isTrue();
+
+ when(patternsInitializer.getAllFilePatterns()).thenReturn(Collections.<Pattern>emptyList());
+ when(patternsInitializer.getBlockPatterns()).thenReturn(Arrays.asList(new Pattern(), new Pattern()));
+ assertThat(scanner.shouldExecuteOnProject(null)).isTrue();
+
+ when(patternsInitializer.getAllFilePatterns()).thenReturn(Collections.<Pattern>emptyList());
+ when(patternsInitializer.getBlockPatterns()).thenReturn(Collections.<Pattern>emptyList());
+ assertThat(scanner.shouldExecuteOnProject(null)).isFalse();
+ }
+
+ @Test
+ public void shouldAnalyseJavaProject() throws IOException {
+ File sourceFile = new File("src/main/java/Foo.java");
+ File testFile = new File("src/test/java/FooTest.java");
+
+ when(project.getLanguageKey()).thenReturn("java");
+ when(fileSystem.files(Mockito.isA(FileQuery.class)))
+ .thenReturn(Arrays.asList(sourceFile))
+ .thenReturn(Arrays.asList(testFile));
+ when(fileSystem.sourceDirs()).thenReturn(Arrays.asList(new File("src/main/java")));
+ when(fileSystem.testDirs()).thenReturn(Arrays.asList(new File("src/test/java")));
+
+ scanner.analyse(project, null);
+
+ verify(regexpScanner).scan("[default].Foo", sourceFile, UTF_8);
+ verify(regexpScanner).scan("[default].FooTest", testFile, UTF_8);
+ }
+
+ @Test
+ public void shouldAnalyseOtherProject() throws IOException {
+ File sourceFile = new File("Foo.php");
+ File testFile = new File("FooTest.php");
+
+ when(project.getLanguageKey()).thenReturn("php");
+ when(fileSystem.files(Mockito.isA(FileQuery.class)))
+ .thenReturn(Arrays.asList(sourceFile))
+ .thenReturn(Arrays.asList(testFile));
+
+ scanner.analyse(project, null);
+
+ verify(regexpScanner).scan("Foo.php", sourceFile, UTF_8);
+ verify(regexpScanner).scan("FooTest.php", testFile, UTF_8);
+ }
+
+ @Test
+ public void shouldAnalyseJavaProjectWithNonJavaFile() throws IOException {
+ File sourceFile = new File("src/main/java/Foo.java");
+ File otherFile = new File("other.js");
+
+ when(project.getLanguageKey()).thenReturn("java");
+ List<File> empty = Collections.emptyList();
+ when(fileSystem.files(Mockito.isA(FileQuery.class)))
+ .thenReturn(Arrays.asList(sourceFile, otherFile))
+ .thenReturn(empty);
+ when(fileSystem.sourceDirs()).thenReturn(Arrays.asList(new File("src/main/java")));
+ when(fileSystem.testDirs()).thenReturn(Arrays.asList(new File("src/test/java")));
+
+ scanner.analyse(project, null);
+
+ verify(regexpScanner, never()).scan("other.js", sourceFile, UTF_8);
+ }
+
+ @Test
+ public void shouldIgnoreInvalidFile() throws IOException {
+ File sourceFile = new File("invalid.java");
+
+ when(project.getLanguageKey()).thenReturn("java");
+ List<File> empty = Collections.emptyList();
+ when(fileSystem.files(Mockito.isA(FileQuery.class)))
+ .thenReturn(Arrays.asList(sourceFile))
+ .thenReturn(empty);
+ when(fileSystem.sourceDirs()).thenReturn(Arrays.asList(new File("src/main/java")));
+ when(fileSystem.testDirs()).thenReturn(Arrays.asList(new File("src/test/java")));
+
+ scanner.analyse(project, null);
+
+ verifyNoMoreInteractions(regexpScanner);
+ }
+
+ @Test
+ public void shouldReportFailure() throws IOException {
+ File sourceFile = new File("Foo.php");
+
+ when(project.getLanguageKey()).thenReturn("php");
+ when(fileSystem.files(Mockito.isA(FileQuery.class))).thenReturn(Arrays.asList(sourceFile));
+ doThrow(new IOException("BUG")).when(regexpScanner).scan("Foo.php", sourceFile, UTF_8);
+
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Unable to read the source file");
+
+ scanner.analyse(project, null);
+ }
+}
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ // SONAR-OFF
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+ // FOO-OFF
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ // SONAR-ON
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ // FOO-ON
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ // SONAR-OFF
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+ // SONAR-ON
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ // FOO-OFF
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ // FOO-ON
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ // SONAR-OFF
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ // SONAR-ON
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+ // SONAR-OFF
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ // SONAR-OFF
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+ // SONAR-ON
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ *
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+ // SONAR-OFF
+
+import java.util.Set;
+
+/**
+ * @SONAR-IGNORE-ALL
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+ // SONAR-ON
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.sonar.plugins.switchoffviolations.pattern;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * @SONAR-IGNORE-ALL
+ */
+public class LineRange {
+ int from, to;
+
+ public LineRange(int from, int to) {
+ if (to < from) {
+ throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to);
+ }
+ this.from = from;
+ this.to = to;
+ }
+
+ public boolean in(int lineId) {
+ return from <= lineId && lineId <= to;
+ }
+
+ public Set<Integer> toLines() {
+ Set<Integer> lines = Sets.newLinkedHashSet();
+ for (int index = from; index <= to; index++) {
+ lines.add(index);
+ }
+ return lines;
+ }
+
+}
\ No newline at end of file