Skip to content

Commit 588a65f

Browse files
committed
clang-tidy: support diagnostic notes
- add error, warning, note samples and tests - add flow support: note items - flow items are without ruleId - refactoring - close #1720 First draft without test in UI.
1 parent 68a5078 commit 588a65f

File tree

5 files changed

+123
-35
lines changed

5 files changed

+123
-35
lines changed

cxx-sensors/src/main/java/org/sonar/cxx/sensors/clangtidy/CxxClangTidySensor.java

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public class CxxClangTidySensor extends CxxIssuesReportSensor {
4343
public static final String DEFAULT_CHARSET_DEF = "UTF-8";
4444
private static final Logger LOG = Loggers.get(CxxClangTidySensor.class);
4545

46-
private static final String REGEX = "(.+|[a-zA-Z]:\\\\.+):([0-9]+):([0-9]+): ([^:]+): ([^]]+) \\[([^]]+)\\]";
46+
private static final String REGEX
47+
= "(.+|[a-zA-Z]:\\\\.+):([0-9]+):([0-9]+): ([^:]+): ([^]]+)( \\[([^]]+)\\])?";
4748
private static final Pattern PATTERN = Pattern.compile(REGEX);
4849

4950
/**
@@ -67,35 +68,47 @@ public void describe(SensorDescriptor descriptor) {
6768
@Override
6869
protected void processReport(final SensorContext context, File report) {
6970
final String reportCharset = getContextStringProperty(context,
70-
getLanguage().getPluginProperty(REPORT_CHARSET_DEF), DEFAULT_CHARSET_DEF);
71+
getLanguage().getPluginProperty(REPORT_CHARSET_DEF), DEFAULT_CHARSET_DEF);
7172
LOG.debug("Parsing 'clang-tidy' report, CharSet= '{}'", reportCharset);
7273

7374
try (Scanner scanner = new Scanner(report, reportCharset)) {
75+
// sample:
7476
// E:\Development\SonarQube\cxx\sonar-cxx\sonar-cxx-plugin\src\test\resources\org\sonar\plugins\cxx\
7577
// reports-project\clang-tidy-reports\..\..\cpd.cc:76:20:
7678
// warning: ISO C++11 does not allow conversion from string literal to 'char *'
7779
// [clang-diagnostic-writable-strings]
78-
// <path>:<line>:<column>: <level>: <message> [<checkname>]
79-
// relative paths
80-
80+
CxxReportIssue issue = null;
8181
while (scanner.hasNextLine()) {
82-
String line = scanner.nextLine();
83-
final Matcher matcher = PATTERN.matcher(line);
82+
String nextLine = scanner.nextLine();
83+
final Matcher matcher = PATTERN.matcher(nextLine);
8484
if (matcher.matches()) {
85+
// group: 1 2 3 4 5 7
86+
// <path>:<line>:<column>: <level>: <info> [<ruleId>]
8587
MatchResult m = matcher.toMatchResult();
86-
String path = m.group(1);
87-
String lineId = m.group(2);
88-
String message = m.group(5);
89-
String check = m.group(6);
88+
String path = m.group(1); // relative paths
89+
String line = m.group(2);
90+
//String column = m.group(3);
91+
String level = m.group(4); // error, warning, note, ...
92+
String info = m.group(5);
93+
String ruleId = m.group(7); // optional
9094

91-
CxxReportIssue issue = new CxxReportIssue(check, path, lineId, message);
92-
saveUniqueViolation(context, issue);
95+
if (ruleId != null) {
96+
if (issue != null) {
97+
saveUniqueViolation(context, issue);
98+
}
99+
issue = new CxxReportIssue(ruleId, path, line, info);
100+
} else if ((issue != null) && "note".equals(level)) {
101+
issue.addFlowElement(path, line, info);
102+
}
93103
}
94104
}
105+
if (issue != null) {
106+
saveUniqueViolation(context, issue);
107+
}
95108
} catch (final java.io.FileNotFoundException
96-
| java.lang.IllegalArgumentException
97-
| java.lang.IllegalStateException
98-
| java.util.InputMismatchException e) {
109+
| java.lang.IllegalArgumentException
110+
| java.lang.IllegalStateException
111+
| java.util.InputMismatchException e) {
99112
LOG.error("Failed to parse clang-tidy report: {}", e);
100113
}
101114
}

cxx-sensors/src/test/java/org/sonar/cxx/sensors/clangtidy/CxxClangTidySensorTest.java

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@
2424
import org.assertj.core.api.SoftAssertions;
2525
import org.junit.Before;
2626
import org.junit.Test;
27-
import org.mockito.Mockito;
2827
import static org.mockito.Mockito.when;
2928
import org.sonar.api.batch.fs.internal.DefaultFileSystem;
3029
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
3130
import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
3231
import org.sonar.api.batch.sensor.internal.SensorContextTester;
33-
import org.sonar.api.config.Configuration;
3432
import org.sonar.api.config.internal.MapSettings;
3533
import org.sonar.cxx.CxxLanguage;
3634
import org.sonar.cxx.sensors.utils.TestUtils;
@@ -40,22 +38,28 @@ public class CxxClangTidySensorTest {
4038
private DefaultFileSystem fs;
4139
private CxxLanguage language;
4240
private final MapSettings settings = new MapSettings();
43-
private final String[] reportName = {"clang-tidy-reports/cpd.report.txt"};
4441

4542
@Before
4643
public void setUp() {
4744
fs = TestUtils.mockFileSystem();
4845
language = TestUtils.mockCxxLanguage();
49-
when(language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY)).thenReturn("sonar.cxx." + CxxClangTidySensor.REPORT_PATH_KEY);
50-
when(language.getPluginProperty(CxxClangTidySensor.REPORT_CHARSET_DEF)).thenReturn("UTF-8");
51-
when(language.IsRecoveryEnabled()).thenReturn(Optional.of(Boolean.TRUE));
46+
47+
when(language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY)).
48+
thenReturn("sonar.cxx." + CxxClangTidySensor.REPORT_PATH_KEY);
49+
when(language.getPluginProperty(CxxClangTidySensor.REPORT_CHARSET_DEF)).
50+
thenReturn("UTF-8");
51+
when(language.IsRecoveryEnabled()).
52+
thenReturn(Optional.of(Boolean.TRUE));
5253
}
5354

5455
@Test
5556
public void shouldIgnoreIssuesIfResourceNotFound() {
5657
SensorContextTester context = SensorContextTester.create(fs.baseDir());
5758

58-
settings.setProperty(language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY), reportName[0]);
59+
settings.setProperty(
60+
language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY),
61+
"clang-tidy-reports/cpd.error.txt"
62+
);
5963
context.setSettings(settings);
6064

6165
CxxClangTidySensor sensor = new CxxClangTidySensor(language);
@@ -64,33 +68,82 @@ public void shouldIgnoreIssuesIfResourceNotFound() {
6468
}
6569

6670
@Test
67-
public void shouldReportCorrectViolations() {
71+
public void shouldReportErrors() {
6872
SensorContextTester context = SensorContextTester.create(fs.baseDir());
6973

70-
settings.setProperty(language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY), reportName[0]);
74+
settings.setProperty(
75+
language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY),
76+
"clang-tidy-reports/cpd.report-error.txt"
77+
);
7178
context.setSettings(settings);
7279

73-
context.fileSystem().add(TestInputFileBuilder.create("ProjectKey", "sources/utils/code_chunks.cpp")
74-
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n").build());
80+
context.fileSystem().add(TestInputFileBuilder
81+
.create("ProjectKey", "sources/utils/code_chunks.cpp")
82+
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n")
83+
.build());
7584

7685
CxxClangTidySensor sensor = new CxxClangTidySensor(language);
86+
sensor.execute(context);
87+
assertThat(context.allIssues()).hasSize(1);
88+
}
7789

78-
Configuration settings = Mockito.mock(Configuration.class);
79-
when(settings.getStringArray("sonar.cxx." + CxxClangTidySensor.REPORT_PATH_KEY)).thenReturn(reportName);
90+
@Test
91+
public void shouldReportWarnings() {
92+
SensorContextTester context = SensorContextTester.create(fs.baseDir());
8093

94+
settings.setProperty(
95+
language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY),
96+
"clang-tidy-reports/cpd.report-warning.txt"
97+
);
98+
context.setSettings(settings);
99+
100+
context.fileSystem().add(TestInputFileBuilder.create("ProjectKey", "sources/utils/code_chunks.cpp")
101+
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n").build());
102+
103+
CxxClangTidySensor sensor = new CxxClangTidySensor(language);
81104
sensor.execute(context);
82105
assertThat(context.allIssues()).hasSize(1);
83106
}
84107

108+
@Test
109+
public void shouldReportFlow() {
110+
SensorContextTester context = SensorContextTester.create(fs.baseDir());
111+
112+
settings.setProperty(
113+
language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY),
114+
"clang-tidy-reports/cpd.report-note.txt"
115+
);
116+
context.setSettings(settings);
117+
118+
context.fileSystem().add(TestInputFileBuilder
119+
.create("ProjectKey", "sources/utils/code_chunks.cpp")
120+
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n")
121+
.build());
122+
123+
CxxClangTidySensor sensor = new CxxClangTidySensor(language);
124+
sensor.execute(context);
125+
126+
SoftAssertions softly = new SoftAssertions();
127+
softly.assertThat(context.allIssues()).hasSize(1); // one issue
128+
softly.assertThat(context.allIssues().iterator().next().flows()).hasSize(1); // with one flow
129+
softly.assertThat(context.allIssues().iterator().next().flows().get(0).locations()).hasSize(4); // with four items
130+
softly.assertAll();
131+
}
132+
85133
@Test
86134
public void invalidReportReportsNoIssues() {
87135
SensorContextTester context = SensorContextTester.create(fs.baseDir());
88136

89-
settings.setProperty(language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY), "clang-tidy-reports/cpd.report-empty.txt");
137+
settings.setProperty(
138+
language.getPluginProperty(CxxClangTidySensor.REPORT_PATH_KEY),
139+
"clang-tidy-reports/cpd.report-empty.txt"
140+
);
90141
context.setSettings(settings);
91142

92-
context.fileSystem().add(TestInputFileBuilder.create("ProjectKey", "sources/utils/code_chunks.cpp")
93-
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n").build());
143+
context.fileSystem().add(TestInputFileBuilder
144+
.create("ProjectKey", "sources/utils/code_chunks.cpp")
145+
.setLanguage("cpp").initMetadata("asd\nasdas\nasda\n")
146+
.build());
94147

95148
CxxClangTidySensor sensor = new CxxClangTidySensor(language);
96149

@@ -105,9 +158,12 @@ public void sensorDescriptor() {
105158
sensor.describe(descriptor);
106159

107160
SoftAssertions softly = new SoftAssertions();
108-
softly.assertThat(descriptor.name()).isEqualTo(language.getName() + " ClangTidySensor");
109-
softly.assertThat(descriptor.languages()).containsOnly(language.getKey());
110-
softly.assertThat(descriptor.ruleRepositories()).containsOnly(CxxClangTidyRuleRepository.getRepositoryKey(language));
161+
softly.assertThat(descriptor.name())
162+
.isEqualTo(language.getName() + " ClangTidySensor");
163+
softly.assertThat(descriptor.languages())
164+
.containsOnly(language.getKey());
165+
softly.assertThat(descriptor.ruleRepositories())
166+
.containsOnly(CxxClangTidyRuleRepository.getRepositoryKey(language));
111167
softly.assertAll();
112168
}
113169

cxx-sensors/src/test/resources/org/sonar/cxx/sensors/reports-project/clang-tidy-reports/cpd.report.txt renamed to cxx-sensors/src/test/resources/org/sonar/cxx/sensors/reports-project/clang-tidy-reports/cpd.report-error.txt

File renamed without changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
1 warning generated.
2+
sources\utils\code_chunks.cpp:11:18: warning: Dereference of null pointer (loaded from variable 'x') [clang-analyzer-core.NullDereference]
3+
printf("%d", *x);
4+
^
5+
sources\utils\code_chunks.cpp:7:5: note: 'x' initialized to a null pointer value
6+
int* x = nullptr;
7+
^
8+
sources\utils\code_chunks.cpp:8:9: note: Assuming 'argc' is <= 1
9+
if (argc > 1) {
10+
^
11+
sources\utils\code_chunks.cpp:8:5: note: Taking false branch
12+
if (argc > 1) {
13+
^
14+
sources\utils\code_chunks.cpp:11:18: note: Dereference of null pointer (loaded from variable 'x')
15+
printf("%d", *x);
16+
^
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sources\utils\code_chunks.cpp:2:10: warning: function 'A::foo' has a definition with different parameter names [readability-inconsistent-declaration-parameter-name]
2+
void foo(int bar);
3+
^

0 commit comments

Comments
 (0)