Skip to content

Commit f5c8971

Browse files
authored
SONARPY-2084: S1451 : Improve issue message (#2048)
1 parent 398f2fe commit f5c8971

11 files changed

+94
-67
lines changed

python-checks/src/main/java/org/sonar/python/checks/FileHeaderCopyrightCheck.java

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
*/
2020
package org.sonar.python.checks;
2121

22+
import java.util.Objects;
2223
import java.util.regex.Pattern;
2324
import java.util.stream.Stream;
25+
import javax.annotation.Nullable;
2426
import org.sonar.check.Rule;
2527
import org.sonar.check.RuleProperty;
2628
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
@@ -33,7 +35,8 @@
3335
public class FileHeaderCopyrightCheck extends PythonSubscriptionCheck {
3436

3537
private static final String DEFAULT_HEADER_FORMAT = "";
36-
private static final String MESSAGE = "Add or update the header of this file.";
38+
private static final String ADD_HEADER_MESSAGE = "Add a header to this file.";
39+
private static final String UPDATE_HEADER_MESSAGE = "Update the header of this file to match the expected one.";
3740

3841
@RuleProperty(
3942
key = "headerFormat",
@@ -61,38 +64,48 @@ public void initialize(Context context) {
6164
}
6265
}
6366

64-
var header = getHeaderText(ctx);
65-
var headerWithoutShebang = shebangPattern.matcher(header).replaceFirst("");
67+
if(headerFormat.isEmpty()) {
68+
return;
69+
}
6670

67-
if (isRegularExpression) {
68-
checkRegularExpression(ctx, header, headerWithoutShebang);
69-
} else if (!headerFormat.isEmpty()) {
70-
var matches = Stream.of(header, headerWithoutShebang)
71-
.anyMatch(h -> h.startsWith(headerFormat));
71+
String header = getHeaderText(ctx);
72+
String fileContent = ctx.pythonFile().content();
73+
var fileContentWithoutShebang = shebangPattern.matcher(fileContent).replaceFirst("");
7274

73-
if (!matches) {
74-
ctx.addFileIssue(MESSAGE);
75-
}
75+
if (header == null && !fileContentWithoutShebang.startsWith("#")) {
76+
ctx.addFileIssue(ADD_HEADER_MESSAGE);
77+
} else if (!isStartingWithCopyrightHeader(header, fileContentWithoutShebang)) {
78+
ctx.addFileIssue(UPDATE_HEADER_MESSAGE);
7679
}
7780
});
7881
}
7982

80-
private static String getHeaderText(SubscriptionContext ctx) {
83+
private static @Nullable String getHeaderText(SubscriptionContext ctx) {
8184
StringLiteral tokenDoc = ((FileInput) ctx.syntaxNode()).docstring();
8285
if (tokenDoc != null && tokenDoc.firstToken().line() == 1) {
8386
return tokenDoc.firstToken().value();
8487
}
85-
return ctx.pythonFile().content();
88+
return null;
89+
}
90+
91+
private boolean isStartingWithCopyrightHeader(@Nullable String header, String fileContentWithoutShebang) {
92+
if (isRegularExpression) {
93+
return isStartingWithRegexSearchPattern(header, fileContentWithoutShebang);
94+
} else {
95+
return isStartingWithNormalSearchPattern(header, fileContentWithoutShebang);
96+
}
8697
}
8798

88-
private void checkRegularExpression(SubscriptionContext ctx, String... fileContent) {
89-
var matches = Stream.of(fileContent)
99+
private boolean isStartingWithRegexSearchPattern(String... fileContent) {
100+
return Stream.of(fileContent)
101+
.filter(Objects::nonNull)
90102
.map(searchPattern::matcher)
91103
.anyMatch(matcher -> matcher.find() && matcher.start() == 0);
92-
93-
if (!matches) {
94-
ctx.addFileIssue(MESSAGE);
95-
}
96104
}
97105

106+
private boolean isStartingWithNormalSearchPattern(String... fileContent) {
107+
return Stream.of(fileContent)
108+
.filter(Objects::nonNull)
109+
.anyMatch(content -> content.startsWith(headerFormat));
110+
}
98111
}

python-checks/src/test/java/org/sonar/python/checks/FileHeaderCopyrightCheckTest.java

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@
2222
import java.io.File;
2323
import java.util.Collection;
2424
import java.util.Collections;
25+
import java.util.List;
2526
import org.assertj.core.api.Assertions;
2627
import org.junit.Assert;
2728
import org.junit.jupiter.api.Test;
29+
import org.sonar.plugins.python.api.PythonCheck;
2830
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2931
import org.sonar.plugins.python.api.PythonVisitorContext;
3032
import org.sonar.python.SubscriptionVisitor;
3133
import org.sonar.python.TestPythonVisitorRunner;
3234
import org.sonar.python.checks.utils.PythonCheckVerifier;
3335

36+
import static org.junit.jupiter.api.Assertions.assertEquals;
37+
3438
class FileHeaderCopyrightCheckTest {
3539

3640
@Test
@@ -55,57 +59,63 @@ void test_noncompliant() {
5559
void test_NoCopyright() {
5660
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/fileHeaderCopyright/headerNoCopyright.py", new FileHeaderCopyrightCheck());
5761
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/fileHeaderCopyright/emptyFileNoCopyright.py", new FileHeaderCopyrightCheck());
62+
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/fileHeaderCopyright/emptyFileWithLineBreakNoCopyright.py", new FileHeaderCopyrightCheck());
5863
}
5964

6065

6166
@Test
6267
void test_copyright_docstring() {
6368
FileHeaderCopyrightCheck fileHeaderCopyrightCheck = new FileHeaderCopyrightCheck();
64-
fileHeaderCopyrightCheck.headerFormat = "\"\"\"\n" +
65-
" SonarQube, open source software quality management tool.\n" +
66-
" Copyright (C) 2008-2018 SonarSource\n" +
67-
" mailto:contact AT sonarsource DOT com\n" +
68-
"\n" +
69-
" SonarQube is free software; you can redistribute it and/or\n" +
70-
" modify it under the terms of the GNU Lesser General Public\n" +
71-
" License as published by the Free Software Foundation; either\n" +
72-
" version 3 of the License, or (at your option) any later version.\n" +
73-
"\n" +
74-
" SonarQube is distributed in the hope that it will be useful,\n" +
75-
" but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
76-
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
77-
" Lesser General Public License for more details.\n" +
78-
"\n" +
79-
" You should have received a copy of the GNU Lesser General Public License\n" +
80-
" along with this program; if not, write to the Free Software Foundation,\n" +
81-
" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" +
82-
"\"\"\"";
69+
fileHeaderCopyrightCheck.headerFormat = """
70+
""\"
71+
SonarQube, open source software quality management tool.
72+
Copyright (C) 2008-2018 SonarSource
73+
mailto:contact AT sonarsource DOT com
74+
75+
SonarQube is free software; you can redistribute it and/or
76+
modify it under the terms of the GNU Lesser General Public
77+
License as published by the Free Software Foundation; either
78+
version 3 of the License, or (at your option) any later version.
79+
80+
SonarQube is distributed in the hope that it will be useful,
81+
but WITHOUT ANY WARRANTY; without even the implied warranty of
82+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
83+
Lesser General Public License for more details.
84+
85+
You should have received a copy of the GNU Lesser General Public License
86+
along with this program; if not, write to the Free Software Foundation,
87+
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
88+
""\"""";
8389
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/fileHeaderCopyright/docstring.py", fileHeaderCopyrightCheck);
8490
}
8591

8692
@Test
8793
void test_copyright_docstring_noncompliant() {
8894
FileHeaderCopyrightCheck fileHeaderCopyrightCheck = new FileHeaderCopyrightCheck();
89-
fileHeaderCopyrightCheck.headerFormat = "\"\"\"\n" +
90-
" SonarQube, open source software quality management tool.\n" +
91-
" Copyright (C) 2008-2018 SonarSource\n" +
92-
" mailto:contact AT sonarsource DOT com\n" +
93-
"\n" +
94-
" SonarQube is free software; you can redistribute it and/or\n" +
95-
" modify it under the terms of the GNU Lesser General Public\n" +
96-
" License as published by the Free Software Foundation; either\n" +
97-
" version 3 of the License, or (at your option) any later version.\n" +
98-
"\n" +
99-
" SonarQube is distributed in the hope that it will be useful,\n" +
100-
" but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
101-
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
102-
" Lesser General Public License for more details.\n" +
103-
"\n" +
104-
" You should have received a copy of the GNU Lesser General Public License\n" +
105-
" along with this program; if not, write to the Free Software Foundation,\n" +
106-
" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" +
107-
"\"\"\"";
95+
fileHeaderCopyrightCheck.headerFormat = """
96+
""\"
97+
SonarQube, open source software quality management tool.
98+
Copyright (C) 2008-2018 SonarSource
99+
mailto:contact AT sonarsource DOT com
100+
101+
SonarQube is free software; you can redistribute it and/or
102+
modify it under the terms of the GNU Lesser General Public
103+
License as published by the Free Software Foundation; either
104+
version 3 of the License, or (at your option) any later version.
105+
106+
SonarQube is distributed in the hope that it will be useful,
107+
but WITHOUT ANY WARRANTY; without even the implied warranty of
108+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
109+
Lesser General Public License for more details.
110+
111+
You should have received a copy of the GNU Lesser General Public License
112+
along with this program; if not, write to the Free Software Foundation,
113+
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
114+
""\"""";
108115
PythonCheckVerifier.verify("src/test/resources/checks/fileHeaderCopyright/docstringNonCompliant.py", fileHeaderCopyrightCheck);
116+
117+
List<PythonCheck.PreciseIssue> issues = PythonCheckVerifier.issues("src/test/resources/checks/fileHeaderCopyright/emptyFileNoCopyright.py", fileHeaderCopyrightCheck);
118+
assertEquals(1, issues.size());
109119
}
110120

111121
@Test
@@ -116,8 +126,10 @@ void test_searchPattern() {
116126
PythonCheckVerifier.verify("src/test/resources/checks/fileHeaderCopyright/copyrightNonCompliant.py", fileHeaderCopyrightCheck);
117127
PythonCheckVerifier.verify("src/test/resources/checks/fileHeaderCopyright/searchPatternNonCompliant.py", fileHeaderCopyrightCheck);
118128
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/fileHeaderCopyright/searchPattern.py", fileHeaderCopyrightCheck);
119-
fileHeaderCopyrightCheck.headerFormat = "";
120-
PythonCheckVerifier.verify("src/test/resources/checks/fileHeaderCopyright/searchPatternNonCompliant.py", fileHeaderCopyrightCheck);
129+
130+
List<PythonCheck.PreciseIssue> issues = PythonCheckVerifier.issues("src/test/resources/checks/fileHeaderCopyright/emptyFileNoCopyright.py",
131+
fileHeaderCopyrightCheck);
132+
assertEquals(1, issues.size());
121133
}
122134

123135
@Test

python-checks/src/test/resources/checks/fileHeaderCopyright/copyrightNonCompliant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright BAR
22

3-
# Noncompliant@-3
3+
# Noncompliant@-3 {{Update the header of this file to match the expected one.}}
44
def fun():
55
if expression:
66
pass

python-checks/src/test/resources/checks/fileHeaderCopyright/docstringNonCompliant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
along with this program; if not, write to the Free Software Foundation,
1919
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2020
"""
21-
# Noncompliant@-21
21+
# Noncompliant@-21 {{Update the header of this file to match the expected one.}}
2222
def fun():
2323
if expression:
2424
pass
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
#Noncompliant@-1
1+
2+
#Noncompliant@-2 {{Add a header to this file.}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

python-checks/src/test/resources/checks/fileHeaderCopyright/noHeaderNonCompliant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Noncompliant@-1
1+
# Noncompliant@-1 {{Update the header of this file to match the expected one.}}
22
def fun():
33
if expression:
44
pass
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# License 101
22
# Copyright 2020
33
# All rights reserved.
4-
# Noncompliant@-4
4+
# Noncompliant@-4 {{Update the header of this file to match the expected one.}}
55
def foo():
66
pass
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright 2020
22
# Another comment
33
# All rights reserved.
4-
# Noncompliant@-4
4+
# Noncompliant@-4 {{Update the header of this file to match the expected one.}}
55
def foo():
66
pass
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
# Copyright_2020
33
# All rights reserved.
4-
# Noncompliant@-4
4+
# Noncompliant@-4 {{Update the header of this file to match the expected one.}}
55
def foo():
66
pass

0 commit comments

Comments
 (0)