Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CxxFileLinesVisitor #1602

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,9 @@
*/
package org.sonar.cxx.sensors.visitors;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.sonar.sslr.api.AstAndTokenVisitor;
import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.AstNodeType;
import com.sonar.sslr.api.GenericTokenType;
import com.sonar.sslr.api.Grammar;
import com.sonar.sslr.api.Token;
import com.sonar.sslr.api.Trivia;
import java.util.ArrayList;
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.SensorContext;
Expand All @@ -45,6 +37,15 @@
import org.sonar.cxx.sensors.utils.CxxUtils;
import org.sonar.squidbridge.SquidAstVisitor;

import com.google.common.collect.Sets;
import com.sonar.sslr.api.AstAndTokenVisitor;
import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.GenericTokenType;
import com.sonar.sslr.api.Grammar;
import com.sonar.sslr.api.Token;
import com.sonar.sslr.api.TokenType;
import com.sonar.sslr.api.Trivia;

/**
* Visitor that computes {@link CoreMetrics#NCLOC_DATA_KEY} and {@link CoreMetrics#COMMENT_LINES_DATA_KEY} metrics used
* by the DevCockpit.
Expand All @@ -55,19 +56,11 @@ public class CxxFileLinesVisitor extends SquidAstVisitor<Grammar> implements Ast

private final CxxLanguage language;
private final FileLinesContextFactory fileLinesContextFactory;
private static final Set<Integer> LINES_OF_CODE = Sets.newHashSet();
private static final Set<Integer> LINES_OF_COMMENTS = Sets.newHashSet();
private static final Set<Integer> EXECUTABLE_LINES = Sets.newHashSet();
private final FileSystem fileSystem;
private static int isWithinFunctionDefinition;
private static final Set<String> IGNORE_TOKEN = Sets.newHashSet(";", "{", "}", "(", ")", "[", "]");
private static final AstNodeType[] NODES_TO_VISIT = {
CxxGrammarImpl.labeledStatement,
CxxGrammarImpl.expressionStatement,
CxxGrammarImpl.iterationStatement,
CxxGrammarImpl.jumpStatement,
CxxGrammarImpl.assignmentExpression,
CxxGrammarImpl.lambdaExpression};
private List<Integer> linesOfCode;
private List<Integer> linesOfComments;
private List<Integer> executableLines;
private int isWithinFunctionDefinition;

/**
* CxxFileLinesVisitor generates sets for linesOfCode, linesOfComments, executableLines
Expand All @@ -85,9 +78,54 @@ public CxxFileLinesVisitor(CxxLanguage language, FileLinesContextFactory fileLin

@Override
public void init() {
subscribeTo(CxxGrammarImpl.functionDefinition);
for (AstNodeType nodeType : NODES_TO_VISIT) {
subscribeTo(nodeType);
subscribeTo(CxxGrammarImpl.functionDefinition,
CxxGrammarImpl.labeledStatement,
CxxGrammarImpl.expressionStatement,
CxxGrammarImpl.iterationStatement,
CxxGrammarImpl.jumpStatement,
CxxGrammarImpl.assignmentExpression,
CxxGrammarImpl.lambdaExpression);
}

static boolean isCodeToken(Token token) {
final TokenType type = token.getType();
if (!(type instanceof CxxPunctuator)) {
return true;
}

switch ((CxxPunctuator) type) {
case SEMICOLON:
case BR_LEFT:
case BR_RIGHT:
case CURLBR_LEFT:
case CURLBR_RIGHT:
case SQBR_LEFT:
case SQBR_RIGHT:
return false;

default:
return true;
}
}

static boolean isExecutableToken(Token token) {
final TokenType type = token.getType();
return !CxxPunctuator.CURLBR_LEFT.equals(type) && !CxxKeyword.DEFAULT.equals(type) && !CxxKeyword.CASE.equals(type);
}

static void addLineNumber(List<Integer> collection, int lineNr) {
// use the fact, that we iterate over tokens from top to bottom.
// if the line was already visited its index was put at the end of
// collection.
//
// don't use Set, because Set would sort elements on each insert
// since we potentially insert line number for each token it would create
// large run-time overhead
//
// we sort/filter duplicates only once - on leaveFile(AstNode)
//
if (collection.isEmpty() || collection.get(collection.size() - 1) != lineNr) {
collection.add(lineNr);
}
}

Expand All @@ -97,14 +135,14 @@ public void visitToken(Token token) {
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is issue #1220. Could this be the reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there is a connection between the metrics and the coverage data. It requires additional investigation.

}

if ((isWithinFunctionDefinition != 0) && !IGNORE_TOKEN.contains(token.getType().getValue())) {
LINES_OF_CODE.add(token.getLine());
if ((isWithinFunctionDefinition != 0) && isCodeToken(token)) {
addLineNumber(linesOfCode, token.getLine());
}

List<Trivia> trivias = token.getTrivia();
for (Trivia trivia : trivias) {
if (trivia.isComment()) {
LINES_OF_COMMENTS.add(trivia.getToken().getLine());
addLineNumber(linesOfComments, trivia.getToken().getLine());
}
}
}
Expand Down Expand Up @@ -133,14 +171,13 @@ public void visitNode(AstNode astNode) {
/**
* @param astNode
*/
private static void visitStatement(AstNode astNode) {
private void visitStatement(AstNode astNode) {
if (astNode.hasDirectChildren(CxxGrammarImpl.declarationStatement)
&& !astNode.hasDescendant(CxxGrammarImpl.initializer)) {
return;
}
String value = astNode.getTokenValue();
if (value != null && !"{".equals(value) && !"default".equals(value) && !"case".equals(value)) {
EXECUTABLE_LINES.add(astNode.getTokenLine());
if (isExecutableToken(astNode.getToken())) {
addLineNumber(executableLines, astNode.getTokenLine());
}
}

Expand All @@ -151,17 +188,11 @@ public void leaveNode(AstNode astNode) {
}
}

/**
*
*/
private static void increaseFunctionDefinition() {
private void increaseFunctionDefinition() {
isWithinFunctionDefinition++;
}

/**
*
*/
private static void decreaseFunctionDefinitions() {
private void decreaseFunctionDefinitions() {
isWithinFunctionDefinition--;
}

Expand All @@ -185,9 +216,9 @@ private static boolean isDefaultOrDeleteFunctionBody(AstNode astNode) {

@Override
public void visitFile(AstNode astNode) {
LINES_OF_CODE.clear();
LINES_OF_COMMENTS.clear();
EXECUTABLE_LINES.clear();
linesOfCode = new ArrayList<>();
linesOfComments = new ArrayList<>();
executableLines = new ArrayList<>();
}

@Override
Expand All @@ -199,23 +230,23 @@ public void leaveFile(AstNode astNode) {
FileLinesContext fileLinesContext = fileLinesContextFactory.createFor(inputFile);

try {
LINES_OF_CODE.stream().forEach(
linesOfCode.stream().sequential().distinct().forEach(
line -> fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1)
);
} catch (IllegalArgumentException e) {
LOG.error("NCLOC_DATA_KEY metric error: {}", e.getMessage());
CxxUtils.validateRecovery(e, language);
}
try {
LINES_OF_COMMENTS.stream().forEach(
linesOfComments.stream().sequential().distinct().forEach(
line -> fileLinesContext.setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, line, 1)
);
} catch (IllegalArgumentException e) {
LOG.error("COMMENT_LINES_DATA_KEY metric error: {}", e.getMessage());
CxxUtils.validateRecovery(e, language);
}
try {
EXECUTABLE_LINES.stream().forEach(
executableLines.stream().sequential().distinct().forEach(
line -> fileLinesContext.setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, 1)
);
} catch (IllegalArgumentException e) {
Expand All @@ -227,22 +258,14 @@ public void leaveFile(AstNode astNode) {
if (LOG.isDebugEnabled()) {
LOG.debug("CxxFileLinesVisitor: '{}'", inputFile.uri().getPath());
LOG.debug(" lines: '{}'", inputFile.lines());
LOG.debug(" executableLines: '{}'", EXECUTABLE_LINES);
LOG.debug(" linesOfCode: '{}'", LINES_OF_CODE);
LOG.debug(" linesOfComments: '{}'", LINES_OF_COMMENTS);
LOG.debug(" executableLines: '{}'", Sets.newHashSet(executableLines));
LOG.debug(" linesOfCode: '{}'", Sets.newHashSet(linesOfCode));
LOG.debug(" linesOfComments: '{}'", Sets.newHashSet(linesOfComments));
}
}

public Set<Integer> getLinesOfCode() {
return ImmutableSet.copyOf(LINES_OF_CODE);
}

public Set<Integer> getLinesOfComments() {
return ImmutableSet.copyOf(LINES_OF_COMMENTS);
}

public Set<Integer> getExecutableLines() {
return ImmutableSet.copyOf(EXECUTABLE_LINES);
linesOfCode = null;
linesOfComments = null;
executableLines = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;

import org.assertj.core.api.SoftAssertions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.mock;
Expand All @@ -39,6 +41,7 @@
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
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.cxx.CxxAstScanner;
Expand All @@ -50,7 +53,54 @@ public class CxxFileLinesVisitorTest {

private CxxLanguage language;
private FileLinesContextFactory fileLinesContextFactory;
private FileLinesContext fileLinesContext;
private class FileLinesContextForTesting implements FileLinesContext
{
public final Set<Integer> executableLines = new HashSet<>();
public final Set<Integer> linesOfCode = new HashSet<>();
public final Set<Integer> linesOfComments = new HashSet<>();

@Override
public void setIntValue(String metricKey, int line, int value) {
Assert.assertEquals(1, value);

switch (metricKey) {
case CoreMetrics.NCLOC_DATA_KEY:
linesOfCode.add(line);
break;
case CoreMetrics.COMMENT_LINES_DATA_KEY:
linesOfComments.add(line);
break;
case CoreMetrics.EXECUTABLE_LINES_DATA_KEY:
executableLines.add(line);
break;
default:
Assert.fail("Unsupported metric key " + metricKey);
}
}

@Override
public Integer getIntValue(String metricKey, int line) {
Assert.fail("unexpected method called: getIntValue()");
return null;
}

@Override
public void setStringValue(String metricKey, int line, String value) {
Assert.fail("unexpected method called: setStringValue()");
}

@Override
public String getStringValue(String metricKey, int line) {
Assert.fail("unexpected method called: getStringValue()");
return null;
}

@Override
public void save() {
}
}

private FileLinesContextForTesting fileLinesContext;
private File baseDir;
private File target;
private Set<Integer> testLines;
Expand All @@ -59,7 +109,7 @@ public class CxxFileLinesVisitorTest {
public void setUp() {
language = TestUtils.mockCxxLanguage();
fileLinesContextFactory = mock(FileLinesContextFactory.class);
fileLinesContext = mock(FileLinesContext.class);
fileLinesContext = new FileLinesContextForTesting();

when(language.getPluginProperty(CxxCoverageSensor.REPORT_PATH_KEY))
.thenReturn("sonar.cxx." + CxxCoverageSensor.REPORT_PATH_KEY);
Expand Down Expand Up @@ -89,7 +139,7 @@ public void TestLinesOfCode() throws UnsupportedEncodingException, IOException {
CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), visitor);

SoftAssertions softly = new SoftAssertions();
softly.assertThat(visitor.getLinesOfCode()).containsExactlyInAnyOrderElementsOf(testLines);
softly.assertThat(fileLinesContext.linesOfCode).containsExactlyInAnyOrderElementsOf(testLines);
softly.assertAll();
}

Expand All @@ -109,7 +159,7 @@ public void TestLinesOfComments() throws UnsupportedEncodingException, IOExcepti

CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), visitor);

assertThat(visitor.getLinesOfComments()).containsExactlyInAnyOrder(48, 1, 33, 97, 35, 117, 102, 7, 119, 106, 13);
assertThat(fileLinesContext.linesOfComments).containsExactlyInAnyOrder(48, 1, 33, 97, 35, 117, 102, 7, 119, 106, 13);
}

@Test
Expand All @@ -129,7 +179,7 @@ public void TestExecutableLinesOfCode() throws UnsupportedEncodingException, IOE

CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), visitor);

assertThat(visitor.getExecutableLines()).containsExactlyInAnyOrder(10, 26, 34, 35, 56, 59, 69, 70, 72, 73,
assertThat(fileLinesContext.executableLines).containsExactlyInAnyOrder(10, 26, 34, 35, 56, 59, 69, 70, 72, 73,
75, 76, 79, 87, 90, 98, 102, 118, 119, 126);
}
}