Skip to content
This repository has been archived by the owner on Jul 8, 2019. It is now read-only.

Added comment lines and comment density metrics. #179

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions src/main/java/com/pablissimo/sonar/CombinedCoverageSensor.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package com.pablissimo.sonar;

import java.util.Map;
import java.util.Set;

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.measures.CoreMetrics;

import java.util.Map;
import java.util.Set;

public class CombinedCoverageSensor implements Sensor {
private LOCSensor locSensor;
private CommentSensor commentSensor;
private TsCoverageSensor coverageSensor;
public CombinedCoverageSensor(LOCSensor locSensor, TsCoverageSensor coverageSensor) {

public CombinedCoverageSensor(LOCSensor locSensor, CommentSensor commentSensor, TsCoverageSensor coverageSensor) {
this.locSensor = locSensor;
this.commentSensor = commentSensor;
this.coverageSensor = coverageSensor;
}

@Override
public void describe(SensorDescriptor descriptor) {
descriptor.name("Combined LCOV and LOC sensor");
descriptor.name("Combined LCOV, LOC and comments");
descriptor.onlyOnLanguage(TypeScriptLanguage.LANGUAGE_KEY);
}

Expand All @@ -28,9 +31,24 @@ public void execute(SensorContext context) {
// First - LOC everything up, as we'll need LOC for uncovered lines metrics
Map<InputFile, Set<Integer>> nonCommentLinesByFile = this.locSensor.execute(context);

// Now the LCOV pass can properly handle files that don't appear in
// Now the LCOV pass can properly handle files that don't appear in
// configuration and set lines-to-cover values as required
this.coverageSensor.execute(context, nonCommentLinesByFile);

// Last but not least - get those comments and compute comment density
Map<InputFile, Set<Integer>> commentedLinesByFile = this.commentSensor.execute(context);

for (InputFile inputFile : nonCommentLinesByFile.keySet()) {
Double commentDensity = 0.0;
Double commentLines = new Double(commentedLinesByFile.get(inputFile).size());
Double nonCommentLines = new Double(nonCommentLinesByFile.get(inputFile).size());
if (commentedLinesByFile.containsKey(inputFile)) {
commentDensity = commentLines / (commentLines + nonCommentLines) * 100;
}
context.<Double>newMeasure().forMetric(CoreMetrics.COMMENT_LINES_DENSITY).on(inputFile).withValue(commentDensity).save();

}

}

}
14 changes: 14 additions & 0 deletions src/main/java/com/pablissimo/sonar/CommentSensor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.pablissimo.sonar;

import java.util.Map;
import java.util.Set;

import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorContext;

@BatchSide
@FunctionalInterface
public interface CommentSensor {
Map<InputFile, Set<Integer>> execute(SensorContext ctx);
}
141 changes: 141 additions & 0 deletions src/main/java/com/pablissimo/sonar/CommentSensorImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.pablissimo.sonar;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.measures.CoreMetrics;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class CommentSensorImpl implements CommentSensor {
private static final Logger LOG = LoggerFactory.getLogger(CommentSensorImpl.class);

@Override
public String toString() {
return getClass().getSimpleName();
}

private BufferedReader getReaderFromFile(InputFile inputFile) throws FileNotFoundException {
return new BufferedReader(new FileReader(inputFile.file()));
}

private Set<Integer> getCommentLineNumbers(InputFile inputFile) {
HashSet<Integer> toReturn = new HashSet<>();

int lineNumber = 0;

BufferedReader br;
try {
br = this.getReaderFromFile(inputFile);

boolean isEOF;
boolean isCommentOpen = false;
boolean isACommentLine;
do {
String line = br.readLine();
lineNumber++;

if (line != null) {
isACommentLine = isCommentOpen;
line = line.trim();

if (isCommentOpen) {
if (line.contains("*/")) {
isCommentOpen = false;
isACommentLine = false;
} else if (line.equals("*")) {
isACommentLine = false;
}
} else {
if (line.startsWith("/*")) {
if (line.contains("*/")) {
isCommentOpen = false;
} else {
isCommentOpen = true;
}
if (line.equals("/*") || line.endsWith("/*")) {
isACommentLine = false;
} else {
isACommentLine = true;
}
} else if (line.startsWith("*")) {
if (line.equals("*")) {
isACommentLine = false;
} else {
isACommentLine = true;
}
} else if (line.startsWith("*/")) {
isCommentOpen = false;
isACommentLine = false;
} else if (line.contains("//") && !line.equals("//")) {
isACommentLine = true;
} else if (line.contains("/*")) {
if (line.contains("*/")) {
isCommentOpen = false;

if (line.substring(line.indexOf("/*") + 1, line.indexOf("*/") - 1).trim().length() > 0) {
isACommentLine = true;
}
} else {
isCommentOpen = true;
}
}


// if (line.contains("/*")) {
// if (line.contains("*/")) {
// isCommentOpen = false;
// } else {
// isCommentOpen = true;
// }
// isACommentLine = true;
// }
}
isEOF = true;
line = line.replaceAll("\\n|\\t|\\s", "");
if (isACommentLine) {
toReturn.add(lineNumber);
}
} else {
isEOF = false;
}
} while (isEOF);

br.close();

} catch (FileNotFoundException e) {
LOG.error("File not found", e);
} catch (IOException e) {
LOG.error("Error while reading BufferedReader", e);
}

return toReturn;
}

@Override
public Map<InputFile, Set<Integer>> execute(SensorContext ctx) {
HashMap<InputFile, Set<Integer>> toReturn = new HashMap<>();

Iterable<InputFile> affectedFiles =
ctx
.fileSystem()
.inputFiles(ctx.fileSystem().predicates().hasLanguage(TypeScriptLanguage.LANGUAGE_KEY));

for (InputFile inputFile : affectedFiles) {
Set<Integer> commentLineNumbers = this.getCommentLineNumbers(inputFile);
toReturn.put(inputFile, commentLineNumbers);

ctx.<Integer>newMeasure().forMetric(CoreMetrics.COMMENT_LINES).on(inputFile).withValue(commentLineNumbers.size()).save();
}

return toReturn;
}
}
1 change: 1 addition & 0 deletions src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ public void define(Context ctx) {
ctx.addExtension(TsLintExecutorImpl.class);
ctx.addExtension(TsLintParserImpl.class);
ctx.addExtension(LOCSensorImpl.class);
ctx.addExtension(CommentSensorImpl.class);
ctx.addExtension(TsCoverageSensorImpl.class);
}
}
47 changes: 28 additions & 19 deletions src/test/java/com/pablissimo/sonar/CombinedCoverageSensorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,63 @@

public class CombinedCoverageSensorTest {
LOCSensor locSensor;
CommentSensor commentSensor;
TsCoverageSensor coverageSensor;

SensorContextTester sensorContext;

CombinedCoverageSensor sensor;

@Before
public void setUp() throws Exception {
this.sensorContext = SensorContextTester.create(new File(""));
this.locSensor = mock(LOCSensor.class);
this.coverageSensor = mock(TsCoverageSensor.class);

this.sensor = new CombinedCoverageSensor(this.locSensor, this.coverageSensor);
this.commentSensor = mock(CommentSensor.class);

this.sensor = new CombinedCoverageSensor(this.locSensor, this.commentSensor, this.coverageSensor);
}

@Test
public void describe_setsName() {
DefaultSensorDescriptor desc = new DefaultSensorDescriptor();

this.sensor.describe(desc);
assertEquals(desc.name(), "Combined LCOV and LOC sensor");

assertEquals(desc.name(), "Combined LCOV, LOC and comments");
}

@Test
public void describe_setsLanguage() {
DefaultSensorDescriptor desc = new DefaultSensorDescriptor();

this.sensor.describe(desc);

assertEquals(TypeScriptLanguage.LANGUAGE_KEY, desc.languages().iterator().next());
}

@Test
public void execute_callsLocSensor() {
this.sensor.execute(this.sensorContext);

verify(this.locSensor, times(1)).execute(eq(this.sensorContext));
}


@Test
public void execute_callsCommentSensor() {
this.sensor.execute(this.sensorContext);

verify(this.commentSensor, times(1)).execute(eq(this.sensorContext));
}

@Test
public void execute_callsCoverageSensorWithLocSensorOutput() {
Map<InputFile, Set<Integer>> locOutput = new HashMap<InputFile, Set<Integer>>();

when(this.locSensor.execute(eq(this.sensorContext))).thenReturn(locOutput);

this.sensor.execute(this.sensorContext);
verify(this.coverageSensor, times(1)).execute(eq(this.sensorContext), eq(locOutput));

verify(this.coverageSensor, times(1)).execute(eq(this.sensorContext), eq(locOutput));
}
}
72 changes: 72 additions & 0 deletions src/test/java/com/pablissimo/sonar/CommentSensorImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.pablissimo.sonar;

import static org.junit.Assert.*;

import java.io.File;
import java.io.FileNotFoundException;

import org.junit.Before;
import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.internal.SensorContextTester;
import org.sonar.api.measures.CoreMetrics;

public class CommentSensorImplTest {
CommentSensor sensor;

SensorContextTester sensorContext;

@Before
public void setUp() throws Exception {
this.sensorContext = SensorContextTester.create(new File(""));
this.sensor = new CommentSensorImpl();
}

@Test
public void toStringReturnsClassName() {
assertEquals("CommentSensorImpl", new CommentSensorImpl().toString());
}

@Test
public void basicBlockCommentsDiscounted() throws FileNotFoundException {
assertLineCount("blockcomments1", 1);
}

@Test
public void blockCommentsNotConfusedWithNestedComments() throws FileNotFoundException {
assertLineCount("blockcomments2", 6);
}

@Test
public void linesEndingWithABlockCommentStillCounted() throws FileNotFoundException {
assertLineCount("blockcomments3", 2);
}

@Test
public void oneLineBlockCommentsDoNotConfuseCounting() throws FileNotFoundException {
assertLineCount("blockcomments4", 1);
}

@Test
public void oneLineBlockCommentAtEndOfRealLineShouldNotConsiderNextLinesAsComments() throws FileNotFoundException {
assertLineCount("blockcomments5", 1);
}

@Test
public void lineLevelCommentsAndWhitespaceHandledCorrectly() throws FileNotFoundException {
assertLineCount("linecomments", 5);
}

private DefaultInputFile resource(String relativePath) {
return new DefaultInputFile("", relativePath).setLanguage(TypeScriptLanguage.LANGUAGE_KEY);
}

private void assertLineCount(String testName, Integer expected) throws FileNotFoundException {
DefaultInputFile resource = resource("src/test/resources/loc/" + testName + ".txt");
this.sensorContext.fileSystem().add(resource);

this.sensor.execute(this.sensorContext);

assertEquals(expected, this.sensorContext.measure(resource.key(), CoreMetrics.COMMENT_LINES).value());
}
}
4 changes: 2 additions & 2 deletions src/test/resources/loc/blockcomments1.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Block comments shouldn't count towards line count - expected 2 lines
Block comments should count towards line count - expect 2 code and 1 comment lines
*/
module Whatever {
}
}
Loading