Skip to content

Commit

Permalink
Metrics analyzer for Razor: Lines of code are outside the range of th…
Browse files Browse the repository at this point in the history
…e file (#9299)
  • Loading branch information
costin-zaharia-sonarsource committed May 22, 2024
1 parent 2d5c052 commit c1b8fd7
Show file tree
Hide file tree
Showing 28 changed files with 236 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import java.util.HashSet;

import java.util.function.UnaryOperator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.sensor.SensorContext;
Expand All @@ -40,6 +43,7 @@ public class MetricsImporter extends ProtobufImporter<MetricsInfo> {
private final SensorContext context;
private final FileLinesContextFactory fileLinesContextFactory;
private final NoSonarFilter noSonarFilter;
private static final Logger LOG = LoggerFactory.getLogger(MetricsImporter.class);

public MetricsImporter(SensorContext context, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter,
UnaryOperator<String> toRealPath) {
Expand All @@ -62,8 +66,13 @@ void consumeFor(InputFile inputFile, MetricsInfo message) {

saveMetric(context, inputFile, CoreMetrics.COMMENT_LINES, message.getNonBlankCommentCount());

int lineCount = inputFile.lines();
for (int line : message.getCodeLineList()) {
fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1);
if (line <= lineCount) {
fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1);
} else if (LOG.isDebugEnabled()) {
LOG.debug("The code line number was out of the range. File {}, Line {}", inputFile.filename(), line);
}
}
saveMetric(context, inputFile, CoreMetrics.NCLOC, message.getCodeLineCount());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,26 @@
import static org.mockito.Mockito.mock;

public class RazorImporterTestBase {
protected final static File TEST_DATA_DIR = new File("src/test/resources/RazorProtobufImporter");
protected final SensorContextTester sensorContext = SensorContextTester.create(TEST_DATA_DIR);
protected final static String TEST_DATA_DIR = "src/test/resources/RazorProtobufImporter";
protected final static String WEB_PROJECT_PATH = Paths.get(TEST_DATA_DIR, "WebProject").toString();
protected final static String ROSLYN_4_9_DIR = Paths.get(TEST_DATA_DIR, "Roslyn 4.9").toString();
protected final static String ROSLYN_4_10_DIR = Paths.get(TEST_DATA_DIR, "Roslyn 4.10").toString();
protected final SensorContextTester sensorContext = SensorContextTester.create(new File(TEST_DATA_DIR));

@Rule
public LogTester logTester = new LogTester();
protected DefaultInputFile CasesInputFile;
protected DefaultInputFile OverlapSymbolReferencesInputFile;
protected DefaultInputFile ProgramInputFile;

protected static String fileName(String filePath) {
return Paths.get(filePath).getFileName().toString();
}

@Before
public void setUp() throws FileNotFoundException {
public void setUp() {
logTester.setLevel(Level.TRACE);
CasesInputFile = addTestFileToContext("Cases.razor");
OverlapSymbolReferencesInputFile = addTestFileToContext("OverlapSymbolReferences.razor");
ProgramInputFile = addTestFileToContext("Program.cs");
}

private DefaultInputFile addTestFileToContext(String testFilePath) throws FileNotFoundException {
var testFile = new File(TEST_DATA_DIR, testFilePath);
protected DefaultInputFile addTestFileToContext(String testFilePath) throws FileNotFoundException {
var testFile = new File(WEB_PROJECT_PATH, testFilePath);
assertThat(testFile).withFailMessage("no such file: " + testFilePath).isFile();
var inputFile = new TestInputFileBuilder("dummyKey", testFilePath)
.setMetadata(new FileMetadata(mock(AnalysisWarnings.class)).readMetadata(new FileReader(testFile)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.event.Level;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.issue.NoSonarFilter;
import org.sonar.api.measures.CoreMetrics;
Expand All @@ -40,24 +45,26 @@
import static org.sonarsource.dotnet.shared.plugins.ProtobufDataImporter.METRICS_FILENAME;

public class RazorMetricsImporterTest extends RazorImporterTestBase {
private static final File PROTOBUF_FILE = new File(TEST_DATA_DIR, METRICS_FILENAME);
private static final File PROTOBUF_4_9_FILE = new File(ROSLYN_4_9_DIR, METRICS_FILENAME);
private static final File PROTOBUF_4_10_FILE = new File(ROSLYN_4_10_DIR, METRICS_FILENAME);

@Before
@Override
public void setUp() throws FileNotFoundException {
public void setUp() {
super.setUp();
assertThat(PROTOBUF_FILE).withFailMessage("no such file: " + PROTOBUF_FILE).isFile();
assertThat(PROTOBUF_4_9_FILE).withFailMessage("no such file: " + PROTOBUF_4_9_FILE).isFile();
assertThat(PROTOBUF_4_10_FILE).withFailMessage("no such file: " + PROTOBUF_4_10_FILE).isFile();
}

@Test
public void roslyn_metrics_are_imported() {
var inputFile = CasesInputFile;
public void roslyn_metrics_are_imported_before_4_10() throws FileNotFoundException {
var inputFile = addTestFileToContext("Cases.razor");
var noSonarFilter = mock(NoSonarFilter.class);
var fileLinesContext = mock(FileLinesContext.class);
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(fileLinesContext);

new MetricsImporter(sensorContext, fileLinesContextFactory, noSonarFilter, RazorImporterTestBase::fileName).accept(PROTOBUF_FILE.toPath());
new MetricsImporter(sensorContext, fileLinesContextFactory, noSonarFilter, RazorImporterTestBase::fileName).accept(PROTOBUF_4_9_FILE.toPath());

var measures = sensorContext.measures(inputFile.key());
assertThat(measures).hasSize(7);
Expand All @@ -74,25 +81,73 @@ public void roslyn_metrics_are_imported() {

verify(noSonarFilter).noSonarInFile(inputFile, Collections.emptySet());

verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 3, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 5, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 8, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 13, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 23, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 24, 1);

verify(fileLinesContext, times(2)).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 3, 1);
verify(fileLinesContext, times(2)).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 5, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 8, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 9, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 13, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 16, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 18, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 19, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 21, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 22, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 23, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 24, 1);
verify(fileLinesContext).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 25, 1);
verifyMetrics(fileLinesContext, CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 8, 23, 24);
verifyMetrics(fileLinesContext, CoreMetrics.NCLOC_DATA_KEY, 3, 5, 8, 9, 13, 16, 18, 19, 21, 22, 23, 24, 25);

assertThat(logTester.logs(Level.DEBUG)).isEmpty();
}

@Test
public void roslyn_metrics_are_imported_starting_with_4_10() throws FileNotFoundException {
var inputFile = addTestFileToContext("Cases.razor");
var noSonarFilter = mock(NoSonarFilter.class);
var fileLinesContext = mock(FileLinesContext.class);
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(fileLinesContext);

new MetricsImporter(sensorContext, fileLinesContextFactory, noSonarFilter, RazorImporterTestBase::fileName).accept(PROTOBUF_4_10_FILE.toPath());

var measures = sensorContext.measures(inputFile.key());
assertThat(measures).hasSize(7);

assertThat(measures).extracting("metric", "value")
.containsOnly(
Tuple.tuple(CoreMetrics.COMPLEXITY, 5),
Tuple.tuple(CoreMetrics.FUNCTIONS, 3),
Tuple.tuple(CoreMetrics.COMMENT_LINES, 0),
Tuple.tuple(CoreMetrics.COGNITIVE_COMPLEXITY, 1),
Tuple.tuple(CoreMetrics.CLASSES, 0),
Tuple.tuple(CoreMetrics.NCLOC, 14),
Tuple.tuple(CoreMetrics.STATEMENTS, 3));

verify(noSonarFilter).noSonarInFile(inputFile, Collections.emptySet());

verifyMetrics(fileLinesContext, CoreMetrics.EXECUTABLE_LINES_DATA_KEY, 8, 23, 24);
verifyMetrics(fileLinesContext, CoreMetrics.NCLOC_DATA_KEY, 1, 3, 5, 8, 9, 13, 16, 18, 19, 21, 22, 23, 24, 25);

assertThat(logTester.logs(Level.DEBUG)).isEmpty();
}

@Test
public void roslyn_metrics_out_of_range_with_4_10_debug_enabled() throws FileNotFoundException {
addTestFileToContext("_Imports.razor");
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(mock(FileLinesContext.class));

new MetricsImporter(sensorContext, fileLinesContextFactory, mock(NoSonarFilter.class), RazorImporterTestBase::fileName).accept(PROTOBUF_4_10_FILE.toPath());

assertThat(logTester.logs(Level.DEBUG)).containsExactly("The code line number was out of the range. File _Imports.razor, Line 4");
}

@Test
public void roslyn_metrics_out_of_range_with_4_10_debug_disabled() throws FileNotFoundException {
logTester.setLevel(Level.INFO);
addTestFileToContext("_Imports.razor");
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(mock(FileLinesContext.class));

new MetricsImporter(sensorContext, fileLinesContextFactory, mock(NoSonarFilter.class), RazorImporterTestBase::fileName).accept(PROTOBUF_4_10_FILE.toPath());

assertThat(logTester.logs(Level.DEBUG)).isEmpty();
}

private void verifyMetrics(FileLinesContext context, String key, int... values) {
var groups = Arrays.stream(values)
.boxed()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

for (int groupKey : groups.keySet()){
verify(context, times(groups.get(groupKey).intValue())).setIntValue(key, groupKey, 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,32 @@
import static org.sonarsource.dotnet.shared.plugins.ProtobufDataImporter.SYMBOLREFS_FILENAME;

public class RazorSymbolRefsImporterTest extends RazorImporterTestBase {
private final File protobuf = new File(TEST_DATA_DIR, SYMBOLREFS_FILENAME);
private static final File PROTOBUF_4_9_FILE = new File(ROSLYN_4_9_DIR, SYMBOLREFS_FILENAME);
private static final File PROTOBUF_4_10_FILE = new File(ROSLYN_4_10_DIR, SYMBOLREFS_FILENAME);

@Override
@Before
public void setUp() throws FileNotFoundException {
public void setUp() {
super.setUp();
assertThat(protobuf).withFailMessage("no such file: " + protobuf).isFile();
assertThat(PROTOBUF_4_9_FILE).withFailMessage("no such file: " + PROTOBUF_4_9_FILE).isFile();
}

@Test
public void test_symbol_refs_get_imported_cases() {
public void test_symbol_refs_get_imported_cases_before_4_10() throws FileNotFoundException {

var inputFile = CasesInputFile;
var sut = new SymbolRefsImporter(sensorContext, RazorImporterTestBase::fileName);
sut.accept(protobuf.toPath());
sut.save();

// a symbol is defined at this location, and referenced at 3 other locations
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 8, 15)).hasSize(2);

// ... other similar examples ...
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 16, 16)).hasSize(4);
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 19, 15)).hasSize(3);
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 21, 17)).isEmpty();

assertThat(logTester.logs(Level.DEBUG)).containsExactly(
"The declaration token at Range[from [line=1, lineOffset=0] to [line=1, lineOffset=17]] overlaps with the referencing token Range[from [line=1, lineOffset=6] to [line=1, lineOffset=23]] in file OverlapSymbolReferences.razor");
verifySymbolRef(PROTOBUF_4_9_FILE);
}

@Test
public void test_symbol_refs_get_imported_overlapSymbolReferences() {
public void test_symbol_refs_get_imported_cases_after_4_10() throws FileNotFoundException {
verifySymbolRef(PROTOBUF_4_10_FILE);
}

var inputFile = OverlapSymbolReferencesInputFile;
@Test
public void test_symbol_refs_get_imported_overlapSymbolReferences_before_4_10() throws FileNotFoundException {
var inputFile = addTestFileToContext("OverlapSymbolReferences.razor");
var sut = new SymbolRefsImporter(sensorContext, s -> Paths.get(s).getFileName().toString());
sut.accept(protobuf.toPath());
sut.accept(PROTOBUF_4_9_FILE.toPath());
sut.save();

var references = sensorContext.referencesForSymbolAt(inputFile.key(), 1, 1);
Expand All @@ -75,4 +66,33 @@ public void test_symbol_refs_get_imported_overlapSymbolReferences() {
assertThat(logTester.logs(Level.DEBUG)).containsExactly(
"The declaration token at Range[from [line=1, lineOffset=0] to [line=1, lineOffset=17]] overlaps with the referencing token Range[from [line=1, lineOffset=6] to [line=1, lineOffset=23]] in file OverlapSymbolReferences.razor");
}

@Test
public void test_symbol_refs_get_imported_overlapSymbolReferences_after_4_10() throws FileNotFoundException {
var inputFile = addTestFileToContext("OverlapSymbolReferences.razor");
var sut = new SymbolRefsImporter(sensorContext, s -> Paths.get(s).getFileName().toString());
sut.accept(PROTOBUF_4_10_FILE.toPath());
sut.save();

// the issue with overlapping symbols has been fixed in dotnet 8.0.5
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 1, 11)).hasSize(1);
assertThat(logTester.logs(Level.DEBUG)).isEmpty();
}

private void verifySymbolRef(File protobuf) throws FileNotFoundException {
var inputFile = addTestFileToContext("Cases.razor");;
var sut = new SymbolRefsImporter(sensorContext, RazorImporterTestBase::fileName);
sut.accept(protobuf.toPath());
sut.save();

// a symbol is defined at this location, and referenced at 3 other locations
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 8, 15)).hasSize(2);

// ... other similar examples ...
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 16, 16)).hasSize(4);
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 19, 15)).hasSize(3);
assertThat(sensorContext.referencesForSymbolAt(inputFile.key(), 21, 17)).isEmpty();

assertThat(logTester.logs(Level.DEBUG)).isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\BlazorWebAssembly.AssemblyInfo.csutf-8�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\.NETCoreApp,Version=v8.0.AssemblyAttributes.csutf-8|
sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.csutf-8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"sdk": {
"version": "8.0.300"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Roslyn version: 4.10.0.0Language version: CSharp12!Concurrent execution: enabled��File 'C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\Microsoft.CodeAnalysis.Razor.Compiler\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\_Imports_razor.g.cs' was recognized as razor generated��File 'C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\Microsoft.CodeAnalysis.Razor.Compiler\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\OverlapSymbolReferences_razor.g.cs' was recognized as razor generated��File 'C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\Microsoft.CodeAnalysis.Razor.Compiler\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\Cases_razor.g.cs' was recognized as razor generated��File 'C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\BlazorWebAssembly.AssemblyInfo.cs' was recognized as generated��File 'C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\.NETCoreApp,Version=v8.0.AssemblyAttributes.cs' was recognized as generated
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
�
wC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\_Imports.razor8r�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\OverlapSymbolReferences.razor8r�
tC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Cases.razor 8r x��
sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.cs 8r
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
y
wC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\_Imports.razor�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\OverlapSymbolReferences.razor
 # 4�
tC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Cases.razor
  ( )2
    & (
   ' 

 �
sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.cs

 

 
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

2C:\Users\martin.strecker\Desktop\WebAss\Program.cs

sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.cs
namespace 
WebAss

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
�
sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.cs
 
      
    
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\BlazorWebAssembly.AssemblyInfo.csutf-8�
�C:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\obj\Debug\net8.0\.NETCoreApp,Version=v8.0.AssemblyAttributes.csutf-8|
sC:\src\work\sonar-dotnet\sonar-dotnet-shared-library\src\test\resources\RazorProtobufImporter\WebProject\Program.csutf-8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"sdk": {
"version": "8.0.202"
}
}
Loading

0 comments on commit c1b8fd7

Please sign in to comment.