diff options
| author | Amaury Levé | 2018-06-21 11:33:42 +0200 | 
|---|---|---|
| committer | Elena Vilchik | 2018-06-21 11:33:42 +0200 | 
| commit | 913028fc4c913fec3bbc1800c00e526413040e01 (patch) | |
| tree | 0d1b7cadc4d45d82a1f0e3c91e96670d4b813551 | |
| parent | 6472431ad488158bfcf863a7b4a5655e1ecc55e8 (diff) | |
| download | sonar-css-913028fc4c913fec3bbc1800c00e526413040e01.tar.bz2 | |
Add comment lines and lines of code metrics (#49)
4 files changed, 196 insertions, 75 deletions
| diff --git a/its/plugin/src/test/java/org/sonar/css/its/MetricsTest.java b/its/plugin/src/test/java/org/sonar/css/its/MetricsTest.java index ea631c9..9af016d 100644 --- a/its/plugin/src/test/java/org/sonar/css/its/MetricsTest.java +++ b/its/plugin/src/test/java/org/sonar/css/its/MetricsTest.java @@ -25,6 +25,7 @@ import org.junit.ClassRule;  import org.junit.Test;  import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.css.its.Tests.getMeasure;  import static org.sonar.css.its.Tests.getProjectMeasureAsDouble;  public class MetricsTest { @@ -42,6 +43,23 @@ public class MetricsTest {    @Test    public void test() {      assertThat(getProjectMeasureAsDouble("lines", PROJECT_KEY)).isEqualTo(33); +    assertThat(getProjectMeasureAsDouble("ncloc", PROJECT_KEY)).isEqualTo(23); +    assertThat(getProjectMeasureAsDouble("comment_lines", PROJECT_KEY)).isEqualTo(5); + +    assertThat(getMeasure("ncloc_data", PROJECT_KEY + ":src/file1.css").getValue()) +        .contains("1=1;", "2=1;", "3=1;", "4=1;", "5=1;", "6=1;", "7=1;", "8=1"); +    assertThat(getMeasure("comment_lines_data", PROJECT_KEY + ":src/file1.css").getValue()) +        .contains("5=1;", "10=1"); + +    assertThat(getMeasure("ncloc_data", PROJECT_KEY + ":src/file2.less").getValue()) +        .contains("1=1;", "2=1;", "3=1;", "4=1;", "5=1;", "6=1;", "7=1;", "8=1;", "9=1"); +    assertThat(getMeasure("comment_lines_data", PROJECT_KEY + ":src/file2.less").getValue()) +        .contains("11=1"); + +    assertThat(getMeasure("ncloc_data", PROJECT_KEY + ":src/file3.scss").getValue()) +        .contains("1=1;", "3=1;", "5=1;", "6=1;", "7=1;", "8=1"); +    assertThat(getMeasure("comment_lines_data", PROJECT_KEY + ":src/file3.scss").getValue()) +        .contains("10=1;", "12=1");    }  } diff --git a/its/plugin/src/test/java/org/sonar/css/its/Tests.java b/its/plugin/src/test/java/org/sonar/css/its/Tests.java index 335bf6a..eb6a01e 100644 --- a/its/plugin/src/test/java/org/sonar/css/its/Tests.java +++ b/its/plugin/src/test/java/org/sonar/css/its/Tests.java @@ -67,7 +67,7 @@ public class Tests {      return (measure == null) ? null : Double.parseDouble(measure.getValue());    } -  private static Measure getMeasure(String metricKey, String projectKey) { +  public static Measure getMeasure(String metricKey, String projectKey) {      ComponentWsResponse response = newWsClient().measures().component(new ComponentRequest()        .setComponent(projectKey)        .setMetricKeys(Collections.singletonList(metricKey))); 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); +  }  } | 
