diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8bb41ed --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +sonar-puppet-plugin/build/libs/*-javadoc.jar +sonar-puppet-plugin/build/libs/*-sources.jar diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..840f81d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM sonarqube:5.6.6-alpine + +COPY sonar-puppet-plugin/build/libs/* /opt/sonarqube/extensions/plugins/ diff --git a/build.gradle b/build.gradle index f286f0d..edc7781 100644 --- a/build.gradle +++ b/build.gradle @@ -37,8 +37,8 @@ subprojects { } ext { - sonarVersion = '4.5.2' - sslrVersion = '1.20' + sonarVersion = '5.6.6' + sslrVersion = '1.21' } dependencies { diff --git a/gradle/sonar.gradle b/gradle/sonar.gradle index e2e61ae..7bc0ccf 100644 --- a/gradle/sonar.gradle +++ b/gradle/sonar.gradle @@ -5,7 +5,7 @@ buildscript { } } dependencies { - classpath "org.sonarqube.gradle:gradle-sonarqube-plugin:1.1" + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.4" } } @@ -14,7 +14,7 @@ apply plugin: org.sonarqube.gradle.SonarQubePlugin sonarqube { properties { - property "sonar.host.url", "http://nemo.sonarqube.org/" + property "sonar.host.url", "https://sonarqube.com/" property "sonar.analysis.mode", "issues" property "sonar.issuesReport.console.enable", "true" property "sonar.issuesReport.html.enable", "true" diff --git a/puppet-checks/build.gradle b/puppet-checks/build.gradle index e9d5f9f..c6b7177 100644 --- a/puppet-checks/build.gradle +++ b/puppet-checks/build.gradle @@ -1,8 +1,6 @@ dependencies { compile project(':puppet-squid') - testCompile "org.codehaus.sonar:sonar-deprecated:$sonarVersion" - testRuntime 'org.slf4j:slf4j-simple:1.7.12' } diff --git a/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/ComplexExpressionCheck.java b/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/ComplexExpressionCheck.java index 7b22170..0f841e0 100644 --- a/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/ComplexExpressionCheck.java +++ b/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/ComplexExpressionCheck.java @@ -73,6 +73,7 @@ public void visitNode(AstNode node) { node.getDescendants(PuppetGrammar.BOOL_OPERATOR).size() - max, max), (double) node.getDescendants(PuppetGrammar.BOOL_OPERATOR).size() - max); + } } diff --git a/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/TestsDirectoryPresentCheck.java b/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/TestsDirectoryPresentCheck.java index 907c8b2..344a536 100644 --- a/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/TestsDirectoryPresentCheck.java +++ b/puppet-checks/src/main/java/com/iadams/sonarqube/puppet/checks/TestsDirectoryPresentCheck.java @@ -25,6 +25,7 @@ package com.iadams.sonarqube.puppet.checks; import com.iadams.sonarqube.puppet.PuppetCheckVisitor; +import com.sonar.sslr.api.AstNode; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.check.Priority; import org.sonar.check.Rule; @@ -43,5 +44,4 @@ public class TestsDirectoryPresentCheck extends PuppetCheckVisitor { public static final String RULE_KEY = "TestsDirectoryPresent"; - } diff --git a/puppet-squid/build.gradle b/puppet-squid/build.gradle index 0f79f3e..4ce1446 100644 --- a/puppet-squid/build.gradle +++ b/puppet-squid/build.gradle @@ -1,9 +1,9 @@ dependencies { - compile "org.codehaus.sonar:sonar-plugin-api:$sonarVersion" - compile "org.codehaus.sonar.sslr:sslr-core:$sslrVersion" - compile 'org.codehaus.sonar.sslr-squid-bridge:sslr-squid-bridge:2.6' + compile "org.sonarsource.sonarqube:sonar-plugin-api:$sonarVersion" + compile "org.sonarsource.sslr:sslr-core:$sslrVersion" + compile 'org.sonarsource.sslr-squid-bridge:sslr-squid-bridge:2.6.1' - testCompile "org.codehaus.sonar.sslr:sslr-testing-harness:$sslrVersion" + testCompile "org.sonarsource.sslr:sslr-testing-harness:$sslrVersion" testCompile 'org.apache.maven:maven-project:2.2.1' testRuntime 'org.slf4j:slf4j-simple:1.7.12' diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocTokenizer.java b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/TokenLocation.java similarity index 55% rename from sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocTokenizer.java rename to puppet-squid/src/main/java/com/iadams/sonarqube/puppet/TokenLocation.java index b1fbace..46fa99d 100644 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocTokenizer.java +++ b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/TokenLocation.java @@ -1,4 +1,4 @@ -/* +/** * SonarQube Puppet Plugin * The MIT License (MIT) * @@ -22,13 +22,47 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.iadams.sonarqube.puppet.colorizer; +package com.iadams.sonarqube.puppet; -import org.sonar.colorizer.InlineDocTokenizer; +import com.sonar.sslr.api.Token; -public class PuppetDocTokenizer extends InlineDocTokenizer { +public class TokenLocation { - public PuppetDocTokenizer(String tagBefore, String tagAfter) { - super("#", tagBefore, tagAfter); + private final int startLine; + private final int startLineOffset; + private final int endLine; + private final int endLineOffset; + + public TokenLocation(Token token) { + this.startLine = token.getLine(); + this.startLineOffset = token.getColumn(); + + String value = token.getValue(); + String[] lines = value.split("\r\n|\n|\r", -1); + + if (lines.length > 1) { + endLine = token.getLine() + lines.length - 1; + endLineOffset = lines[lines.length - 1].length(); + + } else { + this.endLine = this.startLine; + this.endLineOffset = this.startLineOffset + token.getValue().length(); + } + } + + public int startLine() { + return startLine; + } + + public int startLineOffset() { + return startLineOffset; + } + + public int endLine() { + return endLine; + } + + public int endLineOffset() { + return endLineOffset; } } diff --git a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighter.java b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighter.java deleted file mode 100644 index 51bc6a4..0000000 --- a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.highlighter; - -import com.google.common.annotations.VisibleForTesting; -import com.iadams.sonarqube.puppet.PuppetConfiguration; -import com.iadams.sonarqube.puppet.api.PuppetKeyword; -import com.iadams.sonarqube.puppet.api.PuppetTokenType; -import com.iadams.sonarqube.puppet.lexer.PuppetLexer; -import com.sonar.sslr.api.Token; -import com.sonar.sslr.api.TokenType; -import com.sonar.sslr.api.Trivia; -import com.sonar.sslr.impl.Lexer; - -import java.io.File; -import java.nio.charset.Charset; -import java.util.List; - -import org.sonar.api.source.Highlightable; - -public class PuppetHighlighter { - - private Lexer lexer; - private Charset charset; - - public PuppetHighlighter(PuppetConfiguration conf) { - this.lexer = PuppetLexer.create(conf); - this.charset = conf.getCharset(); - } - - public void highlight(Highlightable highlightable, File file) { - SourceFileOffsets offsets = new SourceFileOffsets(file, charset); - List tokens = lexer.lex(file); - doHighlight(highlightable, tokens, offsets); - } - - @VisibleForTesting - public void highlight(Highlightable highlightable, String string) { - SourceFileOffsets offsets = new SourceFileOffsets(string); - List tokens = lexer.lex(string); - doHighlight(highlightable, tokens, offsets); - } - - private void doHighlight(Highlightable highlightable, List tokens, SourceFileOffsets offsets) { - Highlightable.HighlightingBuilder highlighting = highlightable.newHighlighting(); - highlightStringsAndVariablesAndKeywords(highlighting, tokens, offsets); - highlightComments(highlighting, tokens, offsets); - highlighting.done(); - } - - private static void highlightComments(Highlightable.HighlightingBuilder highlighting, List tokens, SourceFileOffsets offsets) { - for (Token token : tokens) { - if (!token.getTrivia().isEmpty()) { - for (Trivia trivia : token.getTrivia()) { - highlight(highlighting, offsets.startOffset(trivia.getToken()), offsets.endOffset(trivia.getToken()), "cd"); - } - } - } - } - - private void highlightStringsAndVariablesAndKeywords(Highlightable.HighlightingBuilder highlighting, List tokens, SourceFileOffsets offsets) { - for (Token token : tokens) { - if (PuppetTokenType.SINGLE_QUOTED_STRING_LITERAL.equals(token.getType()) - || PuppetTokenType.DOUBLE_QUOTED_STRING_LITERAL.equals(token.getType())) { - highlight(highlighting, offsets.startOffset(token), offsets.endOffset(token), "p"); - } else if (isKeyword(token.getType())) { - highlight(highlighting, offsets.startOffset(token), offsets.endOffset(token), "k"); - } else if (PuppetTokenType.VARIABLE.equals(token.getType())) { - highlight(highlighting, offsets.startOffset(token), offsets.endOffset(token), "a"); - } - } - } - - private static void highlight(Highlightable.HighlightingBuilder highlighting, int startOffset, int endOffset, String code) { - if (endOffset > startOffset) { - highlighting.highlight(startOffset, endOffset, code); - } - } - - public boolean isKeyword(TokenType type) { - for (TokenType keywordType : PuppetKeyword.values()) { - if (keywordType.equals(type)) { - return true; - } - } - return false; - } -} diff --git a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsets.java b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsets.java deleted file mode 100644 index 3abf4c5..0000000 --- a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsets.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.highlighter; - -import com.google.common.collect.Lists; -import com.google.common.io.Files; -import com.sonar.sslr.api.Token; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; - -public class SourceFileOffsets { - private final int length; - private final List lineStartOffsets = Lists.newArrayList(); - - public SourceFileOffsets(String content) { - this.length = content.length(); - initOffsets(content); - } - - public SourceFileOffsets(File file, Charset charset) { - this(fileContent(file, charset)); - } - - private static String fileContent(File file, Charset charset) { - String fileContent; - try { - fileContent = Files.toString(file, charset); - } catch (IOException e) { - throw new IllegalStateException("Could not read " + file, e); - } - return fileContent; - } - - private void initOffsets(String toParse) { - lineStartOffsets.add(0); - int i = 0; - while (i < length) { - if (toParse.charAt(i) == '\n' || toParse.charAt(i) == '\r') { - int nextLineStartOffset = i + 1; - if (i < (length - 1) && toParse.charAt(i) == '\r' && toParse.charAt(i + 1) == '\n') { - nextLineStartOffset = i + 2; - i++; - } - lineStartOffsets.add(nextLineStartOffset); - } - i++; - } - } - - public int startOffset(Token token) { - int lineStartOffset = lineStartOffsets.get(token.getLine() - 1); - int column = token.getColumn(); - return lineStartOffset + column; - } - - public int endOffset(Token token) { - return startOffset(token) + token.getValue().length(); - } -} diff --git a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/package-info.java b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/package-info.java deleted file mode 100644 index 71547ca..0000000 --- a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/highlighter/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -@ParametersAreNonnullByDefault -package com.iadams.sonarqube.puppet.highlighter; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/metrics/FileLinesVisitor.java b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/metrics/FileLinesVisitor.java index 16a6e86..8fb6558 100644 --- a/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/metrics/FileLinesVisitor.java +++ b/puppet-squid/src/main/java/com/iadams/sonarqube/puppet/metrics/FileLinesVisitor.java @@ -28,6 +28,7 @@ import com.iadams.sonarqube.puppet.api.PuppetMetric; import com.iadams.sonarqube.puppet.api.PuppetTokenType; import com.sonar.sslr.api.*; +import java.util.Map; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.measures.CoreMetrics; @@ -41,14 +42,27 @@ public class FileLinesVisitor extends SquidAstVisitor implements AstAndTokenVisitor { private final FileLinesContextFactory fileLinesContextFactory; - private final FileSystem fileSystem; - private final Set linesOfCode = Sets.newHashSet(); - private final Set linesOfComments = Sets.newHashSet(); + private boolean seenFirstToken; + + private final boolean ignoreHeaderComments; + + private Set noSonar = Sets.newHashSet(); + private Set linesOfCode = Sets.newHashSet(); + private Set linesOfComments = Sets.newHashSet(); + private Set linesOfDocstring = Sets.newHashSet(); + private final FileSystem fileSystem; + private final Map> allLinesOfCode; - public FileLinesVisitor(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem) { + public FileLinesVisitor( + FileLinesContextFactory fileLinesContextFactory, + FileSystem fileSystem, + Map> linesOfCode, + boolean ignoreHeaderComments) { this.fileLinesContextFactory = fileLinesContextFactory; this.fileSystem = fileSystem; + this.allLinesOfCode = linesOfCode; + this.ignoreHeaderComments = ignoreHeaderComments; } @Override diff --git a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/FileLinesVisitorSpec.groovy b/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/FileLinesVisitorSpec.groovy index 4cc11f5..038b2a3 100644 --- a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/FileLinesVisitorSpec.groovy +++ b/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/FileLinesVisitorSpec.groovy @@ -35,32 +35,35 @@ import org.sonar.api.measures.FileLinesContextFactory import org.sonar.squidbridge.SquidAstVisitor import spock.lang.Specification +import java.nio.file.Paths + class FileLinesVisitorSpec extends Specification { static final File BASE_DIR = new File("src/test/resources/metrics") FileLinesContextFactory fileLinesContextFactory - DefaultFileSystem fileSystem + DefaultFileSystem fileSystem = new DefaultFileSystem(Paths.get("")) FileLinesContext fileLinesContext def setup() { - fileLinesContextFactory = Mock() - fileSystem = new DefaultFileSystem() - fileLinesContext = Mock() + fileLinesContextFactory = Mock(FileLinesContextFactory) + fileSystem = new DefaultFileSystem(BASE_DIR) + fileLinesContext = Mock(FileLinesContext) } def "check metrics calculate correctly"() { when: File file = new File(BASE_DIR, "lines.pp") - InputFile inputFile = new DefaultInputFile(file.getPath()) + InputFile inputFile = new DefaultInputFile("moduleKey",file.getPath()) fileSystem.add(inputFile) fileLinesContextFactory.createFor(inputFile) >> fileLinesContext - SquidAstVisitor visitor = new FileLinesVisitor(fileLinesContextFactory, fileSystem); + HashMap> linesOfCode = new HashMap<>() + SquidAstVisitor visitor = new FileLinesVisitor(fileLinesContextFactory, fileSystem, linesOfCode, false) - PuppetAstScanner.scanSingleFile(file, visitor); + PuppetAstScanner.scanSingleFile(file, visitor) then: 1 * fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, 1, 0) diff --git a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighterSpec.groovy b/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighterSpec.groovy deleted file mode 100644 index 11b5db4..0000000 --- a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/PuppetHighlighterSpec.groovy +++ /dev/null @@ -1,138 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.highlighter - -import com.google.common.base.Charsets -import com.iadams.sonarqube.puppet.PuppetConfiguration -import org.sonar.api.source.Highlightable -import spock.lang.Specification - -class PuppetHighlighterSpec extends Specification { - - private final PuppetHighlighter highlighter = new PuppetHighlighter(new PuppetConfiguration(Charsets.UTF_8)) - private final Highlightable highlightable = Mock() - private final Highlightable.HighlightingBuilder builder = Mock() - - def "no highlighting"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, '') - - then: - 1 * builder.done() - 0 * builder.highlight(_, _, _) - } - - def "highlighting comments"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, '# my comments') - - then: - 1 * builder.done() - 1 * builder.highlight(0, 13, 'cd') - } - - def "highlighting multiline comments"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, '# my comments\n#my comments 2') - - then: - 1 * builder.done() - 1 * builder.highlight(0, 13, 'cd') - 1 * builder.highlight(14, 28, 'cd') - } - - def "highlighting single quoted string"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, " 'blabla' ") - - then: - 1 * builder.done() - 1 * builder.highlight(2, 10, 'p') - } - - def "highlighting double quoted string"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, ' "blabla" ') - - then: - 1 * builder.done() - 1 * builder.highlight(2, 10, 'p') - } - - def "highlighting variable"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, '$var=10') - - then: - 1 * builder.done() - 1 * builder.highlight(0, 4, 'a') - } - - def "highlighting keyword"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, ' class abc {}') - - then: - 1 * builder.done() - 1 * builder.highlight(1, 6, 'k') - } - - def "multiple highlighting"() { - given: - highlightable.newHighlighting() >> builder - - when: - highlighter.highlight(highlightable, "#my comments\nclass abc {\n\$var='abc'}") - - then: - 1 * builder.done() - 1 * builder.highlight(0, 12, 'cd') - 1 * builder.highlight(13, 18, 'k') - 1 * builder.highlight(30, 35, 'p') - 1 * builder.highlight(25, 29, 'a') - } - -} diff --git a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsetsSpec.groovy b/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsetsSpec.groovy deleted file mode 100644 index 54029ea..0000000 --- a/puppet-squid/src/test/groovy/com/iadams/sonarqube/puppet/highlighter/SourceFileOffsetsSpec.groovy +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.highlighter - -import com.google.common.base.Charsets -import com.iadams.sonarqube.puppet.PuppetConfiguration -import com.iadams.sonarqube.puppet.lexer.PuppetLexer -import com.sonar.sslr.api.Token -import com.sonar.sslr.impl.Lexer -import spock.lang.Specification - -class SourceFileOffsetsSpec extends Specification { - - private Lexer lexer = PuppetLexer.create(new PuppetConfiguration(Charsets.UTF_8)); - - def "one line"() { - given: - String string = "class abc {}"; - SourceFileOffsets offsets = new SourceFileOffsets(string); - List tokens = lexer.lex(string); - - expect: - offsets.startOffset(tokens.get(0)) == 0; - offsets.endOffset(tokens.get(0)) == 5; - - offsets.startOffset(tokens.get(1)) == 6; - offsets.endOffset(tokens.get(1)) == 9; - - offsets.startOffset(tokens.get(2)) == 10; - offsets.endOffset(tokens.get(2)) == 11; - - offsets.startOffset(tokens.get(3)) == 11; - offsets.endOffset(tokens.get(3)) == 12; - } - - def "three lines"() { - given: - String string = "class abc {\n\$abc => 'abc'\n }"; - SourceFileOffsets offsets = new SourceFileOffsets(string); - List tokens = lexer.lex(string); - - expect: - offsets.startOffset(tokens.get(0)) == 0; - offsets.endOffset(tokens.get(0)) == 5; - - offsets.startOffset(tokens.get(1)) == 6; - offsets.endOffset(tokens.get(1)) == 9; - - offsets.startOffset(tokens.get(2)) == 10; - offsets.endOffset(tokens.get(2)) == 11; - - offsets.startOffset(tokens.get(3)) == 12; - offsets.endOffset(tokens.get(3)) == 16; - - offsets.startOffset(tokens.get(4)) == 17; - offsets.endOffset(tokens.get(4)) == 19; - - offsets.startOffset(tokens.get(5)) == 20; - offsets.endOffset(tokens.get(5)) == 25; - - offsets.startOffset(tokens.get(6)) == 27; - offsets.endOffset(tokens.get(6)) == 28; - } - -} diff --git a/sonar-puppet-plugin/build.gradle b/sonar-puppet-plugin/build.gradle index ff51bf6..3d56eca 100644 --- a/sonar-puppet-plugin/build.gradle +++ b/sonar-puppet-plugin/build.gradle @@ -25,10 +25,10 @@ task functionalTest(type: Test) { dependencies { compile project(':puppet-checks') - provided "org.codehaus.sonar:sonar-plugin-api:$sonarVersion" + provided "org.sonarsource.sonarqube:sonar-plugin-api:$sonarVersion" - testCompile "org.codehaus.sonar:sonar-plugin-api:$sonarVersion" - testCompile "org.codehaus.sonar:sonar-testing-harness:$sonarVersion" + testCompile "org.sonarsource.sonarqube:sonar-plugin-api:$sonarVersion" + testCompile "org.sonarsource.sonarqube:sonar-testing-harness:$sonarVersion" testCompile 'org.apache.maven:maven-project:2.2.1' testCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1' testCompile 'com.iadams.sonarqube:sonar-functional-test-harness:0.1.7' diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetHighlighter.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetHighlighter.java new file mode 100644 index 0000000..b3cc3b4 --- /dev/null +++ b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetHighlighter.java @@ -0,0 +1,83 @@ +/** + * SonarQube Puppet Plugin + * The MIT License (MIT) + * + * Copyright (c) 2015 Iain Adams and David RACODON + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.iadams.sonarqube.puppet; + +import com.iadams.sonarqube.puppet.api.PuppetKeyword; +import com.iadams.sonarqube.puppet.api.PuppetTokenType; +import com.sonar.sslr.api.AstAndTokenVisitor; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.Trivia; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.squidbridge.SquidAstVisitor; + +public class PuppetHighlighter extends SquidAstVisitor implements AstAndTokenVisitor { + + private NewHighlighting newHighlighting; + private final SensorContext context; + + public PuppetHighlighter(SensorContext context) { + this.context = context; + } + + @Override + public void visitFile(@Nullable AstNode astNode) { + newHighlighting = context.newHighlighting(); + InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().is(getContext().getFile().getAbsoluteFile())); + newHighlighting.onFile(inputFile); + } + + @Override + public void visitToken(Token token) { + if (token.getType().equals(PuppetTokenType.SINGLE_QUOTED_STRING_LITERAL) || token.getType().equals(PuppetTokenType.DOUBLE_QUOTED_STRING_LITERAL)) { + // case: string literal, including doc string + highlight(token, TypeOfText.STRING); + + } else if (token.getType() instanceof PuppetKeyword) { + // case: keyword + highlight(token, TypeOfText.KEYWORD); + } + + for (Trivia trivia : token.getTrivia()) { + // case: comment + highlight(trivia.getToken(), TypeOfText.COMMENT); + } + } + + @Override + public void leaveFile(@Nullable AstNode astNode) { + newHighlighting.save(); + } + + private void highlight(Token token, TypeOfText typeOfText) { + TokenLocation tokenLocation = new TokenLocation(token); + newHighlighting.highlight(tokenLocation.startLine(), tokenLocation.startLineOffset(), tokenLocation.endLine(), tokenLocation.endLineOffset(), typeOfText); + } +} diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetPlugin.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetPlugin.java index 1382e37..ee00640 100644 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetPlugin.java +++ b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetPlugin.java @@ -25,7 +25,6 @@ package com.iadams.sonarqube.puppet; import com.google.common.collect.ImmutableList; -import com.iadams.sonarqube.puppet.colorizer.PuppetColorizer; import com.iadams.sonarqube.puppet.cpd.PuppetCpdMapping; import java.util.List; import org.sonar.api.SonarPlugin; @@ -48,7 +47,6 @@ public List getExtensions() { .build(), Puppet.class, - PuppetColorizer.class, PuppetCpdMapping.class, PuppetProfile.class, PuppetSquidSensor.class, diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetSquidSensor.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetSquidSensor.java index 64c00ed..54047f9 100644 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetSquidSensor.java +++ b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/PuppetSquidSensor.java @@ -28,33 +28,31 @@ import com.iadams.sonarqube.puppet.api.PuppetMetric; import com.iadams.sonarqube.puppet.checks.CheckList; import com.iadams.sonarqube.puppet.checks.ProjectChecks; -import com.iadams.sonarqube.puppet.highlighter.PuppetHighlighter; import com.iadams.sonarqube.puppet.metrics.FileLinesVisitor; import com.sonar.sslr.api.Grammar; +import java.io.Serializable; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.batch.Sensor; -import org.sonar.api.batch.SensorContext; -import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.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.DefaultInputFile; import org.sonar.api.batch.rule.CheckFactory; import org.sonar.api.batch.rule.Checks; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; import org.sonar.api.issue.NoSonarFilter; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.FileLinesContextFactory; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.Project; +import org.sonar.api.measures.Metric; import org.sonar.api.rule.RuleKey; -import org.sonar.api.source.Highlightable; import org.sonar.squidbridge.AstScanner; import org.sonar.squidbridge.SquidAstVisitor; import org.sonar.squidbridge.api.CheckMessage; @@ -62,125 +60,111 @@ import org.sonar.squidbridge.api.SourceFile; import org.sonar.squidbridge.indexer.QueryByType; -public class PuppetSquidSensor implements Sensor { +public final class PuppetSquidSensor implements Sensor { private static final Logger LOGGER = LoggerFactory.getLogger(PuppetSquidSensor.class); private final Checks> checks; private final FileLinesContextFactory fileLinesContextFactory; - private final FilePredicate mainFilePredicate; - - private SensorContext context; - private FileSystem fileSystem; - private ResourcePerspectives resourcePerspectives; private final NoSonarFilter noSonarFilter; - private final RulesProfile rulesProfile; - private Project project; - - public PuppetSquidSensor(FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem, ResourcePerspectives perspectives, CheckFactory checkFactory, - NoSonarFilter noSonarFilter, RulesProfile rulesProfile) { - this.fileLinesContextFactory = fileLinesContextFactory; - this.fileSystem = fileSystem; - this.resourcePerspectives = perspectives; - this.noSonarFilter = noSonarFilter; - this.rulesProfile = rulesProfile; - this.mainFilePredicate = fileSystem.predicates().and( - fileSystem.predicates().hasType(InputFile.Type.MAIN), - fileSystem.predicates().hasLanguage(Puppet.KEY)); + private SensorContext context; + private AstScanner scanner; - checks = checkFactory + public PuppetSquidSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter) { + this.checks = checkFactory .>create(CheckList.REPOSITORY_KEY) .addAnnotatedChecks(CheckList.getChecks()); + this.fileLinesContextFactory = fileLinesContextFactory; + this.noSonarFilter = noSonarFilter; } @Override - public boolean shouldExecuteOnProject(Project project) { - FilePredicates p = fileSystem.predicates(); - return fileSystem.hasFiles(p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(Puppet.KEY))); + public void describe(SensorDescriptor descriptor) { + descriptor + .onlyOnLanguage(Puppet.KEY) + .name("Puppet Squid Sensor") + .onlyOnFileType(InputFile.Type.MAIN); } @Override - public void analyse(Project project, SensorContext context) { - this.project = project; + public void execute(SensorContext context) { this.context = context; + Map> linesOfCode = new HashMap<>(); + + PuppetConfiguration conf = createConfiguration(); List> visitors = Lists.newArrayList(checks.all()); - visitors.add(new FileLinesVisitor(fileLinesContextFactory, fileSystem)); - AstScanner scanner = PuppetAstScanner.create(createConfiguration(), visitors.toArray(new SquidAstVisitor[visitors.size()])); - FilePredicates p = fileSystem.predicates(); - scanner.scanFiles(Lists.newArrayList(fileSystem.files(p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(Puppet.KEY))))); + visitors.add(new FileLinesVisitor(fileLinesContextFactory, context.fileSystem(), linesOfCode, conf.getIgnoreHeaderComments())); + visitors.add(new PuppetHighlighter(context)); + scanner = PuppetAstScanner.create(conf, visitors.toArray(new SquidAstVisitor[visitors.size()])); + FilePredicates p = context.fileSystem().predicates(); + scanner.scanFiles(Lists.newArrayList(context.fileSystem().files(p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(Puppet.KEY))))); Collection squidSourceFiles = scanner.getIndex().search(new QueryByType(SourceFile.class)); save(squidSourceFiles); - highlight(); + new ProjectChecks(context).reportProjectIssues(); +// savePreciseIssues( +// visitors +// .stream() +// .filter(v -> v instanceof PuppetCheck) +// .map(v -> (PuppetCheck) v) +// .collect(Collectors.toList())); + +// (new PuppetCoverageSensor()).execute(context, linesOfCode); } private PuppetConfiguration createConfiguration() { - return new PuppetConfiguration(fileSystem.encoding()); + return new PuppetConfiguration(context.fileSystem().encoding()); } private void save(Collection squidSourceFiles) { for (SourceCode squidSourceFile : squidSourceFiles) { SourceFile squidFile = (SourceFile) squidSourceFile; - InputFile sonarFile = fileSystem.inputFile(fileSystem.predicates().hasAbsolutePath(squidFile.getKey())); - if (sonarFile != null) { - noSonarFilter.addComponent(((DefaultInputFile) sonarFile).key(), squidFile.getNoSonarTagLines()); - } - saveMeasures(sonarFile, squidFile); - saveIssues(sonarFile, squidFile); + InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().is(new java.io.File(squidFile.getKey()))); + + noSonarFilter.noSonarInFile(inputFile, squidFile.getNoSonarTagLines()); + + saveMeasures(inputFile, squidFile); + saveIssues(inputFile, squidFile); } - ProjectChecks projectChecks = new ProjectChecks(project, fileSystem, rulesProfile, checks, resourcePerspectives); - projectChecks.reportProjectIssues(); } private void saveMeasures(InputFile sonarFile, SourceFile squidFile) { - context.saveMeasure(sonarFile, CoreMetrics.FILES, squidFile.getDouble(PuppetMetric.FILES)); - context.saveMeasure(sonarFile, CoreMetrics.LINES, squidFile.getDouble(PuppetMetric.LINES)); - context.saveMeasure(sonarFile, CoreMetrics.NCLOC, squidFile.getDouble(PuppetMetric.LINES_OF_CODE)); - context.saveMeasure(sonarFile, CoreMetrics.STATEMENTS, squidFile.getDouble(PuppetMetric.STATEMENTS)); - context.saveMeasure(sonarFile, CoreMetrics.FUNCTIONS, squidFile.getDouble(PuppetMetric.FUNCTIONS)); - context.saveMeasure(sonarFile, CoreMetrics.CLASSES, squidFile.getDouble(PuppetMetric.CLASSES)); - context.saveMeasure(sonarFile, CoreMetrics.COMPLEXITY, squidFile.getDouble(PuppetMetric.COMPLEXITY)); - context.saveMeasure(sonarFile, CoreMetrics.COMMENT_LINES, squidFile.getDouble(PuppetMetric.COMMENT_LINES)); + saveMetricOnFile(sonarFile, CoreMetrics.FILES, squidFile.getInt(PuppetMetric.FILES)); + saveMetricOnFile(sonarFile, CoreMetrics.LINES, squidFile.getInt(PuppetMetric.LINES)); + saveMetricOnFile(sonarFile, CoreMetrics.NCLOC, squidFile.getInt(PuppetMetric.LINES_OF_CODE)); + saveMetricOnFile(sonarFile, CoreMetrics.STATEMENTS, squidFile.getInt(PuppetMetric.STATEMENTS)); + saveMetricOnFile(sonarFile, CoreMetrics.FUNCTIONS, squidFile.getInt(PuppetMetric.FUNCTIONS)); + saveMetricOnFile(sonarFile, CoreMetrics.CLASSES, squidFile.getInt(PuppetMetric.CLASSES)); + saveMetricOnFile(sonarFile, CoreMetrics.COMPLEXITY, squidFile.getInt(PuppetMetric.COMPLEXITY)); + saveMetricOnFile(sonarFile, CoreMetrics.COMMENT_LINES, squidFile.getInt(PuppetMetric.COMMENT_LINES)); } - private void saveIssues(InputFile sonarFile, SourceFile squidFile) { + private void saveMetricOnFile(InputFile inputFile, Metric metric, T value) { + context.newMeasure() + .withValue(value) + .forMetric(metric) + .on(inputFile) + .save(); + } + + private void saveIssues(InputFile inputFile, SourceFile squidFile) { Collection messages = squidFile.getCheckMessages(); for (CheckMessage message : messages) { RuleKey ruleKey = checks.ruleKey((SquidAstVisitor) message.getCheck()); - Issuable issuable = resourcePerspectives.as(Issuable.class, sonarFile); - - if (issuable != null) { - Issue issue = issuable.newIssueBuilder() - .ruleKey(ruleKey) - .line(message.getLine()) - .message(message.getText(Locale.ENGLISH)) - .effortToFix(message.getCost()) - .build(); - issuable.addIssue(issue); - } - } - } - - private void highlight() { - PuppetHighlighter highlighter = new PuppetHighlighter(createConfiguration()); + NewIssue newIssue = context.newIssue(); - for (InputFile inputFile : fileSystem.inputFiles(mainFilePredicate)) { - Highlightable perspective = resourcePerspectives.as(Highlightable.class, inputFile); + NewIssueLocation primaryLocation = newIssue.newLocation() + .message(message.getText(Locale.ENGLISH)) + .on(inputFile); - if (perspective != null) { - highlighter.highlight(perspective, inputFile.file()); - - } else { - LOGGER.warn("Could not get " + Highlightable.class.getCanonicalName() + " for " + inputFile.file()); + if (message.getLine() != null) { + primaryLocation.at(inputFile.selectLine(message.getLine())); } - } - } - @Override - public String toString() { - return getClass().getSimpleName(); + newIssue.forRule(ruleKey).at(primaryLocation).save(); + } } } diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/checks/ProjectChecks.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/checks/ProjectChecks.java index 3f36bbe..0a746db 100644 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/checks/ProjectChecks.java +++ b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/checks/ProjectChecks.java @@ -24,50 +24,39 @@ */ package com.iadams.sonarqube.puppet.checks; -import com.sonar.sslr.api.Grammar; - import java.io.File; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.rule.Checks; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.Directory; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRule; -import org.sonar.squidbridge.SquidAstVisitor; -import org.sonar.squidbridge.api.CodeVisitor; public class ProjectChecks { - private final Project project; - private final FileSystem fileSystem; - private final RulesProfile rulesProfile; - private final Checks> checks; - private final ResourcePerspectives resourcePerspectives; + private final SensorContext context; + + private static final Logger LOGGER = LoggerFactory.getLogger(ProjectChecks.class); + private static final int readmeCheckDepth = 4; + private static final int metadataCheckDepth = 4; + private static final int testsCheckDepth = 4; - public ProjectChecks(Project project, FileSystem fileSystem, RulesProfile rulesProfile, Checks> checks, - ResourcePerspectives resourcePerspectives) { - this.project = project; - this.fileSystem = fileSystem; - this.rulesProfile = rulesProfile; - this.checks = checks; - this.resourcePerspectives = resourcePerspectives; + public ProjectChecks(SensorContext context) { + this.context = context; } public void reportProjectIssues() { - if (fileSystem.baseDir() != null) { - checkTestsDirectoryPresent(fileSystem.baseDir()); - checkMetadataJsonFilePresent(fileSystem.baseDir()); - checkReadmeFilePresent(fileSystem.baseDir()); + if (context.fileSystem().baseDir() != null) { + checkTestsDirectoryPresent(context.fileSystem().baseDir(), 0); + checkMetadataJsonFilePresent(context.fileSystem().baseDir(), 0); + checkReadmeFilePresent(context.fileSystem().baseDir(), 0); } } - private void checkMetadataJsonFilePresent(File parentFile) { + private void checkMetadataJsonFilePresent(File parentFile, int depth) { for (File file : parentFile.listFiles()) { if (file.isDirectory()) { if ("manifests".equals(file.getName())) { @@ -79,23 +68,24 @@ private void checkMetadataJsonFilePresent(File parentFile) { } } if (!metadataJsonFileFound) { - String path; - Directory directory = Directory.fromIOFile(parentFile, project); - if (directory != null && directory.getPath() != null) { - path = directory.getPath(); + InputDir inputDir = context.fileSystem().inputDir(parentFile); + if (inputDir == null) { + addIssue(MetadataJsonFilePresentCheck.RULE_KEY, "Add a \"metadata.json\" file to the \"" + context.module().key() + "\" Puppet module.", context.module()); } else { - path = parentFile.getName(); + addIssue(MetadataJsonFilePresentCheck.RULE_KEY, "Add a \"metadata.json\" file to the \"" + inputDir.relativePath() + "\" Puppet module.", inputDir); } - addIssue(MetadataJsonFilePresentCheck.RULE_KEY, "Add a \"metadata.json\" file to the \"" + path + "\" Puppet module."); } } else { - checkMetadataJsonFilePresent(file); + depth++; + if (depth < metadataCheckDepth) { + checkMetadataJsonFilePresent(file, depth); + } } } } } - private void checkReadmeFilePresent(File parentFile) { + private void checkReadmeFilePresent(File parentFile, int depth) { for (File file : parentFile.listFiles()) { if (file.isDirectory()) { if ("manifests".equals(file.getName())) { @@ -107,49 +97,63 @@ private void checkReadmeFilePresent(File parentFile) { } } if (!readmeFileFound) { - String path = Directory.fromIOFile(parentFile, project).getPath() != null ? Directory.fromIOFile(parentFile, project).getPath() : parentFile.getName(); - addIssue(ReadmeFilePresentCheck.RULE_KEY, "Add a \"README.md\" file to the \"" + path + "\" Puppet module."); + InputDir inputDir = context.fileSystem().inputDir(parentFile); + if (inputDir == null) { + addIssue(ReadmeFilePresentCheck.RULE_KEY, "Add a \"README.md\" file to the \"" + context.module().key() + "\" Puppet module.", context.module()); + } else { + addIssue(ReadmeFilePresentCheck.RULE_KEY, "Add a \"README.md\" file to the \"" + inputDir.relativePath() + "\" Puppet module.", inputDir); + } } } else { - checkReadmeFilePresent(file); + depth++; + if (depth < readmeCheckDepth) { + checkReadmeFilePresent(file, depth); + } } } } } - private void checkTestsDirectoryPresent(File parentFile) { + private void checkTestsDirectoryPresent(File parentFile, int depth) { for (File file : parentFile.listFiles()) { if (file.isDirectory()) { if ("tests".equals(file.getName())) { + LOGGER.info("Path: " + file.getPath()); for (File testsSiblings : file.getParentFile().listFiles()) { - Directory directory = Directory.fromIOFile(file, project); + InputDir inputDir = context.fileSystem().inputDir(file); if (testsSiblings.isDirectory() - && "manifests".equals(testsSiblings.getName()) - && directory != null - && directory.getPath() != null) { - addIssue(TestsDirectoryPresentCheck.RULE_KEY, "Replace the \"" + directory.getPath() + "\" directory with an \"examples\" directory."); - break; + && "manifests".equals(testsSiblings.getName())) { + if (inputDir == null) { + addIssue(TestsDirectoryPresentCheck.RULE_KEY, "Replace the \"tests\" directory with an \"examples\" directory.", context.module()); + break; + } else { + addIssue(TestsDirectoryPresentCheck.RULE_KEY, "Replace the \"" + inputDir.path() + "\" directory with an \"examples\" directory.", inputDir); + break; + } } } } else { - checkTestsDirectoryPresent(file); + depth++; + if (depth < testsCheckDepth) { + checkTestsDirectoryPresent(file, depth); + } } } } } - protected void addIssue(String ruleKey, String message) { - ActiveRule activeRule = rulesProfile.getActiveRule(CheckList.REPOSITORY_KEY, ruleKey); - if (activeRule != null) { - CodeVisitor check = checks.of(activeRule.getRule().ruleKey()); - if (check != null) { - Issuable issuable = resourcePerspectives.as(Issuable.class, (Resource) project); - if (issuable != null) { - Issue issue = issuable.newIssueBuilder().ruleKey(RuleKey.of(CheckList.REPOSITORY_KEY, ruleKey)).message(message).build(); - issuable.addIssue(issue); - } - } - } + protected void addIssue(String ruleKey, String message, InputComponent inputComponent) { + LOGGER.info("Adding issue: " + ruleKey + " " + message); + NewIssue newIssue = context + .newIssue() + .forRule(RuleKey.of(CheckList.REPOSITORY_KEY, ruleKey)); + + newIssue.at(newLocation(inputComponent, newIssue, message)).save(); + } + + private static NewIssueLocation newLocation(InputComponent input, NewIssue issue, String message) { + return issue.newLocation() + .on(input).message(message); } } diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetColorizer.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetColorizer.java deleted file mode 100644 index c6bf24d..0000000 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetColorizer.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.colorizer; - -import com.google.common.collect.Lists; -import com.iadams.sonarqube.puppet.Puppet; -import com.iadams.sonarqube.puppet.api.PuppetKeyword; -import org.sonar.api.web.CodeColorizerFormat; -import org.sonar.colorizer.*; - -import java.util.List; - -public class PuppetColorizer extends CodeColorizerFormat { - - private List tokenizers; - private static final String END_TAG = ""; - - public PuppetColorizer() { - super(Puppet.KEY); - } - - @Override - public List getTokenizers() { - if (tokenizers == null) { - tokenizers = Lists.newArrayList(); - tokenizers.add(new KeywordsTokenizer("", END_TAG, PuppetKeyword.keywordValues())); - tokenizers.add(new PuppetDocStringTokenizer("", END_TAG)); - tokenizers.add(new StringTokenizer("", END_TAG)); - tokenizers.add(new PuppetDocTokenizer("", END_TAG)); - tokenizers.add(new CDocTokenizer("", END_TAG)); - tokenizers.add(new CppDocTokenizer("", END_TAG)); - } - return tokenizers; - } -} diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocStringTokenizer.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocStringTokenizer.java deleted file mode 100644 index 0268521..0000000 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/PuppetDocStringTokenizer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.colorizer; - -import org.sonar.colorizer.MultilinesDocTokenizer; - -public class PuppetDocStringTokenizer extends MultilinesDocTokenizer { - - public PuppetDocStringTokenizer(String tagBefore, String tagAfter) { - super("\"\"\"", "\"\"\"", tagBefore, tagAfter); - } -} diff --git a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/package-info.java b/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/package-info.java deleted file mode 100644 index ab869aa..0000000 --- a/sonar-puppet-plugin/src/main/java/com/iadams/sonarqube/puppet/colorizer/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -/** - * Support for code colorizer. - */ -@ParametersAreNonnullByDefault -package com.iadams.sonarqube.puppet.colorizer; - -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetHighlighterSpec.groovy b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetHighlighterSpec.groovy new file mode 100644 index 0000000..8fb6ef1 --- /dev/null +++ b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetHighlighterSpec.groovy @@ -0,0 +1,114 @@ +/* + * SonarQube Puppet Plugin + * The MIT License (MIT) + * + * Copyright (c) 2015 Iain Adams and David RACODON + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.iadams.sonarqube.puppet + +import spock.lang.Specification + +import com.google.common.base.Charsets +import org.sonar.api.batch.fs.internal.DefaultInputFile +import org.sonar.api.batch.fs.internal.FileMetadata +import org.sonar.api.batch.sensor.highlighting.TypeOfText +import org.sonar.api.batch.sensor.internal.SensorContextTester + +class PuppetHighlighterSpec extends Specification { + + private SensorContextTester context + + private File file + + def setup(){ + String dir = "src/test/resources/com/iadams/sonarqube/puppet" + + file = new File(dir + "/puppetHighlighter.pp") + DefaultInputFile inputFile = new DefaultInputFile("moduleKey", file.getName()) + .initMetadata(new FileMetadata().readMetadata(file, Charsets.UTF_8)) + + context = SensorContextTester.create(new File(dir)) + context.fileSystem().add(inputFile) + + PuppetHighlighter puppetHighligher = new PuppetHighlighter(context) + PuppetAstScanner.scanSingleFile(file, puppetHighligher) + } + + + def "keywords"() { + expect: + //class ssh::client inherits workstation { } + checkOnRange(7, 0, 5, TypeOfText.KEYWORD) + checkOnRange(7, 19, 7, TypeOfText.KEYWORD) + //class wordpress inherits apache { } + checkOnRange(9, 0, 5, TypeOfText.KEYWORD) + checkOnRange(9, 17, 7, TypeOfText.KEYWORD) + } + + def "string literals"() { + expect: + //$variable = "this is a string" + checkOnRange(4, 13, 17, TypeOfText.STRING) + //$variable2 = "this is a string" + checkOnRange(5, 14, 17, TypeOfText.STRING) + //file { '/tmp/foo': + checkOnRange(11, 8, 9, TypeOfText.STRING) + // purge => 'true', + checkOnRange(12, 12, 5, TypeOfText.STRING) + } + + def "comments"() { + expect: + checkOnRange(1, 1, 8, TypeOfText.COMMENT) + checkOnRange(2, 1, 10, TypeOfText.COMMENT) + } + + /** + * Checks the highlighting of a range of columns. + * The range is the columns of the token. + */ + private void checkOnRange(int line, int firstColumn, int length, TypeOfText expectedTypeOfText) { + // check that every column of the token is highlighted (and with the expected type) + for (int column = firstColumn; column < firstColumn + length; column++) { + checkInternal(line, column, expectedTypeOfText) + } + + // check that the column before the token is not highlighted + if (firstColumn != 1) { + checkInternal(line, firstColumn - 1, null) + } + + // check that the column after the token is not highlighted + checkInternal(line, firstColumn + length, null) + } + + private void checkInternal(int line, int column, TypeOfText expectedTypeOfText) { + String componentKey = "moduleKey:" + file.getName() + List foundTypeOfTexts = context.highlightingTypeAt(componentKey, line, column) + + int expectedNumberOfTypeOfText = expectedTypeOfText == null ? 0 : 1 + foundTypeOfTexts.size() == expectedNumberOfTypeOfText + if (expectedNumberOfTypeOfText > 0) { + foundTypeOfTexts.get(0) == expectedTypeOfText + } + } + +} \ No newline at end of file diff --git a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetPluginSpec.groovy b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetPluginSpec.groovy index 97607ca..492c61b 100644 --- a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetPluginSpec.groovy +++ b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetPluginSpec.groovy @@ -29,6 +29,6 @@ import spock.lang.Specification class PuppetPluginSpec extends Specification { def "GetExtensions"() { expect: - new PuppetPlugin().getExtensions().size() == 8 + new PuppetPlugin().getExtensions().size() == 7 } } diff --git a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetSquidSensorSpec.groovy b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetSquidSensorSpec.groovy index a77f0d0..62652c9 100644 --- a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetSquidSensorSpec.groovy +++ b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/PuppetSquidSensorSpec.groovy @@ -24,81 +24,165 @@ */ package com.iadams.sonarqube.puppet +import com.google.common.base.Charsets import com.iadams.sonarqube.puppet.checks.CheckList -import org.sonar.api.batch.SensorContext +import org.junit.Rule +import org.junit.rules.TemporaryFolder import org.sonar.api.batch.fs.InputFile -import org.sonar.api.batch.fs.internal.DefaultFileSystem +import org.sonar.api.batch.fs.InputFile.Type import org.sonar.api.batch.fs.internal.DefaultInputFile +import org.sonar.api.batch.fs.internal.DefaultTextRange +import org.sonar.api.batch.fs.internal.FileMetadata import org.sonar.api.batch.rule.ActiveRules import org.sonar.api.batch.rule.CheckFactory import org.sonar.api.batch.rule.internal.ActiveRulesBuilder -import org.sonar.api.component.ResourcePerspectives +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor +import org.sonar.api.batch.sensor.internal.SensorContextTester +import org.sonar.api.batch.sensor.issue.Issue +import org.sonar.api.batch.sensor.issue.IssueLocation import org.sonar.api.issue.NoSonarFilter import org.sonar.api.measures.CoreMetrics import org.sonar.api.measures.FileLinesContext import org.sonar.api.measures.FileLinesContextFactory -import org.sonar.api.profiles.RulesProfile -import org.sonar.api.resources.Project import org.sonar.api.rule.RuleKey +import org.sonar.api.utils.log.LogTester import spock.lang.Specification class PuppetSquidSensorSpec extends Specification { - private PuppetSquidSensor sensor - private DefaultFileSystem fs = new DefaultFileSystem() - ResourcePerspectives perspectives + @Rule + TemporaryFolder folder = new TemporaryFolder() - def setup() { - FileLinesContextFactory fileLinesContextFactory = Mock() - FileLinesContext fileLinesContext = Mock() - NoSonarFilter noSonarFilter = Mock() - RulesProfile rulesProfile = Mock() + @Rule + LogTester logTester = new LogTester() - fileLinesContextFactory.createFor(_ as InputFile) >> fileLinesContext - ActiveRules activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, "LineLength")) - .setName("Lines should not be too long") + private final File baseDir = new File("src/test/resources/com/iadams/sonarqube/puppet/squid-sensor") + private SensorContextTester context = SensorContextTester.create(baseDir) + private ActiveRules activeRules + + def "sensor descriptor"() { + given: + activeRules = (new ActiveRulesBuilder()).build() + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + sensor().describe(descriptor) + + expect: + descriptor.name() == "Puppet Squid Sensor" + descriptor.languages().size() == 1 + descriptor.languages().contains('pp') + descriptor.type() == Type.MAIN + } + + def "test execute"() { + given: + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.REPOSITORY_KEY, "QuotedBoolean")) + .setName("Booleans should not be quoted") .activate() - .build(); - CheckFactory checkFactory = new CheckFactory(activeRules) - perspectives = Mock() - sensor = new PuppetSquidSensor(fileLinesContextFactory, fs, perspectives, checkFactory, noSonarFilter, rulesProfile) + .build() + + inputFile("file1.pp") + + sensor().execute(context) + + String key = "moduleKey:file1.pp" + + expect: + context.measure(key, CoreMetrics.NCLOC).value() == 14 + context.measure(key, CoreMetrics.FILES).value() == 1 + context.measure(key, CoreMetrics.STATEMENTS).value() == 7 + context.measure(key, CoreMetrics.CLASSES).value() == 2 + context.measure(key, CoreMetrics.COMPLEXITY).value() == 9 + context.measure(key, CoreMetrics.COMMENT_LINES).value() == 7 + + context.allIssues().size() == 1 } - def "should execute on puppet project"() { - when: - Project project = Mock() + def "test issues correct"() { + given: + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.REPOSITORY_KEY, "QuotedBoolean")) + .activate() + .create(RuleKey.of(CheckList.REPOSITORY_KEY, "S1862")) + .activate() + .create(RuleKey.of(CheckList.REPOSITORY_KEY, "ComplexExpression")) + .setParam('max', '2') + .activate() + .build() - then: - sensor.toString() == "PuppetSquidSensor" - sensor.shouldExecuteOnProject(project) == false + InputFile inputFile = inputFile("file1.pp") when: - fs.add(new DefaultInputFile("test.pp").setLanguage(Puppet.KEY)) + sensor().execute(context) then: - sensor.shouldExecuteOnProject(project) == true - } + context.allIssues().size() == 3 + Iterator issuesIterator = context.allIssues().iterator() - def "should_analyse"() { - given: - String relativePath = "src/test/resources/com/iadams/sonarqube/puppet/code_chunks.pp" - DefaultInputFile inputFile = new DefaultInputFile(relativePath).setLanguage(Puppet.KEY) - inputFile.setAbsolutePath((new File(relativePath)).getAbsolutePath()) + int checkedIssues = 0 - fs.add(inputFile) + while (issuesIterator.hasNext()) { + Issue issue = issuesIterator.next() + IssueLocation issueLocation = issue.primaryLocation() + issueLocation.inputComponent() == inputFile +DefaultTextRange + if (issue.ruleKey().rule() == "S1862") { + assert issueLocation.message() == 'This branch duplicates the one on line 17.' + assert issueLocation.textRange() == inputFile.newRange(23, 0, 23, 33) + assert issue.flows().isEmpty() + assert issue.gap() == null + checkedIssues++ + } else if (issue.ruleKey().rule() == "QuotedBoolean") { + assert issueLocation.message() == "Remove quotes." + assert issueLocation.textRange() == inputFile.newRange(14, 0, 14, 18) + assert issue.flows().isEmpty() + assert issue.gap() == null + checkedIssues++ + } else if (issue.ruleKey().rule() == "ComplexExpression") { + assert issueLocation.message() == "Reduce the number of boolean operators. This condition contains 4 boolean operators, 2 more than the 2 maximum." + assert issueLocation.textRange() == inputFile.newRange(11, 0, 11, 103) + assert issue.flows().isEmpty() + assert issue.gap() == null + checkedIssues++ + } else { + throw new IllegalStateException() + } + } - Project project = new Project("key") - SensorContext context = Mock() + checkedIssues == 3 + } + + def "parse_error"() { + given: + inputFile("parsing_error.pp") + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.REPOSITORY_KEY, "ParsingError")) + .activate() + .build() when: - sensor.analyse(project, context) + sensor().execute(context) then: - 1 * context.saveMeasure(_, CoreMetrics.FILES, 1.0) - 1 * context.saveMeasure(_, CoreMetrics.LINES, 9.0) - 1 * context.saveMeasure(_, CoreMetrics.CLASSES, 2.0) - 1 * context.saveMeasure(_, CoreMetrics.COMMENT_LINES, 2.0) + context.allIssues().size() == 1 + context.allIssues()[0].primaryLocation().message() == 'Parse error at line 1 column 13:\n\n --> file { \'name\';}EOF' } + private PuppetSquidSensor sensor() { + FileLinesContextFactory fileLinesContextFactory = Mock(FileLinesContextFactory) + FileLinesContext fileLinesContext = Mock(FileLinesContext) + fileLinesContextFactory.createFor(_ as InputFile) >> fileLinesContext + CheckFactory checkFactory = new CheckFactory(activeRules) + return new PuppetSquidSensor(fileLinesContextFactory, checkFactory, new NoSonarFilter()) + } + + private InputFile inputFile(String name) { + DefaultInputFile inputFile = new DefaultInputFile("moduleKey", name) + .setModuleBaseDir(baseDir.toPath()) + .setType(Type.MAIN) + .setLanguage(Puppet.KEY) + context.fileSystem().add(inputFile) + inputFile.initMetadata(new FileMetadata().readMetadata(inputFile.file(), Charsets.UTF_8)) + return inputFile + } } diff --git a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/checks/ProjectChecksSpec.groovy b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/checks/ProjectChecksSpec.groovy index 73c4214..dce37aa 100644 --- a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/checks/ProjectChecksSpec.groovy +++ b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/checks/ProjectChecksSpec.groovy @@ -24,20 +24,13 @@ */ package com.iadams.sonarqube.puppet.checks -import com.sonar.sslr.api.Grammar import org.junit.Rule import org.junit.rules.TemporaryFolder -import org.sonar.api.batch.fs.internal.DefaultFileSystem -import org.sonar.api.batch.rule.ActiveRules -import org.sonar.api.batch.rule.CheckFactory -import org.sonar.api.batch.rule.Checks +import org.sonar.api.batch.fs.internal.DefaultInputDir +import org.sonar.api.batch.fs.internal.DefaultInputFile import org.sonar.api.batch.rule.internal.ActiveRulesBuilder -import org.sonar.api.component.ResourcePerspectives -import org.sonar.api.profiles.RulesProfile -import org.sonar.api.resources.Project -import org.sonar.api.resources.ProjectFileSystem +import org.sonar.api.batch.sensor.internal.SensorContextTester import org.sonar.api.rule.RuleKey -import org.sonar.squidbridge.SquidAstVisitor import spock.lang.Specification class ProjectChecksSpec extends Specification { @@ -45,59 +38,51 @@ class ProjectChecksSpec extends Specification { @Rule TemporaryFolder testProjectDir = new TemporaryFolder() - ProjectChecks projectChecks; - DefaultFileSystem fs = new DefaultFileSystem() - Project project - Checks> checks - Map issues; + private SensorContextTester context + + ProjectChecks projectChecks def setup() { - fs.setBaseDir(testProjectDir.root) - ProjectFileSystem pfs = Mock() - project = Mock() - project.getFileSystem() >> pfs - pfs.getBasedir() >> fs.baseDir() - ActiveRules activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ReadmeFilePresentCheck.RULE_KEY)) - .activate() - .build(); - CheckFactory checkFactory = new CheckFactory(activeRules) - checks = checkFactory - .> create(CheckList.REPOSITORY_KEY) - .addAnnotatedChecks(CheckList.getChecks()); - - issues = new HashMap<>(); - - projectChecks = Spy(ProjectChecks, constructorArgs: [project, fs, Mock(RulesProfile), checks, Mock(ResourcePerspectives)]) - projectChecks.addIssue(_, _) >> { String ruleKey, String message -> issues.put(ruleKey, message) } + context = SensorContextTester.create(testProjectDir.root) + context.activeRules = new ActiveRulesBuilder() + .create(RuleKey.of(CheckList.REPOSITORY_KEY, ReadmeFilePresentCheck.RULE_KEY)).activate() + .create(RuleKey.of(CheckList.REPOSITORY_KEY, MetadataJsonFilePresentCheck.RULE_KEY)).activate() + .create(RuleKey.of(CheckList.REPOSITORY_KEY, TestsDirectoryPresentCheck.RULE_KEY)).activate() + .build() + projectChecks = new ProjectChecks(context) } def "readme file present check"() { given: testProjectDir.newFolder('manifests') testProjectDir.newFile('metadata.json') + context.fileSystem().add(new DefaultInputDir("myProjectKey", 'manifests').setModuleBaseDir(testProjectDir.root.toPath())) + context.fileSystem().add(new DefaultInputFile("myProjectKey", "metadata.json").setModuleBaseDir(testProjectDir.root.toPath()).initMetadata('stuff')) when: projectChecks.reportProjectIssues() then: noExceptionThrown() - issues.size() == 1 - issues.containsKey(ReadmeFilePresentCheck.RULE_KEY) + context.allIssues().size() == 1 + context.allIssues()[0].ruleKey() == RuleKey.of(CheckList.REPOSITORY_KEY, ReadmeFilePresentCheck.RULE_KEY) } def "manifest file present check"() { given: testProjectDir.newFolder('manifests') testProjectDir.newFile('README.md') + context.fileSystem().add(new DefaultInputDir("myProjectKey", 'manifests').setModuleBaseDir(testProjectDir.root.toPath())) + context.fileSystem().add(new DefaultInputFile("myProjectKey", "README.md").setModuleBaseDir(testProjectDir.root.toPath()).initMetadata('stuff')) when: projectChecks.reportProjectIssues() then: noExceptionThrown() - issues.size() == 1 - issues.containsKey(MetadataJsonFilePresentCheck.RULE_KEY) + context.allIssues().size() == 1 + context.allIssues()[0].ruleKey() == RuleKey.of(CheckList.REPOSITORY_KEY, MetadataJsonFilePresentCheck.RULE_KEY) + context.allIssues()[0].primaryLocation().message() } @@ -107,13 +92,14 @@ class ProjectChecksSpec extends Specification { testProjectDir.newFolder('tests') testProjectDir.newFile('README.md') testProjectDir.newFile('metadata.json') + context.fileSystem().add(new DefaultInputDir("myProjectKey", 'tests').setModuleBaseDir(testProjectDir.root.toPath())) when: projectChecks.reportProjectIssues() then: noExceptionThrown() - issues.size() == 1 - issues.containsKey(TestsDirectoryPresentCheck.RULE_KEY) + context.allIssues().size() == 1 + context.allIssues()[0].ruleKey() == RuleKey.of(CheckList.REPOSITORY_KEY, TestsDirectoryPresentCheck.RULE_KEY) } } diff --git a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/colorizer/PuppetColorizerSpec.groovy b/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/colorizer/PuppetColorizerSpec.groovy deleted file mode 100644 index c6e27bd..0000000 --- a/sonar-puppet-plugin/src/test/groovy/com/iadams/sonarqube/puppet/colorizer/PuppetColorizerSpec.groovy +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube Puppet Plugin - * The MIT License (MIT) - * - * Copyright (c) 2015 Iain Adams and David RACODON - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.iadams.sonarqube.puppet.colorizer - -import org.sonar.colorizer.CodeColorizer -import spock.lang.Specification - -class PuppetColorizerSpec extends Specification { - - PuppetColorizer puppetColorizer - CodeColorizer codeColorizer - - def setup() { - puppetColorizer = new PuppetColorizer() - codeColorizer = new CodeColorizer(puppetColorizer.getTokenizers()) - } - - /*def "keywords are coloured"(){ - expect: - colorize("false").contains('false') - }*/ - - def "# comment should colorize"() { - expect: - colorize("# comment").contains("# comment") - } - - def "// comment should colorize"() { - expect: - colorize("// comment").contains("// comment") - } - - def "/* */ comment should colorize"() { - expect: - colorize("/* comment */").contains("/* comment */") - } - - def "should colorize short string literals"() { - expect: - colorize('"string"').contains("\"string\"") - } - - def "should colorize long string literals"() { - expect: - colorize('"string"').contains('"string"') - } - - private String colorize(String sourceCode) { - return codeColorizer.toHtml(new StringReader(sourceCode)) - } -} \ No newline at end of file diff --git a/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/puppetHighlighter.pp b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/puppetHighlighter.pp new file mode 100644 index 0000000..a1adac0 --- /dev/null +++ b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/puppetHighlighter.pp @@ -0,0 +1,13 @@ +# Comment +# Comment 2 + +$variable = "this is a string" +$variable2 = "this is a string" + +class ssh::client inherits workstation { } + +class wordpress inherits apache { } + +file { '/tmp/foo': + purge => 'true', +} \ No newline at end of file diff --git a/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/file1.pp b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/file1.pp new file mode 100644 index 0000000..a127c94 --- /dev/null +++ b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/file1.pp @@ -0,0 +1,26 @@ +# Comment +# Comment 2 + +$variable = "this is a string" +$variable2 = "this is a string" + +class ssh::client inherits workstation { } + +class wordpress inherits apache { } + +if $var1 > $var1 or $var1 > $var1 or $var1 > $var1 or $var1 > $var1 or $var1 > $var1 {} # Noncompliant + +file { '/tmp/foo': + purge => 'true', +} + +if $param == 1{ + # stuff +} +elsif $param == 2{ + # stuff +} +elsif $param == 1{ # Noncompliant + # stuff +} + diff --git a/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/parsing_error.pp b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/parsing_error.pp new file mode 100644 index 0000000..c3bee36 --- /dev/null +++ b/sonar-puppet-plugin/src/test/resources/com/iadams/sonarqube/puppet/squid-sensor/parsing_error.pp @@ -0,0 +1 @@ +file { 'name';} \ No newline at end of file diff --git a/sslr-puppet-toolkit/build.gradle b/sslr-puppet-toolkit/build.gradle index 2d6ea9a..04aed82 100644 --- a/sslr-puppet-toolkit/build.gradle +++ b/sslr-puppet-toolkit/build.gradle @@ -11,7 +11,7 @@ apply plugin: 'com.github.johnrengelman.shadow' description = 'Puppet :: SSLR Toolkit' dependencies { compile project(':puppet-squid') - compile "org.codehaus.sonar.sslr:sslr-toolkit:$sslrVersion" + compile "org.sonarsource.sslr:sslr-toolkit:$sslrVersion" compile "ch.qos.logback:logback-classic:0.9.15" } @@ -19,23 +19,23 @@ shadowJar { classifier = '' manifest { attributes 'Main-Class': 'com.iadams.sonarqube.puppet.PuppetToolkit' - } - dependencies { - include(project(':puppet-squid')) - include(dependency('org.codehaus.sonar.sslr:sslr-core')) - include(dependency('org.codehaus.sonar.sslr:sslr-xpath')) - include(dependency('org.codehaus.sonar.sslr-squid-bridge:sslr-squid-bridge')) - include(dependency('jaxen:jaxen')) - include(dependency('org.codehaus.sonar.sslr:sslr-toolkit')) - include(dependency('org.codehaus.sonar:sonar-colorizer')) - include(dependency('org.codehaus.sonar:sonar-channel')) - include(dependency('org.slf4j:slf4j-api')) - include(dependency('org.slf4j:jcl-over-slf4j')) - include(dependency('ch.qos.logback:logback-classic')) - include(dependency('ch.qos.logback:logback-core')) - include(dependency('commons-io:commons-io')) - include(dependency('commons-lang:commons-lang')) - include(dependency('com.google.guava:guava')) + dependencies { + include(project(':puppet-squid')) + include(dependency('org.codehaus.sonar.sslr:sslr-core')) + include(dependency('org.codehaus.sonar.sslr:sslr-xpath')) + include(dependency('org.codehaus.sonar.sslr-squid-bridge:sslr-squid-bridge')) + include(dependency('jaxen:jaxen')) + include(dependency('org.codehaus.sonar.sslr:sslr-toolkit')) + include(dependency('org.codehaus.sonar:sonar-colorizer')) + include(dependency('org.codehaus.sonar:sonar-channel')) + include(dependency('org.slf4j:slf4j-api')) + include(dependency('org.slf4j:jcl-over-slf4j')) + include(dependency('ch.qos.logback:logback-classic')) + include(dependency('ch.qos.logback:logback-core')) + include(dependency('commons-io:commons-io')) + include(dependency('commons-lang:commons-lang')) + include(dependency('com.google.guava:guava')) + } } }