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

Metrics analyzer for Razor: Lines of code are outside the range of the file #9299

Merged
merged 3 commits into from
May 22, 2024
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 @@ -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()) {
gregory-paidis-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
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");
gregory-paidis-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

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);
gregory-paidis-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
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);
gregory-paidis-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

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);

Choose a reason for hiding this comment

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

I find it a bit hard to understand what you are doing here.
Why do you group the lines and then do times?
From debugging they are always the unique.
How could it be that a group has more than one occurrence of each line?

Do you want to make sure exactly this? That every line appears ONLY once?

Copy link
Member Author

Choose a reason for hiding this comment

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

When multiple files with test cases are added to the context, we can have lines reported multiple times. This was the behavior before my changes when the test base class added files to the context.

Strictly speaking, this is now used only to check that each element is added only once, and the query can be simplified. I wanted to have this method able to check duplicates, so we can easily add test cases in the future.

Choose a reason for hiding this comment

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

That makes sense, thanks!

}
}
}
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
Loading