aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-css-plugin/src
diff options
context:
space:
mode:
authorAmaury Levé2018-06-21 11:33:42 +0200
committerElena Vilchik2018-06-21 11:33:42 +0200
commit913028fc4c913fec3bbc1800c00e526413040e01 (patch)
tree0d1b7cadc4d45d82a1f0e3c91e96670d4b813551 /sonar-css-plugin/src
parent6472431ad488158bfcf863a7b4a5655e1ecc55e8 (diff)
downloadsonar-css-913028fc4c913fec3bbc1800c00e526413040e01.tar.bz2
Add comment lines and lines of code metrics (#49)
Diffstat (limited to 'sonar-css-plugin/src')
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/MetricSensor.java142
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/MetricSensorTest.java109
2 files changed, 177 insertions, 74 deletions
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
index 6257b74..1c635c7 100644
--- 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
@@ -20,7 +20,10 @@
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;
@@ -28,6 +31,9 @@ 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;
@@ -35,10 +41,16 @@ 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
- .onlyOnLanguage(CssLanguage.KEY);
+ .onlyOnLanguage(CssLanguage.KEY);
}
@Override
@@ -48,72 +60,98 @@ public class MetricSensor implements Sensor {
Tokenizer tokenizer = new Tokenizer();
- for (InputFile input : inputFiles) {
- saveHighlights(context, input, tokenizer);
+ for (InputFile file : inputFiles) {
+ try {
+ List<CssToken> 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 sensorContext, InputFile input, Tokenizer tokenizer) {
- try {
- NewHighlighting highlighting = sensorContext.newHighlighting().onFile(input);
- List<CssToken> tokenList = tokenizer.tokenize(input.contents());
+ private static void saveHighlights(SensorContext context, InputFile file, List<CssToken> 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;
+ 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;
+ TypeOfText highlightingType = null;
+ switch (currentToken.type) {
+ case COMMENT:
+ highlightingType = TypeOfText.COMMENT;
+ break;
- case STRING:
- highlightingType = TypeOfText.STRING;
- break;
+ case STRING:
+ highlightingType = TypeOfText.STRING;
+ break;
- case NUMBER:
- highlightingType = TypeOfText.CONSTANT;
- break;
+ case NUMBER:
+ highlightingType = TypeOfText.CONSTANT;
+ break;
- case AT_IDENTIFIER:
- highlightingType = TypeOfText.ANNOTATION;
- break;
+ case AT_IDENTIFIER:
+ highlightingType = TypeOfText.ANNOTATION;
+ break;
- case DOLLAR_IDENTIFIER:
+ 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 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;
- }
+ }
+ 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);
- }
+ if (highlightingType != null) {
+ highlighting.highlight(currentToken.startLine, currentToken.startColumn, currentToken.endLine, currentToken.endColumn, highlightingType);
}
+ }
- highlighting.save();
+ highlighting.save();
+ }
- } catch (IOException e) {
- LOG.error(String.format("Failed to read file '%s'", input.toString()), e);
+ private void saveLineTypes(SensorContext context, InputFile file, List<CssToken> tokenList) {
+ // collect line types
+ Set<Integer> linesOfCode = new HashSet<>();
+ Set<Integer> 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.<Integer>newMeasure().on(file).forMetric(CoreMetrics.NCLOC).withValue(linesOfCode.size()).save();
+ context.<Integer>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));
+ linesOfComment.forEach(line -> fileLinesContext.setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, line, 1));
+ fileLinesContext.save();
}
}
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
index e8eb31b..7e49277 100644
--- 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
@@ -30,8 +30,13 @@ 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 {
@@ -44,93 +49,142 @@ public class MetricSensorTest {
@Test
public void should_describe() {
DefaultSensorDescriptor desc = new DefaultSensorDescriptor();
- new MetricSensor().describe(desc);
+ new MetricSensor(null).describe(desc);
assertThat(desc.languages()).containsOnly("css");
}
@Test
public void empty_input() throws Exception {
- highlight("foo");
+ executeSensor("foo");
assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 0)).isEmpty();
assertThat(sensorContext.highlightingTypeAt(inputFile.key(), 1, 1)).isEmpty();
}
@Test
public void comment() throws IOException {
- highlight("/* some comment */");
+ executeSensor("/* some comment */");
assertHighlighting(1, 0, 18, TypeOfText.COMMENT);
- highlight("/* some comment\nmultiline */");
+ executeSensor("/* some comment\nmultiline */");
assertHighlighting(1, 0, 15, TypeOfText.COMMENT);
assertHighlighting(2, 0, 12, TypeOfText.COMMENT);
}
@Test
public void string() throws IOException {
- highlight("\"foo\"");
+ executeSensor("\"foo\"");
assertHighlighting(1, 0, 5, TypeOfText.STRING);
- highlight("\"foo\\\nbar\"");
+ executeSensor("\"foo\\\nbar\"");
assertHighlighting(1, 0, 4, TypeOfText.STRING);
assertHighlighting(2, 0, 4, TypeOfText.STRING);
}
@Test
public void constant() throws IOException {
- highlight("1");
+ executeSensor("1");
assertHighlighting(1, 0, 1, TypeOfText.CONSTANT);
- highlight("1.0");
+ executeSensor("1.0");
assertHighlighting(1, 0, 3, TypeOfText.CONSTANT);
- highlight("0px");
+ executeSensor("0px");
assertHighlighting(1, 0, 3, TypeOfText.CONSTANT);
- highlight("1em");
+ executeSensor("1em");
assertHighlighting(1, 0, 3, TypeOfText.CONSTANT);
- highlight("#ddd");
+ executeSensor("#ddd");
assertHighlighting(1, 0, 4, TypeOfText.CONSTANT);
}
@Test
public void annotation() throws IOException {
- highlight("@bar { }");
+ executeSensor("@bar { }");
assertHighlighting(1, 0, 4, TypeOfText.ANNOTATION);
- highlight("@my-selector: banner;");
+ executeSensor("@my-selector: banner;");
assertHighlighting(1, 0, 12, TypeOfText.ANNOTATION);
- highlight("@import \"src/themes\"");
+ executeSensor("@import \"src/themes\"");
assertHighlighting(1, 0, 7, TypeOfText.ANNOTATION);
- highlight(".element { color: @@color }");
+ executeSensor(".element { color: @@color }");
assertHighlighting(1, 18, 7, TypeOfText.ANNOTATION);
}
@Test
public void keyword() throws IOException {
- highlight("$foo { }");
+ executeSensor("$foo { }");
assertHighlighting(1, 0, 4, TypeOfText.KEYWORD);
- highlight("#header { .border-radius(4px); }");
+ executeSensor("#header { .border-radius(4px); }");
assertHighlighting(1, 0, 7, TypeOfText.KEYWORD);
}
@Test
public void keyword_light() throws IOException {
- highlight("bar: foo { }");
+ executeSensor("bar: foo { }");
assertHighlighting(1, 0, 3, TypeOfText.KEYWORD_LIGHT);
- highlight("bar { foo: 1px }");
+ executeSensor("bar { foo: 1px }");
assertHighlighting(1, 6, 3, TypeOfText.KEYWORD_LIGHT);
- highlight("bar { foo-bar: 1px }");
+ executeSensor("bar { foo-bar: 1px }");
assertHighlighting(1, 6, 7, TypeOfText.KEYWORD_LIGHT);
}
- private void highlight(String content) throws IOException {
+ @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")
@@ -140,7 +194,10 @@ public class MetricSensorTest {
sensorContext = SensorContextTester.create(tempFolder.getRoot());
sensorContext.fileSystem().add(inputFile);
- new MetricSensor().execute(sensorContext);
+ 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) {
@@ -149,4 +206,12 @@ public class MetricSensorTest {
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);
+ }
}