From e65fb9a64ef2b81c3741a63d6d4a046b37659628 Mon Sep 17 00:00:00 2001 From: vilchik-elena Date: Tue, 24 Dec 2019 11:19:48 +0100 Subject: Restructure packages --- .../main/java/org/sonar/css/plugin/CssLexer.java | 81 ------- .../main/java/org/sonar/css/plugin/CssPlugin.java | 3 +- .../main/java/org/sonar/css/plugin/CssToken.java | 45 ---- .../java/org/sonar/css/plugin/CssTokenType.java | 49 ----- .../java/org/sonar/css/plugin/MetricSensor.java | 157 -------------- .../main/java/org/sonar/css/plugin/Tokenizer.java | 38 ---- .../src/main/java/org/sonar/css/plugin/Zip.java | 52 ----- .../java/org/sonar/css/plugin/bundle/Bundle.java | 34 --- .../sonar/css/plugin/bundle/CssAnalyzerBundle.java | 82 ------- .../org/sonar/css/plugin/bundle/package-info.java | 21 -- .../org/sonar/css/plugin/metrics/CssLexer.java | 81 +++++++ .../org/sonar/css/plugin/metrics/CssToken.java | 45 ++++ .../org/sonar/css/plugin/metrics/CssTokenType.java | 49 +++++ .../org/sonar/css/plugin/metrics/MetricSensor.java | 158 ++++++++++++++ .../org/sonar/css/plugin/metrics/Tokenizer.java | 38 ++++ .../css/plugin/server/CssAnalyzerBridgeServer.java | 2 +- .../org/sonar/css/plugin/server/bundle/Bundle.java | 34 +++ .../plugin/server/bundle/CssAnalyzerBundle.java | 81 +++++++ .../org/sonar/css/plugin/server/bundle/Zip.java | 52 +++++ .../css/plugin/server/bundle/package-info.java | 21 ++ .../org/sonar/css/plugin/MetricSensorTest.java | 217 ------------------- .../java/org/sonar/css/plugin/TokenizerTest.java | 241 --------------------- .../css/plugin/bundle/CssAnalyzerBundleTest.java | 85 -------- .../sonar/css/plugin/metrics/MetricSensorTest.java | 218 +++++++++++++++++++ .../sonar/css/plugin/metrics/TokenizerTest.java | 241 +++++++++++++++++++++ .../plugin/server/CssAnalyzerBridgeServerTest.java | 2 +- .../server/bundle/CssAnalyzerBundleTest.java | 85 ++++++++ 27 files changed, 1107 insertions(+), 1105 deletions(-) delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssLexer.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssToken.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssTokenType.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/MetricSensor.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/Tokenizer.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/Zip.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/Bundle.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/CssAnalyzerBundle.java delete mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/package-info.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssLexer.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssToken.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssTokenType.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/MetricSensor.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/Tokenizer.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundle.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Zip.java create mode 100644 sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/package-info.java delete mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/MetricSensorTest.java delete mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/TokenizerTest.java delete mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java create mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/MetricSensorTest.java create mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/TokenizerTest.java create mode 100644 sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundleTest.java diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssLexer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssLexer.java deleted file mode 100644 index 4167b76..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssLexer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import com.sonar.sslr.impl.Lexer; - -import static com.sonar.sslr.impl.channel.RegexpChannelBuilder.regexp; - -// This is a at-best lexer. -// It is far from being entirely matching the standard definition of css/less/scss tokens nor -// following the theory of what a lexer responsibilities are but as we are only building line metrics and highlighting -// on top of it we decided to focus on simplicity over being extensive. - -// Be careful to avoid/limit usage of backtracking regex. There is nearly always an alternative with a forward lookup. -// This will allow to improve performance and avoid a lof of StackOverflowException. -public final class CssLexer { - - private static final String NEW_LINE = "(?:\r\n|\r|\n|\f)"; - private static final String WHITESPACE = "[\t\n\f\r ]"; - private static final String NON_ASCII = "[^\\p{ASCII}]"; - private static final String HEX_DIGIT = "0-9a-fA-F"; - private static final String ESCAPE = "(?:\\\\[" + HEX_DIGIT + "]{1,6}" + WHITESPACE + "?)|\\[^\r\n\f" + HEX_DIGIT + "]"; - - private static final String PUNCTUATOR = "[!:,;%&+#\\*-/=>\\(\\)\\[\\]\\{\\}]"; - - // Use dotall mode (https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#DOTALL) to match line return - // while using . - private static final String MULTI_LINE_COMMENT = "(?s)/\\*.*?\\*/"; - private static final String INLINE_COMMENT = "//[^\n\r\f]*+"; - - private static final String NUMBER = "[+|-]?+(?:\\d++(?:\\.\\d++)?+|\\.\\d++)(?:[a-z]++|%)?+"; - - private static final String NAME_CHAR = "[a-zA-Z0-9_-]|" + NON_ASCII + "|" + ESCAPE; - private static final String NAME_START = "[a-zA-Z_]|" + NON_ASCII + "|" + ESCAPE; - - private static final String IDENTIFIER = "-?+(?:" + NAME_START + ")(?:" + NAME_CHAR + ")*+"; - private static final String AT_IDENTIFIER = "@++" + IDENTIFIER; - private static final String HASH_IDENTIFIER = "#(?:" + NAME_CHAR + ")++"; - private static final String DOLLAR_IDENTIFIER = "\\$(?:" + NAME_CHAR + ")++"; - - private static final String DOUBLE_QUOTE_STRING = "~?+\"(?:[^\"\\\\\r\n\f]|" + ESCAPE + "|\\\\" + NEW_LINE + ")*+\""; - private static final String SINGLE_QUOTE_STRING = "~?+'(?:[^'\\\\\r\n\f]|" + ESCAPE + "|\\\\" + NEW_LINE + ")*+'"; - - private CssLexer() { - } - - public static Lexer create() { - return Lexer.builder() - .withFailIfNoChannelToConsumeOneCharacter(false) - - .withChannel(regexp(CssTokenType.COMMENT, MULTI_LINE_COMMENT)) - .withChannel(regexp(CssTokenType.COMMENT, INLINE_COMMENT)) - .withChannel(regexp(CssTokenType.STRING, DOUBLE_QUOTE_STRING)) - .withChannel(regexp(CssTokenType.STRING, SINGLE_QUOTE_STRING)) - .withChannel(regexp(CssTokenType.AT_IDENTIFIER, AT_IDENTIFIER)) - .withChannel(regexp(CssTokenType.HASH_IDENTIFIER, HASH_IDENTIFIER)) - .withChannel(regexp(CssTokenType.DOLLAR_IDENTIFIER, DOLLAR_IDENTIFIER)) - .withChannel(regexp(CssTokenType.IDENTIFIER, IDENTIFIER)) - .withChannel(regexp(CssTokenType.NUMBER, NUMBER)) - .withChannel(regexp(CssTokenType.PUNCTUATOR, PUNCTUATOR)) - - .build(); - } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java index a5df2fc..1e83cd2 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java @@ -22,7 +22,8 @@ package org.sonar.css.plugin; import org.sonar.api.Plugin; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.resources.Qualifiers; -import org.sonar.css.plugin.bundle.CssAnalyzerBundle; +import org.sonar.css.plugin.server.bundle.CssAnalyzerBundle; +import org.sonar.css.plugin.metrics.MetricSensor; import org.sonar.css.plugin.server.CssAnalyzerBridgeServer; public class CssPlugin implements Plugin { diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssToken.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssToken.java deleted file mode 100644 index c615dcc..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssToken.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import com.sonar.sslr.api.Token; -import com.sonar.sslr.api.TokenType; -import org.sonarsource.analyzer.commons.TokenLocation; - -public class CssToken { - CssTokenType type; - String text; - Integer startLine; - Integer startColumn; - Integer endLine; - Integer endColumn; - - public CssToken(Token token) { - TokenType tokenType = token.getType(); - this.type = (CssTokenType)tokenType; - this.text = token.getValue(); - - TokenLocation tokenLocation = new TokenLocation(token.getLine(), token.getColumn(), token.getValue()); - this.startLine = tokenLocation.startLine(); - this.startColumn = tokenLocation.startLineOffset(); - this.endLine = tokenLocation.endLine(); - this.endColumn = tokenLocation.endLineOffset(); - } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssTokenType.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssTokenType.java deleted file mode 100644 index 7525470..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssTokenType.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.TokenType; - -public enum CssTokenType implements TokenType { - COMMENT, - PUNCTUATOR, - NUMBER, - STRING, - AT_IDENTIFIER, - HASH_IDENTIFIER, - DOLLAR_IDENTIFIER, - IDENTIFIER; - - @Override - public String getName() { - return name(); - } - - @Override - public String getValue() { - return name(); - } - - @Override - public boolean hasToBeSkippedFromAst(AstNode node) { - return false; - } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/MetricSensor.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/MetricSensor.java deleted file mode 100644 index 4c0b026..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/MetricSensor.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.sensor.Sensor; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.batch.sensor.SensorDescriptor; -import org.sonar.api.batch.sensor.highlighting.NewHighlighting; -import org.sonar.api.batch.sensor.highlighting.TypeOfText; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.FileLinesContext; -import org.sonar.api.measures.FileLinesContextFactory; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -public class MetricSensor implements Sensor { - - private static final Logger LOG = Loggers.get(MetricSensor.class); - - private final FileLinesContextFactory fileLinesContextFactory; - - public MetricSensor(FileLinesContextFactory fileLinesContextFactory) { - this.fileLinesContextFactory = fileLinesContextFactory; - } - - @Override - public void describe(SensorDescriptor descriptor) { - descriptor - .name("SonarCSS Metrics") - .onlyOnLanguage(CssLanguage.KEY); - } - - @Override - public void execute(SensorContext context) { - FileSystem fileSystem = context.fileSystem(); - Iterable inputFiles = fileSystem.inputFiles(fileSystem.predicates().hasLanguage(CssLanguage.KEY)); - - Tokenizer tokenizer = new Tokenizer(); - - for (InputFile file : inputFiles) { - try { - List tokenList = tokenizer.tokenize(file.contents()); - - saveHighlights(context, file, tokenList); - saveLineTypes(context, file, tokenList); - - } catch (IOException e) { - LOG.error(String.format("Failed to read file '%s'", file.toString()), e); - } - } - } - - private static void saveHighlights(SensorContext context, InputFile file, List tokenList) { - NewHighlighting highlighting = context.newHighlighting().onFile(file); - - for (int i = 0; i < tokenList.size(); i++) { - CssToken currentToken = tokenList.get(i); - CssToken nextToken = i + 1 < tokenList.size() ? tokenList.get(i + 1) : null; - - TypeOfText highlightingType = null; - switch (currentToken.type) { - case COMMENT: - highlightingType = TypeOfText.COMMENT; - break; - - case STRING: - highlightingType = TypeOfText.STRING; - break; - - case NUMBER: - highlightingType = TypeOfText.CONSTANT; - break; - - case AT_IDENTIFIER: - highlightingType = TypeOfText.ANNOTATION; - break; - - case DOLLAR_IDENTIFIER: - highlightingType = TypeOfText.KEYWORD; - break; - - case HASH_IDENTIFIER: - if (currentToken.text.matches("^#[0-9a-fA-F]+$")) { - highlightingType = TypeOfText.CONSTANT; - } else { - highlightingType = TypeOfText.KEYWORD; - } - break; - - case IDENTIFIER: - // We want to highlight the property key of a css/scss/less file and as the tokenizer is putting the ':' into another token - // we need to look for identifier followed by a PUNCTUATOR token with text ':'. - if (nextToken != null && nextToken.text.equals(":")) { - highlightingType = TypeOfText.KEYWORD_LIGHT; - } - break; - - default: - highlightingType = null; - } - - if (highlightingType != null) { - highlighting.highlight(currentToken.startLine, currentToken.startColumn, currentToken.endLine, currentToken.endColumn, highlightingType); - } - } - - highlighting.save(); - } - - private void saveLineTypes(SensorContext context, InputFile file, List tokenList) { - // collect line types - Set linesOfCode = new HashSet<>(); - Set linesOfComment = new HashSet<>(); - - for (CssToken token : tokenList) { - for (int line = token.startLine; line <= token.endLine; line++) { - if (token.type.equals(CssTokenType.COMMENT)) { - linesOfComment.add(line); - } else { - linesOfCode.add(line); - } - } - } - - context.newMeasure().on(file).forMetric(CoreMetrics.NCLOC).withValue(linesOfCode.size()).save(); - context.newMeasure().on(file).forMetric(CoreMetrics.COMMENT_LINES).withValue(linesOfComment.size()).save(); - - FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(file); - linesOfCode.forEach(line -> fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1)); - fileLinesContext.save(); - } - -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Tokenizer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Tokenizer.java deleted file mode 100644 index 179d2d8..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Tokenizer.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import com.sonar.sslr.api.Token; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class Tokenizer { - - public List tokenize(String css) { - List tokenList = CssLexer.create().lex(css); - - // remove last token (EOF token) - List cloneTokenList = new ArrayList<>(tokenList); - cloneTokenList.remove(cloneTokenList.size() - 1); - - return cloneTokenList.stream().map(CssToken::new).collect(Collectors.toList()); - } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Zip.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Zip.java deleted file mode 100644 index 4a72aa7..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/Zip.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apache.commons.io.FileUtils; - -public class Zip { - - private Zip() { - // utility class - } - - public static void extract(InputStream bundle, File destination) throws IOException { - ZipInputStream zip = new ZipInputStream(bundle); - ZipEntry entry = zip.getNextEntry(); - if (entry == null) { - throw new IllegalStateException("At least one entry expected."); - } - while (entry != null) { - File entryDestination = new File(destination, entry.getName()); - if (entry.isDirectory()) { - entryDestination.mkdirs(); - } else { - FileUtils.copyToFile(zip, entryDestination); - } - zip.closeEntry(); - entry = zip.getNextEntry(); - } - } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/Bundle.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/Bundle.java deleted file mode 100644 index 8e9ce9b..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/Bundle.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin.bundle; - -import java.io.IOException; -import java.nio.file.Path; - -public interface Bundle { - - void deploy(Path deployLocation) throws IOException; - - /** - * should be called after deploy(Path deployLocation) - */ - String startServerScript(); - -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/CssAnalyzerBundle.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/CssAnalyzerBundle.java deleted file mode 100644 index ba5d714..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/CssAnalyzerBundle.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin.bundle; - -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.sonar.api.internal.google.common.annotations.VisibleForTesting; -import org.sonar.api.scanner.ScannerSide; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; -import org.sonar.css.plugin.Zip; -import org.sonarsource.api.sonarlint.SonarLintSide; - -import static org.sonarsource.api.sonarlint.SonarLintSide.MULTIPLE_ANALYSES; - -@ScannerSide -@SonarLintSide(lifespan = MULTIPLE_ANALYSES) -public class CssAnalyzerBundle implements Bundle { - - private static final Logger LOG = Loggers.get(CssAnalyzerBundle.class); - private static final Profiler PROFILER = Profiler.createIfDebug(LOG); - - // this archive is created in css-bundle module - private static final String DEFAULT_BUNDLE_LOCATION = "/css-bundle.zip"; - private static final Path DEFAULT_STARTUP_SCRIPT = Paths.get("css-bundle", "bin", "server"); - - final String bundleLocation; - - private String startServerScript = DEFAULT_STARTUP_SCRIPT.toString(); - - public CssAnalyzerBundle() { - this(DEFAULT_BUNDLE_LOCATION); - } - - @VisibleForTesting - CssAnalyzerBundle(String bundleLocation) { - this.bundleLocation = bundleLocation; - } - - @Override - public void deploy(Path deployLocation) { - PROFILER.startDebug("Deploying bundle"); - LOG.debug("Deploying css-bundle into {}", deployLocation); - InputStream bundle = getClass().getResourceAsStream(bundleLocation); - if (bundle == null) { - throw new IllegalStateException("css-bundle not found in " + bundleLocation); - } - try { - LOG.debug("Deploying css-bundle to {}", deployLocation.toAbsolutePath()); - Zip.extract(bundle, deployLocation.toFile()); - startServerScript = deployLocation.resolve(DEFAULT_STARTUP_SCRIPT).toAbsolutePath().toString(); - } catch (Exception e) { - throw new IllegalStateException("Failed to deploy css-bundle (with classpath '" + bundleLocation + "')", e); - } - PROFILER.stopDebug(); - } - - @Override - public String startServerScript() { - return startServerScript; - } - -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/package-info.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/package-info.java deleted file mode 100644 index b4344f7..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/bundle/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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. - */ -@javax.annotation.ParametersAreNonnullByDefault -package org.sonar.css.plugin.bundle; diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssLexer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssLexer.java new file mode 100644 index 0000000..67e026a --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssLexer.java @@ -0,0 +1,81 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import com.sonar.sslr.impl.Lexer; + +import static com.sonar.sslr.impl.channel.RegexpChannelBuilder.regexp; + +// This is a at-best lexer. +// It is far from being entirely matching the standard definition of css/less/scss tokens nor +// following the theory of what a lexer responsibilities are but as we are only building line metrics and highlighting +// on top of it we decided to focus on simplicity over being extensive. + +// Be careful to avoid/limit usage of backtracking regex. There is nearly always an alternative with a forward lookup. +// This will allow to improve performance and avoid a lof of StackOverflowException. +public final class CssLexer { + + private static final String NEW_LINE = "(?:\r\n|\r|\n|\f)"; + private static final String WHITESPACE = "[\t\n\f\r ]"; + private static final String NON_ASCII = "[^\\p{ASCII}]"; + private static final String HEX_DIGIT = "0-9a-fA-F"; + private static final String ESCAPE = "(?:\\\\[" + HEX_DIGIT + "]{1,6}" + WHITESPACE + "?)|\\[^\r\n\f" + HEX_DIGIT + "]"; + + private static final String PUNCTUATOR = "[!:,;%&+#\\*-/=>\\(\\)\\[\\]\\{\\}]"; + + // Use dotall mode (https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#DOTALL) to match line return + // while using . + private static final String MULTI_LINE_COMMENT = "(?s)/\\*.*?\\*/"; + private static final String INLINE_COMMENT = "//[^\n\r\f]*+"; + + private static final String NUMBER = "[+|-]?+(?:\\d++(?:\\.\\d++)?+|\\.\\d++)(?:[a-z]++|%)?+"; + + private static final String NAME_CHAR = "[a-zA-Z0-9_-]|" + NON_ASCII + "|" + ESCAPE; + private static final String NAME_START = "[a-zA-Z_]|" + NON_ASCII + "|" + ESCAPE; + + private static final String IDENTIFIER = "-?+(?:" + NAME_START + ")(?:" + NAME_CHAR + ")*+"; + private static final String AT_IDENTIFIER = "@++" + IDENTIFIER; + private static final String HASH_IDENTIFIER = "#(?:" + NAME_CHAR + ")++"; + private static final String DOLLAR_IDENTIFIER = "\\$(?:" + NAME_CHAR + ")++"; + + private static final String DOUBLE_QUOTE_STRING = "~?+\"(?:[^\"\\\\\r\n\f]|" + ESCAPE + "|\\\\" + NEW_LINE + ")*+\""; + private static final String SINGLE_QUOTE_STRING = "~?+'(?:[^'\\\\\r\n\f]|" + ESCAPE + "|\\\\" + NEW_LINE + ")*+'"; + + private CssLexer() { + } + + public static Lexer create() { + return Lexer.builder() + .withFailIfNoChannelToConsumeOneCharacter(false) + + .withChannel(regexp(CssTokenType.COMMENT, MULTI_LINE_COMMENT)) + .withChannel(regexp(CssTokenType.COMMENT, INLINE_COMMENT)) + .withChannel(regexp(CssTokenType.STRING, DOUBLE_QUOTE_STRING)) + .withChannel(regexp(CssTokenType.STRING, SINGLE_QUOTE_STRING)) + .withChannel(regexp(CssTokenType.AT_IDENTIFIER, AT_IDENTIFIER)) + .withChannel(regexp(CssTokenType.HASH_IDENTIFIER, HASH_IDENTIFIER)) + .withChannel(regexp(CssTokenType.DOLLAR_IDENTIFIER, DOLLAR_IDENTIFIER)) + .withChannel(regexp(CssTokenType.IDENTIFIER, IDENTIFIER)) + .withChannel(regexp(CssTokenType.NUMBER, NUMBER)) + .withChannel(regexp(CssTokenType.PUNCTUATOR, PUNCTUATOR)) + + .build(); + } +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssToken.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssToken.java new file mode 100644 index 0000000..f72830e --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssToken.java @@ -0,0 +1,45 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.TokenType; +import org.sonarsource.analyzer.commons.TokenLocation; + +public class CssToken { + CssTokenType type; + String text; + Integer startLine; + Integer startColumn; + Integer endLine; + Integer endColumn; + + public CssToken(Token token) { + TokenType tokenType = token.getType(); + this.type = (CssTokenType)tokenType; + this.text = token.getValue(); + + TokenLocation tokenLocation = new TokenLocation(token.getLine(), token.getColumn(), token.getValue()); + this.startLine = tokenLocation.startLine(); + this.startColumn = tokenLocation.startLineOffset(); + this.endLine = tokenLocation.endLine(); + this.endColumn = tokenLocation.endLineOffset(); + } +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssTokenType.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssTokenType.java new file mode 100644 index 0000000..ba66647 --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/CssTokenType.java @@ -0,0 +1,49 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.TokenType; + +public enum CssTokenType implements TokenType { + COMMENT, + PUNCTUATOR, + NUMBER, + STRING, + AT_IDENTIFIER, + HASH_IDENTIFIER, + DOLLAR_IDENTIFIER, + IDENTIFIER; + + @Override + public String getName() { + return name(); + } + + @Override + public String getValue() { + return name(); + } + + @Override + public boolean hasToBeSkippedFromAst(AstNode node) { + return false; + } +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/MetricSensor.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/MetricSensor.java new file mode 100644 index 0000000..2c4a248 --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/MetricSensor.java @@ -0,0 +1,158 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.css.plugin.CssLanguage; + +public class MetricSensor implements Sensor { + + private static final Logger LOG = Loggers.get(MetricSensor.class); + + private final FileLinesContextFactory fileLinesContextFactory; + + public MetricSensor(FileLinesContextFactory fileLinesContextFactory) { + this.fileLinesContextFactory = fileLinesContextFactory; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("SonarCSS Metrics") + .onlyOnLanguage(CssLanguage.KEY); + } + + @Override + public void execute(SensorContext context) { + FileSystem fileSystem = context.fileSystem(); + Iterable inputFiles = fileSystem.inputFiles(fileSystem.predicates().hasLanguage(CssLanguage.KEY)); + + Tokenizer tokenizer = new Tokenizer(); + + for (InputFile file : inputFiles) { + try { + List tokenList = tokenizer.tokenize(file.contents()); + + saveHighlights(context, file, tokenList); + saveLineTypes(context, file, tokenList); + + } catch (IOException e) { + LOG.error(String.format("Failed to read file '%s'", file.toString()), e); + } + } + } + + private static void saveHighlights(SensorContext context, InputFile file, List tokenList) { + NewHighlighting highlighting = context.newHighlighting().onFile(file); + + for (int i = 0; i < tokenList.size(); i++) { + CssToken currentToken = tokenList.get(i); + CssToken nextToken = i + 1 < tokenList.size() ? tokenList.get(i + 1) : null; + + TypeOfText highlightingType = null; + switch (currentToken.type) { + case COMMENT: + highlightingType = TypeOfText.COMMENT; + break; + + case STRING: + highlightingType = TypeOfText.STRING; + break; + + case NUMBER: + highlightingType = TypeOfText.CONSTANT; + break; + + case AT_IDENTIFIER: + highlightingType = TypeOfText.ANNOTATION; + break; + + case DOLLAR_IDENTIFIER: + highlightingType = TypeOfText.KEYWORD; + break; + + case HASH_IDENTIFIER: + if (currentToken.text.matches("^#[0-9a-fA-F]+$")) { + highlightingType = TypeOfText.CONSTANT; + } else { + highlightingType = TypeOfText.KEYWORD; + } + break; + + case IDENTIFIER: + // We want to highlight the property key of a css/scss/less file and as the tokenizer is putting the ':' into another token + // we need to look for identifier followed by a PUNCTUATOR token with text ':'. + if (nextToken != null && nextToken.text.equals(":")) { + highlightingType = TypeOfText.KEYWORD_LIGHT; + } + break; + + default: + highlightingType = null; + } + + if (highlightingType != null) { + highlighting.highlight(currentToken.startLine, currentToken.startColumn, currentToken.endLine, currentToken.endColumn, highlightingType); + } + } + + highlighting.save(); + } + + private void saveLineTypes(SensorContext context, InputFile file, List tokenList) { + // collect line types + Set linesOfCode = new HashSet<>(); + Set linesOfComment = new HashSet<>(); + + for (CssToken token : tokenList) { + for (int line = token.startLine; line <= token.endLine; line++) { + if (token.type.equals(CssTokenType.COMMENT)) { + linesOfComment.add(line); + } else { + linesOfCode.add(line); + } + } + } + + context.newMeasure().on(file).forMetric(CoreMetrics.NCLOC).withValue(linesOfCode.size()).save(); + context.newMeasure().on(file).forMetric(CoreMetrics.COMMENT_LINES).withValue(linesOfComment.size()).save(); + + FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(file); + linesOfCode.forEach(line -> fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1)); + fileLinesContext.save(); + } + +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/Tokenizer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/Tokenizer.java new file mode 100644 index 0000000..451b2ee --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/metrics/Tokenizer.java @@ -0,0 +1,38 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import com.sonar.sslr.api.Token; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Tokenizer { + + public List tokenize(String css) { + List tokenList = CssLexer.create().lex(css); + + // remove last token (EOF token) + List cloneTokenList = new ArrayList<>(tokenList); + cloneTokenList.remove(cloneTokenList.size() - 1); + + return cloneTokenList.stream().map(CssToken::new).collect(Collectors.toList()); + } +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java index fb8ee45..05760f8 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java @@ -33,7 +33,7 @@ import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; -import org.sonar.css.plugin.bundle.Bundle; +import org.sonar.css.plugin.server.bundle.Bundle; import org.sonar.css.plugin.server.exception.ServerAlreadyFailedException; import org.sonarsource.nodejs.NodeCommand; import org.sonarsource.nodejs.NodeCommandBuilder; diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java new file mode 100644 index 0000000..513131f --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java @@ -0,0 +1,34 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.server.bundle; + +import java.io.IOException; +import java.nio.file.Path; + +public interface Bundle { + + void deploy(Path deployLocation) throws IOException; + + /** + * should be called after deploy(Path deployLocation) + */ + String startServerScript(); + +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundle.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundle.java new file mode 100644 index 0000000..1aae1da --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundle.java @@ -0,0 +1,81 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.server.bundle; + +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.sonar.api.internal.google.common.annotations.VisibleForTesting; +import org.sonar.api.scanner.ScannerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonarsource.api.sonarlint.SonarLintSide; + +import static org.sonarsource.api.sonarlint.SonarLintSide.MULTIPLE_ANALYSES; + +@ScannerSide +@SonarLintSide(lifespan = MULTIPLE_ANALYSES) +public class CssAnalyzerBundle implements Bundle { + + private static final Logger LOG = Loggers.get(CssAnalyzerBundle.class); + private static final Profiler PROFILER = Profiler.createIfDebug(LOG); + + // this archive is created in css-bundle module + private static final String DEFAULT_BUNDLE_LOCATION = "/css-bundle.zip"; + private static final Path DEFAULT_STARTUP_SCRIPT = Paths.get("css-bundle", "bin", "server"); + + final String bundleLocation; + + private String startServerScript = DEFAULT_STARTUP_SCRIPT.toString(); + + public CssAnalyzerBundle() { + this(DEFAULT_BUNDLE_LOCATION); + } + + @VisibleForTesting + CssAnalyzerBundle(String bundleLocation) { + this.bundleLocation = bundleLocation; + } + + @Override + public void deploy(Path deployLocation) { + PROFILER.startDebug("Deploying bundle"); + LOG.debug("Deploying css-bundle into {}", deployLocation); + InputStream bundle = getClass().getResourceAsStream(bundleLocation); + if (bundle == null) { + throw new IllegalStateException("css-bundle not found in " + bundleLocation); + } + try { + LOG.debug("Deploying css-bundle to {}", deployLocation.toAbsolutePath()); + Zip.extract(bundle, deployLocation.toFile()); + startServerScript = deployLocation.resolve(DEFAULT_STARTUP_SCRIPT).toAbsolutePath().toString(); + } catch (Exception e) { + throw new IllegalStateException("Failed to deploy css-bundle (with classpath '" + bundleLocation + "')", e); + } + PROFILER.stopDebug(); + } + + @Override + public String startServerScript() { + return startServerScript; + } + +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Zip.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Zip.java new file mode 100644 index 0000000..9be150a --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Zip.java @@ -0,0 +1,52 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.server.bundle; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.commons.io.FileUtils; + +public class Zip { + + private Zip() { + // utility class + } + + public static void extract(InputStream bundle, File destination) throws IOException { + ZipInputStream zip = new ZipInputStream(bundle); + ZipEntry entry = zip.getNextEntry(); + if (entry == null) { + throw new IllegalStateException("At least one entry expected."); + } + while (entry != null) { + File entryDestination = new File(destination, entry.getName()); + if (entry.isDirectory()) { + entryDestination.mkdirs(); + } else { + FileUtils.copyToFile(zip, entryDestination); + } + zip.closeEntry(); + entry = zip.getNextEntry(); + } + } +} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/package-info.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/package-info.java new file mode 100644 index 0000000..d9ffe4d --- /dev/null +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.css.plugin.server.bundle; diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/MetricSensorTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/MetricSensorTest.java deleted file mode 100644 index ae66af1..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/MetricSensorTest.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.batch.fs.internal.TestInputFileBuilder; -import org.sonar.api.batch.sensor.highlighting.TypeOfText; -import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; -import org.sonar.api.batch.sensor.internal.SensorContextTester; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.FileLinesContext; -import org.sonar.api.measures.FileLinesContextFactory; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MetricSensorTest { - - private DefaultInputFile inputFile; - private SensorContextTester sensorContext; - - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Test - public void should_describe() { - DefaultSensorDescriptor desc = new DefaultSensorDescriptor(); - new MetricSensor(null).describe(desc); - - assertThat(desc.languages()).containsOnly("css"); - } - - @Test - public void empty_input() throws Exception { - executeSensor("foo"); - assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 0)).isEmpty(); - assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 1)).isEmpty(); - } - - @Test - public void comment() throws IOException { - executeSensor("/* some comment */"); - assertHighlighting(1, 0, 18, TypeOfText.COMMENT); - - executeSensor("/* some comment\nmultiline */"); - assertHighlighting(1, 0, 15, TypeOfText.COMMENT); - assertHighlighting(2, 0, 12, TypeOfText.COMMENT); - } - - @Test - public void string() throws IOException { - executeSensor("\"foo\""); - assertHighlighting(1, 0, 5, TypeOfText.STRING); - - executeSensor("\"foo\\\nbar\""); - assertHighlighting(1, 0, 4, TypeOfText.STRING); - assertHighlighting(2, 0, 4, TypeOfText.STRING); - } - - @Test - public void constant() throws IOException { - executeSensor("1"); - assertHighlighting(1, 0, 1, TypeOfText.CONSTANT); - - executeSensor("1.0"); - assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); - - executeSensor("0px"); - assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); - - executeSensor("1em"); - assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); - - executeSensor("#ddd"); - assertHighlighting(1, 0, 4, TypeOfText.CONSTANT); - } - - @Test - public void annotation() throws IOException { - executeSensor("@bar { }"); - assertHighlighting(1, 0, 4, TypeOfText.ANNOTATION); - - executeSensor("@my-selector: banner;"); - assertHighlighting(1, 0, 12, TypeOfText.ANNOTATION); - - executeSensor("@import \"src/themes\""); - assertHighlighting(1, 0, 7, TypeOfText.ANNOTATION); - - executeSensor(".element { color: @@color }"); - assertHighlighting(1, 18, 7, TypeOfText.ANNOTATION); - } - - @Test - public void keyword() throws IOException { - executeSensor("$foo { }"); - assertHighlighting(1, 0, 4, TypeOfText.KEYWORD); - - executeSensor("#header { .border-radius(4px); }"); - assertHighlighting(1, 0, 7, TypeOfText.KEYWORD); - } - - @Test - public void keyword_light() throws IOException { - executeSensor("bar: foo { }"); - assertHighlighting(1, 0, 3, TypeOfText.KEYWORD_LIGHT); - - executeSensor("bar { foo: 1px }"); - assertHighlighting(1, 6, 3, TypeOfText.KEYWORD_LIGHT); - - executeSensor("bar { foo-bar: 1px }"); - assertHighlighting(1, 6, 7, TypeOfText.KEYWORD_LIGHT); - } - - @Test - public void lines_of_code() throws IOException { - executeSensor("bar { }"); - assertLinesOfCode(1); - - executeSensor("bar\n{ }"); - assertLinesOfCode(2); - - // We don't count empty lines - executeSensor("\n\n\nsomething\n\n\n"); - assertLinesOfCode(1); - - // We don't count comments - executeSensor("// foo"); - assertLinesOfCode(0); - executeSensor("/* dasdsa */"); - assertLinesOfCode(0); - executeSensor("/* das\ndsa */"); - assertLinesOfCode(0); - - // Mix code and comment - executeSensor("foo {} // some comment"); - assertLinesOfCode(1); - } - - @Test - public void lines_of_comment() throws IOException { - executeSensor("// inline comment"); - assertLinesOfComment(1); - - executeSensor("/* single line comment */"); - assertLinesOfComment(1); - - executeSensor("/* multiline\n *\n *\n * comment\n*/"); - assertLinesOfComment(5); - - // We don't count empty lines - executeSensor("\n\n\n/* something */\n\n\n"); - assertLinesOfComment(1); - - // We don't count code - executeSensor("foo {}"); - assertLinesOfComment(0); - - // Mix code and comment - executeSensor("foo {} // some comment"); - assertLinesOfComment(1); - } - - private void executeSensor(String content) throws IOException { - File file = tempFolder.newFile(); - inputFile = new TestInputFileBuilder("moduleKey", file.getName()) - .setLanguage("css") - .setContents(content) - .build(); - - sensorContext = SensorContextTester.create(tempFolder.getRoot()); - sensorContext.fileSystem().add(inputFile); - - FileLinesContext linesContext = mock(FileLinesContext.class); - FileLinesContextFactory linesContextFactory = mock(FileLinesContextFactory.class); - when(linesContextFactory.createFor(inputFile)).thenReturn(linesContext); - new MetricSensor(linesContextFactory).execute(sensorContext); - } - - private void assertHighlighting(int line, int column, int length, TypeOfText type) { - for (int i = column; i < column + length; i++) { - List typeOfTexts = sensorContext.highlightingTypeAt(inputFile.key(), line, i); - assertThat(typeOfTexts).containsOnly(type); - } - } - - private void assertLinesOfCode(int expected) { - assertThat(sensorContext.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(expected); - } - - private void assertLinesOfComment(int expected) { - assertThat(sensorContext.measure(inputFile.key(), CoreMetrics.COMMENT_LINES).value()).isEqualTo(expected); - } -} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/TokenizerTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/TokenizerTest.java deleted file mode 100644 index 100bcb7..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/TokenizerTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin; - -import java.util.List; -import org.apache.commons.lang.StringUtils; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TokenizerTest { - - private final static Tokenizer tokenizer = new Tokenizer(); - - @Test - public void identifier() { - assertToken("bar { }", 0, "bar", CssTokenType.IDENTIFIER); - assertToken("bar: foo { }", 0, "bar", CssTokenType.IDENTIFIER); - assertToken("bar: foo-baz { }", 2, "foo-baz", CssTokenType.IDENTIFIER); - assertToken("foo bar { }", 1, "bar", CssTokenType.IDENTIFIER); - assertToken("foo.bar { }", 0, "foo", CssTokenType.IDENTIFIER); - assertToken(".bar { }", 1, "bar", CssTokenType.IDENTIFIER); - assertToken("bar { foo: 42; }", 2, "foo", CssTokenType.IDENTIFIER); - assertToken("bar { foo: baz; }", 4, "baz", CssTokenType.IDENTIFIER); - assertToken("foo , bar { }", 2, "bar", CssTokenType.IDENTIFIER); - - // support unicode characters - assertToken("\u03A9 { }", 0, "\u03A9", CssTokenType.IDENTIFIER); - } - - @Test - public void at_identifier() { - assertToken("@bar { }", 0, "@bar", CssTokenType.AT_IDENTIFIER); - } - - @Test - public void hash_identifier() { - assertToken("#bar { }", 0, "#bar", CssTokenType.HASH_IDENTIFIER); - assertToken("bar { color: #333; }", 4, "#333", CssTokenType.HASH_IDENTIFIER); - assertToken("bar { color: #e535ab; }", 4, "#e535ab", CssTokenType.HASH_IDENTIFIER); - } - - @Test - public void semi_colon() { - assertToken("bar { foo; }", 3, ";", CssTokenType.PUNCTUATOR); - } - - @Test - public void colon() { - assertToken("bar { foo: 2px; }", 3, ":", CssTokenType.PUNCTUATOR); - } - - @Test - public void comma() { - assertToken("foo , bar { }", 1, ",", CssTokenType.PUNCTUATOR); - assertToken("foo, bar { }", 1, ",", CssTokenType.PUNCTUATOR); - } - - @Test - public void number() { - assertToken("1.15", 0, "1.15", CssTokenType.NUMBER); - assertToken("1", 0, "1", CssTokenType.NUMBER); - assertToken(".1", 0, ".1", CssTokenType.NUMBER); - assertToken("1.15px", 0, "1.15px", CssTokenType.NUMBER); - assertToken("1.15%", 0, "1.15%", CssTokenType.NUMBER); - assertToken("1px", 0, "1px", CssTokenType.NUMBER); - assertToken("1em/150%", 0, "1em", CssTokenType.NUMBER); - assertToken("1T1", 0, "1", CssTokenType.NUMBER); - } - - @Test - public void parenthesis() { - assertToken("bar { foo: (1.15); }", 4, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { foo: (1.15); }", 6, ")", CssTokenType.PUNCTUATOR); - assertToken("bar { foo: ( 1.15 ); }", 4, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { foo: (1.15 1 0px); }", 4, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { foo: (1.15, 1, 0px); }", 4, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { content: string(doctitle); }", 5, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { string-set: booktitle content(); }", 6, "(", CssTokenType.PUNCTUATOR); - assertToken("bar { a: b(attr(href, url), c) \")\"; }", 7, "(", CssTokenType.PUNCTUATOR); - } - - @Test - public void strings() { - assertToken("bar { foo: \"text\"; }", 4, "\"text\"", CssTokenType.STRING); - assertToken("bar { foo: \"hello, world\"; }", 4, "\"hello, world\"", CssTokenType.STRING); - assertToken("bar { foo: \"\"; }", 4, "\"\"", CssTokenType.STRING); - assertToken("\"foo\\\nbar\"", 0, "\"foo\\\nbar\"", CssTokenType.STRING); - assertToken("@min768: ~\"(min-width: 768px)\"", 2, "~\"(min-width: 768px)\"", CssTokenType.STRING); - - int numberOfChars = 1000000; - String seedCode = StringUtils.repeat("a", numberOfChars); - - String testCode = "\"" + seedCode + "\""; - assertToken(testCode, 0, testCode, CssTokenType.STRING, 1, 0, 1, testCode.length()); - testCode = "'" + seedCode + "'"; - assertToken(testCode, 0, testCode, CssTokenType.STRING, 1, 0, 1, testCode.length()); - } - - @Test - public void comment() { - assertToken("/* foo */", 0, "/* foo */", CssTokenType.COMMENT); - assertToken("foo { a: /* foo */ 42; }", 4, "/* foo */", CssTokenType.COMMENT); - assertToken("foo { a: /* foo\nbar*/ 42; }", 4, "/* foo\nbar*/", CssTokenType.COMMENT, 1, 9, 2, 5); - assertToken("foo { a: /* foo\r\nbar*/ 42; }", 4, "/* foo\r\nbar*/", CssTokenType.COMMENT, 1, 9, 2, 5); - assertToken("foo { a: /* foo\fbar*/ 42; }", 4, "/* foo\fbar*/", CssTokenType.COMMENT, 1, 9, 1, 21); - String code = "/* \n" - + " this is a comment\n" - + " and it is awesome because\n" - + " it is multiline!\n" - + "*/"; - assertToken(code, 0, code, CssTokenType.COMMENT, 1, 0, 5, 2); - - int numberOfLineReturn = 1000000; - code = "/*" + StringUtils.repeat(" *\n", numberOfLineReturn) + " */"; - assertToken(code, 0, code, CssTokenType.COMMENT, 1, 0, numberOfLineReturn + 1, 3); - } - - @Test - public void scss_variable() { - assertToken("$font-stack: Helvetica;", 0, "$font-stack", CssTokenType.DOLLAR_IDENTIFIER); - assertToken("$message-color: blue !default;", 4, "default", CssTokenType.IDENTIFIER); - - List tokenList = tokenizer.tokenize("p.message-#{$alertClass} { color: red; }"); - assertThat(tokenList.size()).isEqualTo(13); - assertToken(tokenList, 0, "p", CssTokenType.IDENTIFIER); - assertToken(tokenList, 1, ".", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 2, "message-", CssTokenType.IDENTIFIER); - assertToken(tokenList, 3, "#", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 4, "{", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 5, "$alertClass", CssTokenType.DOLLAR_IDENTIFIER); - assertToken(tokenList, 6, "}", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 7, "{", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 8, "color", CssTokenType.IDENTIFIER); - assertToken(tokenList, 9, ":", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 10, "red", CssTokenType.IDENTIFIER); - assertToken(tokenList, 11, ";", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 12, "}", CssTokenType.PUNCTUATOR); - } - - @Test - public void scss_import() { - List tokenList = tokenizer.tokenize("@import 'base';"); - - assertThat(tokenList.size()).isEqualTo(3); - assertToken(tokenList, 0, "@import", CssTokenType.AT_IDENTIFIER); - assertToken(tokenList, 1, "'base'", CssTokenType.STRING); - assertToken(tokenList, 2, ";", CssTokenType.PUNCTUATOR); - } - - @Test - public void scss_role() { - List tokenList = tokenizer.tokenize("article[role=\"main\"] { width: 1px; }"); - - assertThat(tokenList.size()).isEqualTo(12); - assertToken(tokenList, 0, "article", CssTokenType.IDENTIFIER); - assertToken(tokenList, 1, "[", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 2, "role", CssTokenType.IDENTIFIER); - assertToken(tokenList, 3, "=", CssTokenType.PUNCTUATOR); - assertToken(tokenList, 4, "\"main\"", CssTokenType.STRING); - assertToken(tokenList, 5, "]", CssTokenType.PUNCTUATOR); - } - - @Test - public void scss_less_operators() { - assertToken("foo { width: 300px + 960px; }", 5, "+", CssTokenType.PUNCTUATOR); - assertToken("foo { width: 300px - 960px; }", 5, "-", CssTokenType.PUNCTUATOR); - assertToken("foo { width: 300px * 960px; }", 5, "*", CssTokenType.PUNCTUATOR); - assertToken("foo { width: 300px / 960px; }", 5, "/", CssTokenType.PUNCTUATOR); - } - - @Test - public void scss_parent_selector() { - assertToken("a { &:hover { color: red; } }", 2, "&", CssTokenType.PUNCTUATOR); - assertToken("p { body.no-touch & { display: none; } }", 5, "&", CssTokenType.PUNCTUATOR); - } - - @Test - public void scss_control_directives() { - assertToken("@if ($debug) { }", 0, "@if", CssTokenType.AT_IDENTIFIER); - assertToken("@each $name in 'save' 'cancel' { }", 0, "@each", CssTokenType.AT_IDENTIFIER); - } - - @Test - public void less_variable() { - assertToken("@nice-blue: #5B83AD;", 0, "@nice-blue", CssTokenType.AT_IDENTIFIER); - assertToken("foo { color: @@color; }", 4, "@@color", CssTokenType.AT_IDENTIFIER); - } - - @Test - public void less_comment() { - assertToken("// Get in line!", 0, "// Get in line!", CssTokenType.COMMENT); - assertToken("// body font size = 62.5%\n\n/* some comment */", 0, "// body font size = 62.5%", CssTokenType.COMMENT); - assertToken("/* One heck of a block\n * style comment! */", 0, "/* One heck of a block\n * style comment! */", CssTokenType.COMMENT); - } - - @Test - public void unrecognized() { - assertToken("$$a", 0, "$a", CssTokenType.DOLLAR_IDENTIFIER); - } - - private static void assertToken(String input, int index, String value, CssTokenType CssTokenType) { - List tokenList = tokenizer.tokenize(input); - assertToken(tokenList, index, value, CssTokenType); - } - - private static void assertToken(String input, int index, String value, CssTokenType CssTokenType, int line, int column, int endLine, int endColumn) { - List tokenList = tokenizer.tokenize(input); - assertToken(tokenList, index, value, CssTokenType, line, column, endLine, endColumn); - } - - private static void assertToken(List tokenList, int index, String value, CssTokenType CssTokenType) { - assertThat(tokenList.get(index).type).isEqualTo(CssTokenType); - assertThat(tokenList.get(index).text).isEqualTo(value); - } - - private static void assertToken(List tokenList, int index, String value, CssTokenType CssTokenType, int line, int column, int endLine, int endColumn) { - assertToken(tokenList, index, value, CssTokenType); - assertThat(tokenList.get(index).startLine).isEqualTo(line); - assertThat(tokenList.get(index).startColumn).isEqualTo(column); - assertThat(tokenList.get(index).endLine).isEqualTo(endLine); - assertThat(tokenList.get(index).endColumn).isEqualTo(endColumn); - } -} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java deleted file mode 100644 index dadb16c..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program 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. - * - * This program 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.css.plugin.bundle; - -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.internal.JUnitTempFolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class CssAnalyzerBundleTest { - - @Rule - public JUnitTempFolder tempFolder = new JUnitTempFolder(); - - @Test - public void default_css_bundle_location() throws Exception { - CssAnalyzerBundle bundle = new CssAnalyzerBundle(); - assertThat(bundle.bundleLocation).isEqualTo("/css-bundle.zip"); - } - - @Test - public void almost_empty_css_bundle() throws Exception { - Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); - Path deployLocation = tempFolder.newDir().toPath(); - String expectedStartServer = deployLocation.resolve(Paths.get("css-bundle", "bin", "server")).toString(); - bundle.deploy(deployLocation); - String script = bundle.startServerScript(); - assertThat(script).isEqualTo(expectedStartServer); - File scriptFile = new File(script); - assertThat(scriptFile).exists(); - String content = new String(Files.readAllBytes(scriptFile.toPath()), StandardCharsets.UTF_8); - assertThat(content).startsWith("#!/usr/bin/env node"); - } - - @Test - public void missing_bundle() throws Exception { - Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-bundle-path.zip"); - assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("css-bundle not found in /bundle/invalid-bundle-path.zip"); - } - - @Test - public void invalid_bundle_zip() throws Exception { - Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-zip-file.zip"); - assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to deploy css-bundle (with classpath '/bundle/invalid-zip-file.zip')"); - } - - @Test - public void should_not_fail_when_deployed_twice() throws Exception { - Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); - Path deployLocation = tempFolder.newDir().toPath(); - assertThatCode(() -> { - bundle.deploy(deployLocation); - bundle.deploy(deployLocation); - }).doesNotThrowAnyException(); - } -} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/MetricSensorTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/MetricSensorTest.java new file mode 100644 index 0000000..14b2a16 --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/MetricSensorTest.java @@ -0,0 +1,218 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.css.plugin.metrics.MetricSensor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MetricSensorTest { + + private DefaultInputFile inputFile; + private SensorContextTester sensorContext; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void should_describe() { + DefaultSensorDescriptor desc = new DefaultSensorDescriptor(); + new MetricSensor(null).describe(desc); + + assertThat(desc.languages()).containsOnly("css"); + } + + @Test + public void empty_input() throws Exception { + executeSensor("foo"); + assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 0)).isEmpty(); + assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 1)).isEmpty(); + } + + @Test + public void comment() throws IOException { + executeSensor("/* some comment */"); + assertHighlighting(1, 0, 18, TypeOfText.COMMENT); + + executeSensor("/* some comment\nmultiline */"); + assertHighlighting(1, 0, 15, TypeOfText.COMMENT); + assertHighlighting(2, 0, 12, TypeOfText.COMMENT); + } + + @Test + public void string() throws IOException { + executeSensor("\"foo\""); + assertHighlighting(1, 0, 5, TypeOfText.STRING); + + executeSensor("\"foo\\\nbar\""); + assertHighlighting(1, 0, 4, TypeOfText.STRING); + assertHighlighting(2, 0, 4, TypeOfText.STRING); + } + + @Test + public void constant() throws IOException { + executeSensor("1"); + assertHighlighting(1, 0, 1, TypeOfText.CONSTANT); + + executeSensor("1.0"); + assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); + + executeSensor("0px"); + assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); + + executeSensor("1em"); + assertHighlighting(1, 0, 3, TypeOfText.CONSTANT); + + executeSensor("#ddd"); + assertHighlighting(1, 0, 4, TypeOfText.CONSTANT); + } + + @Test + public void annotation() throws IOException { + executeSensor("@bar { }"); + assertHighlighting(1, 0, 4, TypeOfText.ANNOTATION); + + executeSensor("@my-selector: banner;"); + assertHighlighting(1, 0, 12, TypeOfText.ANNOTATION); + + executeSensor("@import \"src/themes\""); + assertHighlighting(1, 0, 7, TypeOfText.ANNOTATION); + + executeSensor(".element { color: @@color }"); + assertHighlighting(1, 18, 7, TypeOfText.ANNOTATION); + } + + @Test + public void keyword() throws IOException { + executeSensor("$foo { }"); + assertHighlighting(1, 0, 4, TypeOfText.KEYWORD); + + executeSensor("#header { .border-radius(4px); }"); + assertHighlighting(1, 0, 7, TypeOfText.KEYWORD); + } + + @Test + public void keyword_light() throws IOException { + executeSensor("bar: foo { }"); + assertHighlighting(1, 0, 3, TypeOfText.KEYWORD_LIGHT); + + executeSensor("bar { foo: 1px }"); + assertHighlighting(1, 6, 3, TypeOfText.KEYWORD_LIGHT); + + executeSensor("bar { foo-bar: 1px }"); + assertHighlighting(1, 6, 7, TypeOfText.KEYWORD_LIGHT); + } + + @Test + public void lines_of_code() throws IOException { + executeSensor("bar { }"); + assertLinesOfCode(1); + + executeSensor("bar\n{ }"); + assertLinesOfCode(2); + + // We don't count empty lines + executeSensor("\n\n\nsomething\n\n\n"); + assertLinesOfCode(1); + + // We don't count comments + executeSensor("// foo"); + assertLinesOfCode(0); + executeSensor("/* dasdsa */"); + assertLinesOfCode(0); + executeSensor("/* das\ndsa */"); + assertLinesOfCode(0); + + // Mix code and comment + executeSensor("foo {} // some comment"); + assertLinesOfCode(1); + } + + @Test + public void lines_of_comment() throws IOException { + executeSensor("// inline comment"); + assertLinesOfComment(1); + + executeSensor("/* single line comment */"); + assertLinesOfComment(1); + + executeSensor("/* multiline\n *\n *\n * comment\n*/"); + assertLinesOfComment(5); + + // We don't count empty lines + executeSensor("\n\n\n/* something */\n\n\n"); + assertLinesOfComment(1); + + // We don't count code + executeSensor("foo {}"); + assertLinesOfComment(0); + + // Mix code and comment + executeSensor("foo {} // some comment"); + assertLinesOfComment(1); + } + + private void executeSensor(String content) throws IOException { + File file = tempFolder.newFile(); + inputFile = new TestInputFileBuilder("moduleKey", file.getName()) + .setLanguage("css") + .setContents(content) + .build(); + + sensorContext = SensorContextTester.create(tempFolder.getRoot()); + sensorContext.fileSystem().add(inputFile); + + FileLinesContext linesContext = mock(FileLinesContext.class); + FileLinesContextFactory linesContextFactory = mock(FileLinesContextFactory.class); + when(linesContextFactory.createFor(inputFile)).thenReturn(linesContext); + new MetricSensor(linesContextFactory).execute(sensorContext); + } + + private void assertHighlighting(int line, int column, int length, TypeOfText type) { + for (int i = column; i < column + length; i++) { + List typeOfTexts = sensorContext.highlightingTypeAt(inputFile.key(), line, i); + assertThat(typeOfTexts).containsOnly(type); + } + } + + private void assertLinesOfCode(int expected) { + assertThat(sensorContext.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(expected); + } + + private void assertLinesOfComment(int expected) { + assertThat(sensorContext.measure(inputFile.key(), CoreMetrics.COMMENT_LINES).value()).isEqualTo(expected); + } +} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/TokenizerTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/TokenizerTest.java new file mode 100644 index 0000000..cf2cdfd --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/metrics/TokenizerTest.java @@ -0,0 +1,241 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.metrics; + +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TokenizerTest { + + private final static Tokenizer tokenizer = new Tokenizer(); + + @Test + public void identifier() { + assertToken("bar { }", 0, "bar", CssTokenType.IDENTIFIER); + assertToken("bar: foo { }", 0, "bar", CssTokenType.IDENTIFIER); + assertToken("bar: foo-baz { }", 2, "foo-baz", CssTokenType.IDENTIFIER); + assertToken("foo bar { }", 1, "bar", CssTokenType.IDENTIFIER); + assertToken("foo.bar { }", 0, "foo", CssTokenType.IDENTIFIER); + assertToken(".bar { }", 1, "bar", CssTokenType.IDENTIFIER); + assertToken("bar { foo: 42; }", 2, "foo", CssTokenType.IDENTIFIER); + assertToken("bar { foo: baz; }", 4, "baz", CssTokenType.IDENTIFIER); + assertToken("foo , bar { }", 2, "bar", CssTokenType.IDENTIFIER); + + // support unicode characters + assertToken("\u03A9 { }", 0, "\u03A9", CssTokenType.IDENTIFIER); + } + + @Test + public void at_identifier() { + assertToken("@bar { }", 0, "@bar", CssTokenType.AT_IDENTIFIER); + } + + @Test + public void hash_identifier() { + assertToken("#bar { }", 0, "#bar", CssTokenType.HASH_IDENTIFIER); + assertToken("bar { color: #333; }", 4, "#333", CssTokenType.HASH_IDENTIFIER); + assertToken("bar { color: #e535ab; }", 4, "#e535ab", CssTokenType.HASH_IDENTIFIER); + } + + @Test + public void semi_colon() { + assertToken("bar { foo; }", 3, ";", CssTokenType.PUNCTUATOR); + } + + @Test + public void colon() { + assertToken("bar { foo: 2px; }", 3, ":", CssTokenType.PUNCTUATOR); + } + + @Test + public void comma() { + assertToken("foo , bar { }", 1, ",", CssTokenType.PUNCTUATOR); + assertToken("foo, bar { }", 1, ",", CssTokenType.PUNCTUATOR); + } + + @Test + public void number() { + assertToken("1.15", 0, "1.15", CssTokenType.NUMBER); + assertToken("1", 0, "1", CssTokenType.NUMBER); + assertToken(".1", 0, ".1", CssTokenType.NUMBER); + assertToken("1.15px", 0, "1.15px", CssTokenType.NUMBER); + assertToken("1.15%", 0, "1.15%", CssTokenType.NUMBER); + assertToken("1px", 0, "1px", CssTokenType.NUMBER); + assertToken("1em/150%", 0, "1em", CssTokenType.NUMBER); + assertToken("1T1", 0, "1", CssTokenType.NUMBER); + } + + @Test + public void parenthesis() { + assertToken("bar { foo: (1.15); }", 4, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { foo: (1.15); }", 6, ")", CssTokenType.PUNCTUATOR); + assertToken("bar { foo: ( 1.15 ); }", 4, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { foo: (1.15 1 0px); }", 4, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { foo: (1.15, 1, 0px); }", 4, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { content: string(doctitle); }", 5, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { string-set: booktitle content(); }", 6, "(", CssTokenType.PUNCTUATOR); + assertToken("bar { a: b(attr(href, url), c) \")\"; }", 7, "(", CssTokenType.PUNCTUATOR); + } + + @Test + public void strings() { + assertToken("bar { foo: \"text\"; }", 4, "\"text\"", CssTokenType.STRING); + assertToken("bar { foo: \"hello, world\"; }", 4, "\"hello, world\"", CssTokenType.STRING); + assertToken("bar { foo: \"\"; }", 4, "\"\"", CssTokenType.STRING); + assertToken("\"foo\\\nbar\"", 0, "\"foo\\\nbar\"", CssTokenType.STRING); + assertToken("@min768: ~\"(min-width: 768px)\"", 2, "~\"(min-width: 768px)\"", CssTokenType.STRING); + + int numberOfChars = 1000000; + String seedCode = StringUtils.repeat("a", numberOfChars); + + String testCode = "\"" + seedCode + "\""; + assertToken(testCode, 0, testCode, CssTokenType.STRING, 1, 0, 1, testCode.length()); + testCode = "'" + seedCode + "'"; + assertToken(testCode, 0, testCode, CssTokenType.STRING, 1, 0, 1, testCode.length()); + } + + @Test + public void comment() { + assertToken("/* foo */", 0, "/* foo */", CssTokenType.COMMENT); + assertToken("foo { a: /* foo */ 42; }", 4, "/* foo */", CssTokenType.COMMENT); + assertToken("foo { a: /* foo\nbar*/ 42; }", 4, "/* foo\nbar*/", CssTokenType.COMMENT, 1, 9, 2, 5); + assertToken("foo { a: /* foo\r\nbar*/ 42; }", 4, "/* foo\r\nbar*/", CssTokenType.COMMENT, 1, 9, 2, 5); + assertToken("foo { a: /* foo\fbar*/ 42; }", 4, "/* foo\fbar*/", CssTokenType.COMMENT, 1, 9, 1, 21); + String code = "/* \n" + + " this is a comment\n" + + " and it is awesome because\n" + + " it is multiline!\n" + + "*/"; + assertToken(code, 0, code, CssTokenType.COMMENT, 1, 0, 5, 2); + + int numberOfLineReturn = 1000000; + code = "/*" + StringUtils.repeat(" *\n", numberOfLineReturn) + " */"; + assertToken(code, 0, code, CssTokenType.COMMENT, 1, 0, numberOfLineReturn + 1, 3); + } + + @Test + public void scss_variable() { + assertToken("$font-stack: Helvetica;", 0, "$font-stack", CssTokenType.DOLLAR_IDENTIFIER); + assertToken("$message-color: blue !default;", 4, "default", CssTokenType.IDENTIFIER); + + List tokenList = tokenizer.tokenize("p.message-#{$alertClass} { color: red; }"); + assertThat(tokenList.size()).isEqualTo(13); + assertToken(tokenList, 0, "p", CssTokenType.IDENTIFIER); + assertToken(tokenList, 1, ".", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 2, "message-", CssTokenType.IDENTIFIER); + assertToken(tokenList, 3, "#", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 4, "{", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 5, "$alertClass", CssTokenType.DOLLAR_IDENTIFIER); + assertToken(tokenList, 6, "}", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 7, "{", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 8, "color", CssTokenType.IDENTIFIER); + assertToken(tokenList, 9, ":", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 10, "red", CssTokenType.IDENTIFIER); + assertToken(tokenList, 11, ";", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 12, "}", CssTokenType.PUNCTUATOR); + } + + @Test + public void scss_import() { + List tokenList = tokenizer.tokenize("@import 'base';"); + + assertThat(tokenList.size()).isEqualTo(3); + assertToken(tokenList, 0, "@import", CssTokenType.AT_IDENTIFIER); + assertToken(tokenList, 1, "'base'", CssTokenType.STRING); + assertToken(tokenList, 2, ";", CssTokenType.PUNCTUATOR); + } + + @Test + public void scss_role() { + List tokenList = tokenizer.tokenize("article[role=\"main\"] { width: 1px; }"); + + assertThat(tokenList.size()).isEqualTo(12); + assertToken(tokenList, 0, "article", CssTokenType.IDENTIFIER); + assertToken(tokenList, 1, "[", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 2, "role", CssTokenType.IDENTIFIER); + assertToken(tokenList, 3, "=", CssTokenType.PUNCTUATOR); + assertToken(tokenList, 4, "\"main\"", CssTokenType.STRING); + assertToken(tokenList, 5, "]", CssTokenType.PUNCTUATOR); + } + + @Test + public void scss_less_operators() { + assertToken("foo { width: 300px + 960px; }", 5, "+", CssTokenType.PUNCTUATOR); + assertToken("foo { width: 300px - 960px; }", 5, "-", CssTokenType.PUNCTUATOR); + assertToken("foo { width: 300px * 960px; }", 5, "*", CssTokenType.PUNCTUATOR); + assertToken("foo { width: 300px / 960px; }", 5, "/", CssTokenType.PUNCTUATOR); + } + + @Test + public void scss_parent_selector() { + assertToken("a { &:hover { color: red; } }", 2, "&", CssTokenType.PUNCTUATOR); + assertToken("p { body.no-touch & { display: none; } }", 5, "&", CssTokenType.PUNCTUATOR); + } + + @Test + public void scss_control_directives() { + assertToken("@if ($debug) { }", 0, "@if", CssTokenType.AT_IDENTIFIER); + assertToken("@each $name in 'save' 'cancel' { }", 0, "@each", CssTokenType.AT_IDENTIFIER); + } + + @Test + public void less_variable() { + assertToken("@nice-blue: #5B83AD;", 0, "@nice-blue", CssTokenType.AT_IDENTIFIER); + assertToken("foo { color: @@color; }", 4, "@@color", CssTokenType.AT_IDENTIFIER); + } + + @Test + public void less_comment() { + assertToken("// Get in line!", 0, "// Get in line!", CssTokenType.COMMENT); + assertToken("// body font size = 62.5%\n\n/* some comment */", 0, "// body font size = 62.5%", CssTokenType.COMMENT); + assertToken("/* One heck of a block\n * style comment! */", 0, "/* One heck of a block\n * style comment! */", CssTokenType.COMMENT); + } + + @Test + public void unrecognized() { + assertToken("$$a", 0, "$a", CssTokenType.DOLLAR_IDENTIFIER); + } + + private static void assertToken(String input, int index, String value, CssTokenType CssTokenType) { + List tokenList = tokenizer.tokenize(input); + assertToken(tokenList, index, value, CssTokenType); + } + + private static void assertToken(String input, int index, String value, CssTokenType CssTokenType, int line, int column, int endLine, int endColumn) { + List tokenList = tokenizer.tokenize(input); + assertToken(tokenList, index, value, CssTokenType, line, column, endLine, endColumn); + } + + private static void assertToken(List tokenList, int index, String value, CssTokenType CssTokenType) { + assertThat(tokenList.get(index).type).isEqualTo(CssTokenType); + assertThat(tokenList.get(index).text).isEqualTo(value); + } + + private static void assertToken(List tokenList, int index, String value, CssTokenType CssTokenType, int line, int column, int endLine, int endColumn) { + assertToken(tokenList, index, value, CssTokenType); + assertThat(tokenList.get(index).startLine).isEqualTo(line); + assertThat(tokenList.get(index).startColumn).isEqualTo(column); + assertThat(tokenList.get(index).endLine).isEqualTo(endLine); + assertThat(tokenList.get(index).endColumn).isEqualTo(endColumn); + } +} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java index 91c95af..99b14ff 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java @@ -30,7 +30,7 @@ import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.utils.internal.JUnitTempFolder; import org.sonar.api.utils.log.LogTester; -import org.sonar.css.plugin.bundle.Bundle; +import org.sonar.css.plugin.server.bundle.Bundle; import org.sonar.css.plugin.server.AnalyzerBridgeServer.Issue; import org.sonar.css.plugin.server.AnalyzerBridgeServer.Request; import org.sonar.css.plugin.server.exception.ServerAlreadyFailedException; diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundleTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundleTest.java new file mode 100644 index 0000000..07dcfe3 --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/bundle/CssAnalyzerBundleTest.java @@ -0,0 +1,85 @@ +/* + * SonarCSS + * Copyright (C) 2018-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.css.plugin.server.bundle; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.internal.JUnitTempFolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CssAnalyzerBundleTest { + + @Rule + public JUnitTempFolder tempFolder = new JUnitTempFolder(); + + @Test + public void default_css_bundle_location() throws Exception { + CssAnalyzerBundle bundle = new CssAnalyzerBundle(); + assertThat(bundle.bundleLocation).isEqualTo("/css-bundle.zip"); + } + + @Test + public void almost_empty_css_bundle() throws Exception { + Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); + Path deployLocation = tempFolder.newDir().toPath(); + String expectedStartServer = deployLocation.resolve(Paths.get("css-bundle", "bin", "server")).toString(); + bundle.deploy(deployLocation); + String script = bundle.startServerScript(); + assertThat(script).isEqualTo(expectedStartServer); + File scriptFile = new File(script); + assertThat(scriptFile).exists(); + String content = new String(Files.readAllBytes(scriptFile.toPath()), StandardCharsets.UTF_8); + assertThat(content).startsWith("#!/usr/bin/env node"); + } + + @Test + public void missing_bundle() throws Exception { + Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-bundle-path.zip"); + assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("css-bundle not found in /bundle/invalid-bundle-path.zip"); + } + + @Test + public void invalid_bundle_zip() throws Exception { + Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-zip-file.zip"); + assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Failed to deploy css-bundle (with classpath '/bundle/invalid-zip-file.zip')"); + } + + @Test + public void should_not_fail_when_deployed_twice() throws Exception { + Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); + Path deployLocation = tempFolder.newDir().toPath(); + assertThatCode(() -> { + bundle.deploy(deployLocation); + bundle.deploy(deployLocation); + }).doesNotThrowAnyException(); + } +} -- cgit v1.2.3