diff --git a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/PylintReportTest.java b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/PylintReportTest.java index 8a98ac2d04..153294b9d1 100644 --- a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/PylintReportTest.java +++ b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/PylintReportTest.java @@ -49,38 +49,42 @@ public void import_report() { @Test public void missing_report() { analyseProjectWithReport("missing"); - assertThat(issues()).hasSize(0); + assertThat(issues()).isEmpty(); } @Test public void invalid_report() { BuildResult result = analyseProjectWithReport("invalid.txt"); assertThat(result.getLogs()).contains("Cannot parse the line: trash"); - assertThat(issues()).hasSize(0); + assertThat(issues()).isEmpty(); } @Test public void unknown_rule() { BuildResult result = analyseProjectWithReport("rule-unknown.txt"); - assertThat(result.getLogs()).doesNotContain("Pylint rule 'C0102' is unknown"); - assertThat(result.getLogs()).containsOnlyOnce("Pylint rule 'C8888' is unknown"); - assertThat(result.getLogs()).containsOnlyOnce("Pylint rule 'C9999' is unknown"); - assertThat(issues()).hasSize(0); + assertThat(issues()).hasSize(4); + } + + @Test + public void multiple_reports() { + analyseProjectWithReport("pylint-report.txt, rule-unknown.txt"); + assertThat(issues()).hasSize(8); } private static List issues() { return newWsClient().issues().search(new SearchRequest().setProjects(singletonList(PROJECT))).getIssuesList(); } - private static BuildResult analyseProjectWithReport(String reportPath) { + private static BuildResult analyseProjectWithReport(String reportPaths) { ORCHESTRATOR.resetData(); ORCHESTRATOR.getServer().provisionProject(PROJECT, PROJECT); - ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT, "py", "pylint-rules"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT, "py", "no_rule"); + return ORCHESTRATOR.executeBuild( SonarScanner.create() .setDebugLogs(true) .setProjectDir(new File("projects/pylint_project")) - .setProperty("sonar.python.pylint.reportPath", reportPath)); + .setProperty("sonar.python.pylint.reportPaths", reportPaths)); } } diff --git a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/Tests.java b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/Tests.java index 62278cf0fa..0da2259c53 100644 --- a/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/Tests.java +++ b/its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/Tests.java @@ -47,6 +47,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ BanditReportTest.class, + PylintReportTest.class, MetricsTest.class, CPDTest.class, CoverageTest.class, diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/ExternalIssuesSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/ExternalIssuesSensor.java index 84e25d7d9f..42f384262c 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/ExternalIssuesSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/ExternalIssuesSensor.java @@ -21,19 +21,27 @@ import java.io.File; +import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.sonar.api.utils.log.Logger; import java.util.stream.Collectors; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.Severity; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.issue.NewExternalIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.log.Logger; import org.sonarsource.analyzer.commons.ExternalReportProvider; +import org.sonarsource.analyzer.commons.internal.json.simple.parser.ParseException; public abstract class ExternalIssuesSensor implements Sensor { private static final int MAX_LOGGED_FILE_NAMES = 20; + private static final Long DEFAULT_CONSTANT_DEBT_MINUTES = 5L; @Override public void describe(SensorDescriptor descriptor) { @@ -47,10 +55,18 @@ public void describe(SensorDescriptor descriptor) { public void execute(SensorContext context) { Set unresolvedInputFiles = new HashSet<>(); List reportFiles = ExternalReportProvider.getReportFiles(context, reportPathKey()); - reportFiles.forEach(report -> importReport(report, context, unresolvedInputFiles)); + reportFiles.forEach(report -> importExternalReport(report, context, unresolvedInputFiles)); logUnresolvedInputFiles(unresolvedInputFiles); } + private void importExternalReport(File reportPath, SensorContext context, Set unresolvedInputFiles) { + try { + importReport(reportPath, context, unresolvedInputFiles); + } catch (IOException | ParseException | RuntimeException e) { + logFileCantBeRead(e, reportPath); + } + } + private void logUnresolvedInputFiles(Set unresolvedInputFiles) { if (unresolvedInputFiles.isEmpty()) { return; @@ -62,7 +78,40 @@ private void logUnresolvedInputFiles(Set unresolvedInputFiles) { logger().warn("Failed to resolve {} file path(s) in " + linterName() + " report. No issues imported related to file(s): {}", unresolvedInputFiles.size(), fileList); } - protected abstract void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles); + private void logFileCantBeRead(Exception e, File reportPath) { + logger().error("No issues information will be saved as the report file '{}' can't be read. {}: {}" + , reportPath, e.getClass().getSimpleName(), e.getMessage()); + } + + protected void saveIssue(SensorContext context, TextReportReader.Issue issue, Set unresolvedInputFiles, String linterKey) { + InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().hasPath(issue.filePath)); + if (inputFile == null) { + unresolvedInputFiles.add(issue.filePath); + return; + } + + NewExternalIssue newExternalIssue = context.newExternalIssue(); + newExternalIssue + .type(RuleType.CODE_SMELL) + .severity(Severity.MAJOR) + .remediationEffortMinutes(DEFAULT_CONSTANT_DEBT_MINUTES); + + NewIssueLocation primaryLocation = newExternalIssue.newLocation() + .message(issue.message) + .on(inputFile); + if (issue.columnNumber != null && issue.columnNumber < inputFile.selectLine(issue.lineNumber).end().lineOffset()) { + primaryLocation.at(inputFile.newRange(issue.lineNumber, issue.columnNumber, issue.lineNumber, issue.columnNumber + 1)); + } else { + // Pylint formatted issues might not provide column information + primaryLocation.at(inputFile.selectLine(issue.lineNumber)); + } + + newExternalIssue.at(primaryLocation); + newExternalIssue.engineId(linterKey).ruleId(issue.ruleKey); + newExternalIssue.save(); + } + + protected abstract void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) throws IOException, ParseException; protected abstract String linterName(); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java index c2ccfd1613..0072e08b56 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonPlugin.java @@ -31,6 +31,8 @@ import org.sonar.plugins.python.coverage.PythonCoverageSensor; import org.sonar.plugins.python.flake8.Flake8RulesDefinition; import org.sonar.plugins.python.flake8.Flake8Sensor; +import org.sonar.plugins.python.pylint.PylintRulesDefinition; +import org.sonar.plugins.python.pylint.PylintSensor; import org.sonar.plugins.python.warnings.DefaultAnalysisWarningsWrapper; import org.sonar.plugins.python.xunit.PythonXUnitSensor; @@ -75,6 +77,7 @@ public void define(Context context) { context.addExtension(DefaultAnalysisWarningsWrapper.class); addCoberturaExtensions(context); addXUnitExtensions(context); + addPylintExtensions(context); addBanditExtensions(context); addFlake8Extensions(context); } @@ -148,6 +151,20 @@ private static void addBanditExtensions(Context context) { } } + private static void addPylintExtensions(Context context) { + context.addExtension(PylintSensor.class); + context.addExtensions( + PropertyDefinition.builder(PylintSensor.REPORT_PATH_KEY) + .name("Pylint Report Files") + .description("Paths (absolute or relative) to report files with Pylint issues.") + .category(EXTERNAL_ANALYZERS_CATEGORY) + .subCategory(PYTHON_CATEGORY) + .onQualifiers(Qualifiers.PROJECT) + .multiValues(true) + .build(), + PylintRulesDefinition.class); + } + private static void addFlake8Extensions(Context context) { context.addExtension(Flake8Sensor.class); context.addExtensions( diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TextReportReader.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TextReportReader.java new file mode 100644 index 0000000000..ada107c7a2 --- /dev/null +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TextReportReader.java @@ -0,0 +1,122 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +/** + * Common implementation to parse Flake8 and Pylint reports + */ +public class TextReportReader { + + protected static final Pattern DEFAULT_PATTERN = Pattern.compile("(.+):(\\d+):(\\d+): (\\S+[^:]):? (.*)"); + protected static final Pattern LEGACY_PATTERN = Pattern.compile("(.+):(\\d+): \\[(.*)\\] (.*)"); + private static final Logger LOG = Loggers.get(TextReportReader.class); + public static final int COLUMN_ZERO_BASED = 0; + public static final int COLUMN_ONE_BASED = 1; + + private final int reportOffset; + + public TextReportReader(int columnStartIndex) { + this.reportOffset = columnStartIndex; + } + + public List parse(File report, FileSystem fileSystem) throws IOException { + List issues = new ArrayList<>(); + try (Scanner scanner = new Scanner(report.toPath(), fileSystem.encoding().name())) { + while (scanner.hasNextLine()) { + Issue issue = parseLine(scanner.nextLine()); + if (issue != null) { + issues.add(issue); + } + } + } + return issues; + } + + private Issue parseLine(String line) { + if (line.length() > 0) { + Matcher m = TextReportReader.DEFAULT_PATTERN.matcher(line); + if (m.matches()) { + return extractDefaultStyleIssue(m, reportOffset); + } + m = TextReportReader.LEGACY_PATTERN.matcher(line); + if (m.matches()) { + return extractLegacyStyleIssue(m); + } + LOG.debug("Cannot parse the line: {}", line); + } + return null; + } + + private static Issue extractDefaultStyleIssue(Matcher m, int columnOffset) { + String filePath = m.group(1); + int lineNumber = Integer.parseInt(m.group(2)); + int columnNumber = Integer.parseInt(m.group(3)); + // Flake8 column numbering starts at 1 + columnNumber -= columnOffset; + String ruleKey = m.group(4); + String message = m.group(5); + return new Issue(filePath, ruleKey, message, lineNumber, columnNumber); + } + + private static Issue extractLegacyStyleIssue(Matcher m) { + String filePath = m.group(1); + int lineNumber = Integer.parseInt(m.group(2)); + String ruleKey = m.group(3); + int lastIndex = Math.min(ruleKey.indexOf(","), ruleKey.indexOf("(")); + if (lastIndex > 0) { + ruleKey = ruleKey.substring(0, lastIndex); + } + String message = m.group(4); + return new Issue(filePath, ruleKey, message, lineNumber, null); + } + + public static class Issue { + + public final String filePath; + + public final String ruleKey; + + public final String message; + + public final Integer lineNumber; + + public final Integer columnNumber; + + public Issue(String filePath, String ruleKey, String message, Integer lineNumber, @Nullable Integer columnNumber) { + this.filePath = filePath; + this.ruleKey = ruleKey; + this.message = message; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + } +} diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/bandit/BanditSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/bandit/BanditSensor.java index 352ebb5576..6c3cf60806 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/bandit/BanditSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/bandit/BanditSensor.java @@ -51,15 +51,11 @@ public class BanditSensor extends ExternalIssuesSensor { private static final Long DEFAULT_CONSTANT_DEBT_MINUTES = 5L; @Override - protected void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) { - try (InputStream in = new FileInputStream(reportPath)) { - LOG.info("Importing {}", reportPath); - boolean engineIdIsSupported = context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 4)); - BanditJsonReportReader.read(in, issue -> saveIssue(context, issue, unresolvedInputFiles, engineIdIsSupported)); - } catch (IOException | ParseException | RuntimeException e) { - LOG.error("No issues information will be saved as the report file '{}' can't be read. " + - e.getClass().getSimpleName() + ": " + e.getMessage(), reportPath, e); - } + protected void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) throws IOException, ParseException { + InputStream in = new FileInputStream(reportPath); + LOG.info("Importing {}", reportPath); + boolean engineIdIsSupported = context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 4)); + BanditJsonReportReader.read(in, issue -> saveIssue(context, issue, unresolvedInputFiles, engineIdIsSupported)); } private static void saveIssue(SensorContext context, Issue issue, Set unresolvedInputFiles, boolean engineIdIsSupported) { diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8ReportReader.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8ReportReader.java deleted file mode 100644 index 70fc64d80b..0000000000 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8ReportReader.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube Python Plugin - * Copyright (C) 2011-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.python.flake8; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nullable; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -public class Flake8ReportReader { - - private static final Logger LOG = Loggers.get(Flake8ReportReader.class); - private static final Pattern DEFAULT_PATTERN = Pattern.compile("(.+):(\\d+):(\\d+): (\\S+) (.*)"); - private static final Pattern PYLINT_PATTERN = Pattern.compile("(.+):(\\d+): \\[(.*)\\] (.*)"); - - public List parse(File report, FileSystem fileSystem) throws IOException { - List issues = new ArrayList<>(); - try (Scanner scanner = new Scanner(report.toPath(), fileSystem.encoding().name())) { - while (scanner.hasNextLine()) { - Issue issue = parseLine(scanner.nextLine()); - if (issue != null) { - issues.add(issue); - } - } - } - return issues; - } - - private static Issue parseLine(String line) { - - if (line.length() > 0) { - if (!startsWithWhitespace(line)) { - Matcher m = DEFAULT_PATTERN.matcher(line); - if (m.matches()) { - String filePath = m.group(1); - int lineNumber = Integer.parseInt(m.group(2)); - int columnNumber = Integer.parseInt(m.group(3)); - String ruleKey = m.group(4); - String message = m.group(5); - return new Issue(filePath, ruleKey, message, lineNumber, columnNumber); - } - m = PYLINT_PATTERN.matcher(line); - if (m.matches()) { - String filePath = m.group(1); - int lineNumber = Integer.parseInt(m.group(2)); - String ruleKey = m.group(3); - String message = m.group(4); - return new Issue(filePath, ruleKey, message, lineNumber, null); - } - LOG.debug("Cannot parse the line: {}", line); - } else { - LOG.debug("Classifying as detail and ignoring line '{}'", line); - } - } - return null; - } - - private static boolean startsWithWhitespace(String line) { - char first = line.charAt(0); - return first == ' ' || first == '\t' || first == '\n'; - } - - public static class Issue { - - String filePath; - - String ruleKey; - - String message; - - Integer lineNumber; - - Integer columnNumber; - - public Issue(String filePath, String ruleKey, String message, Integer lineNumber, @Nullable Integer columnNumber) { - this.filePath = filePath; - this.ruleKey = ruleKey; - this.message = message; - this.lineNumber = lineNumber; - this.columnNumber = columnNumber; - } - } -} diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8Sensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8Sensor.java index d87f5aa541..365b6af6b6 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8Sensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/flake8/Flake8Sensor.java @@ -23,15 +23,12 @@ import java.io.IOException; import java.util.List; import java.util.Set; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.rule.Severity; import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.batch.sensor.issue.NewExternalIssue; -import org.sonar.api.batch.sensor.issue.NewIssueLocation; -import org.sonar.api.rules.RuleType; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.plugins.python.ExternalIssuesSensor; +import org.sonar.plugins.python.TextReportReader; +import org.sonar.plugins.python.TextReportReader.Issue; public class Flake8Sensor extends ExternalIssuesSensor { @@ -41,46 +38,10 @@ public class Flake8Sensor extends ExternalIssuesSensor { public static final String LINTER_KEY = "flake8"; public static final String REPORT_PATH_KEY = "sonar.python.flake8.reportPaths"; - private static final Long DEFAULT_CONSTANT_DEBT_MINUTES = 5L; - @Override - protected void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) { - try { - List issues = new Flake8ReportReader().parse(reportPath, context.fileSystem()); - issues.forEach(i -> saveIssue(context, i, unresolvedInputFiles)); - } catch (IOException e) { - LOG.error("No issues information will be saved as the report file '{}' can't be read. " + - e.getClass().getSimpleName() + ": " + e.getMessage(), reportPath, e); - } - } - - private static void saveIssue(SensorContext context, Flake8ReportReader.Issue issue, Set unresolvedInputFiles) { - InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().hasPath(issue.filePath)); - if (inputFile == null) { - unresolvedInputFiles.add(issue.filePath); - return; - } - - NewExternalIssue newExternalIssue = context.newExternalIssue(); - newExternalIssue - .type(RuleType.CODE_SMELL) - .severity(Severity.MAJOR) - .remediationEffortMinutes(DEFAULT_CONSTANT_DEBT_MINUTES); - - NewIssueLocation primaryLocation = newExternalIssue.newLocation() - .message(issue.message) - .on(inputFile); - if (issue.columnNumber != null && issue.columnNumber < inputFile.selectLine(issue.lineNumber).end().lineOffset() + 1) { - inputFile.selectLine(issue.lineNumber).end().lineOffset(); - primaryLocation.at(inputFile.newRange(issue.lineNumber, issue.columnNumber - 1, issue.lineNumber, issue.columnNumber)); - } else { - // Pylint formatted issues don't provide column information - primaryLocation.at(inputFile.selectLine(issue.lineNumber)); - } - - newExternalIssue.at(primaryLocation); - newExternalIssue.engineId(LINTER_KEY).ruleId(issue.ruleKey); - newExternalIssue.save(); + protected void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) throws IOException { + List issues = new TextReportReader(TextReportReader.COLUMN_ONE_BASED).parse(reportPath, context.fileSystem()); + issues.forEach(i -> saveIssue(context, i, unresolvedInputFiles, LINTER_KEY)); } @Override diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintRulesDefinition.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintRulesDefinition.java new file mode 100644 index 0000000000..83117d6e04 --- /dev/null +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintRulesDefinition.java @@ -0,0 +1,36 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.pylint; + +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.plugins.python.Python; +import org.sonarsource.analyzer.commons.ExternalRuleLoader; + +public class PylintRulesDefinition implements RulesDefinition { + + private static final String RULES_JSON = "org/sonar/plugins/python/pylint/rules.json"; + private static final ExternalRuleLoader RULE_LOADER = new ExternalRuleLoader(PylintSensor.LINTER_KEY, PylintSensor.LINTER_NAME, RULES_JSON, Python.KEY); + + + @Override + public void define(RulesDefinition.Context context) { + RULE_LOADER.createExternalRuleRepository(context); + } +} diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintSensor.java new file mode 100644 index 0000000000..1e808985eb --- /dev/null +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/pylint/PylintSensor.java @@ -0,0 +1,61 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.pylint; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.plugins.python.ExternalIssuesSensor; +import org.sonar.plugins.python.TextReportReader; +import org.sonar.plugins.python.TextReportReader.Issue; + +public class PylintSensor extends ExternalIssuesSensor { + + private static final Logger LOG = Loggers.get(PylintSensor.class); + + public static final String LINTER_NAME = "Pylint"; + public static final String LINTER_KEY = "pylint"; + public static final String REPORT_PATH_KEY = "sonar.python.pylint.reportPaths"; + + @Override + protected void importReport(File reportPath, SensorContext context, Set unresolvedInputFiles) throws IOException { + List issues = new TextReportReader(TextReportReader.COLUMN_ZERO_BASED).parse(reportPath, context.fileSystem()); + issues.forEach(i -> saveIssue(context, i, unresolvedInputFiles, LINTER_KEY)); + } + + @Override + protected String reportPathKey() { + return REPORT_PATH_KEY; + } + + @Override + protected String linterName() { + return LINTER_NAME; + } + + @Override + protected Logger logger() { + return LOG; + } +} diff --git a/sonar-python-plugin/src/main/resources/org/sonar/plugins/python/pylint/rules.json b/sonar-python-plugin/src/main/resources/org/sonar/plugins/python/pylint/rules.json new file mode 100644 index 0000000000..814ba3903e --- /dev/null +++ b/sonar-python-plugin/src/main/resources/org/sonar/plugins/python/pylint/rules.json @@ -0,0 +1,2620 @@ +[ + { + "configKey": "C0102", + "constantDebtMinutes": 1, + "key": "C0102", + "name": "Black listed name", + "priority": "MINOR" + }, + { + "configKey": "C0103", + "constantDebtMinutes": 1, + "key": "C0103", + "name": "Invalid name", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0111", + "constantDebtMinutes": 5, + "key": "C0111", + "name": "Missing docstring", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0112", + "constantDebtMinutes": 5, + "key": "C0112", + "name": "Empty docstring", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0113", + "constantDebtMinutes": 1, + "key": "C0113", + "name": "Useless negation", + "priority": "MINOR" + }, + { + "configKey": "C0121", + "constantDebtMinutes": 20, + "key": "C0121", + "name": "Singleton comparison", + "priority": "MINOR" + }, + { + "configKey": "C0122", + "constantDebtMinutes": 1, + "key": "C0122", + "name": "Misplaced comparison constant", + "priority": "MINOR" + }, + { + "configKey": "C0123", + "constantDebtMinutes": 15, + "key": "C0123", + "name": "Using type() instead of isinstance() for a typecheck.", + "priority": "MINOR" + }, + { + "configKey": "C0200", + "constantDebtMinutes": 20, + "key": "C0200", + "name": "Consider using enumerate instead of iterating with range and len", + "priority": "MINOR" + }, + { + "configKey": "C0201", + "constantDebtMinutes": 20, + "key": "C0201", + "name": "Consider iterating the dictionary directly instead of calling .keys()", + "priority": "MINOR" + }, + { + "configKey": "C0202", + "constantDebtMinutes": 10, + "key": "C0202", + "name": "Class method should have \"cls\" as first argument", + "priority": "MINOR" + }, + { + "configKey": "C0203", + "constantDebtMinutes": 10, + "key": "C0203", + "name": "Metaclass method should have \"mcs\" as first argument", + "priority": "MINOR" + }, + { + "configKey": "C0204", + "constantDebtMinutes": 20, + "key": "C0204", + "name": "Metaclass class method first argument", + "priority": "MINOR" + }, + { + "configKey": "C0205", + "constantDebtMinutes": 15, + "key": "C0205", + "name": "Class __slots__ should be a non-string iterable", + "priority": "MINOR" + }, + { + "configKey": "C0301", + "constantDebtMinutes": 1, + "key": "C0301", + "name": "Line too long", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0302", + "constantDebtMinutes": 30, + "key": "C0302", + "name": "Too many lines in module", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0303", + "constantDebtMinutes": 60, + "key": "C0303", + "name": "Trailing whitespace", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0304", + "constantDebtMinutes": 10, + "key": "C0304", + "name": "Final newline missing", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0305", + "constantDebtMinutes": 1, + "key": "C0305", + "name": "Trailing newlines", + "priority": "MINOR" + }, + { + "configKey": "C0321", + "constantDebtMinutes": 5, + "key": "C0321", + "name": "More than one statement on a single line", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0322", + "constantDebtMinutes": 1, + "key": "C0322", + "name": "Operator not preceded by a space", + "priority": "MINOR" + }, + { + "configKey": "C0323", + "constantDebtMinutes": 1, + "key": "C0323", + "name": "Operator not followed by a space", + "priority": "MINOR" + }, + { + "configKey": "C0324", + "constantDebtMinutes": 1, + "key": "C0324", + "name": "Comma not followed by a space", + "priority": "MINOR" + }, + { + "configKey": "C0325", + "constantDebtMinutes": 5, + "key": "C0325", + "name": "Unnecessary parentheses", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "C0326", + "constantDebtMinutes": 1, + "key": "C0326", + "name": "Wrong number of spaces around an operator, bracket, or comma, or before a block opener", + "priority": "MINOR" + }, + { + "configKey": "C0327", + "constantDebtMinutes": 15, + "key": "C0327", + "name": "Mixed line endings LF and CRLF", + "priority": "MINOR" + }, + { + "configKey": "C0328", + "constantDebtMinutes": 1, + "key": "C0328", + "name": "Unexpected line ending format", + "priority": "MINOR" + }, + { + "configKey": "C0330", + "constantDebtMinutes": 5, + "key": "C0330", + "name": "Bad continuation", + "priority": "MINOR" + }, + { + "configKey": "C0401", + "constantDebtMinutes": 1, + "key": "C0401", + "name": "Wrong spelling of a word in a comment", + "priority": "MINOR" + }, + { + "configKey": "C0402", + "constantDebtMinutes": 1, + "key": "C0402", + "name": "Wrong spelling of a word in a docstring", + "priority": "MINOR" + }, + { + "configKey": "C0403", + "constantDebtMinutes": 1, + "key": "C0403", + "name": "Invalid characters in a docstring", + "priority": "MINOR" + }, + { + "configKey": "C0410", + "constantDebtMinutes": 5, + "key": "C0410", + "name": "Multiple imports on one line", + "priority": "MINOR" + }, + { + "configKey": "C0411", + "constantDebtMinutes": 5, + "key": "C0411", + "name": "Wrong import order", + "priority": "MINOR" + }, + { + "configKey": "C0412", + "constantDebtMinutes": 5, + "key": "C0412", + "name": "Ungrouped imports", + "priority": "MINOR" + }, + { + "configKey": "C0413", + "constantDebtMinutes": 5, + "key": "C0413", + "name": "Wrong import position", + "priority": "MINOR" + }, + { + "configKey": "C1001", + "constantDebtMinutes": 20, + "key": "C1001", + "name": "Old-style class defined.", + "priority": "MINOR" + }, + { + "configKey": "C0414", + "constantDebtMinutes": 5, + "key": "C0414", + "name": "Import alias does not rename original package", + "priority": "MINOR" + }, + { + "configKey": "C1801", + "constantDebtMinutes": 20, + "key": "C1801", + "name": "Do not use `len(SEQUENCE)` to determine if a sequence is empty", + "priority": "MINOR" + }, + { + "configKey": "E0001", + "constantDebtMinutes": 5, + "key": "E0001", + "name": "Syntax error", + "priority": "MAJOR" + }, + { + "configKey": "E0011", + "constantDebtMinutes": 5, + "key": "E0011", + "name": "Unrecognized file option", + "priority": "MAJOR" + }, + { + "configKey": "E0012", + "constantDebtMinutes": 5, + "key": "E0012", + "name": "Bad option value", + "priority": "MAJOR" + }, + { + "configKey": "E0100", + "constantDebtMinutes": 30, + "key": "E0100", + "name": "__init__ method is a generator", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0101", + "constantDebtMinutes": 5, + "key": "E0101", + "name": "Explicit return in __init__", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0102", + "constantDebtMinutes": 15, + "key": "E0102", + "name": "Redefined function/class/method", + "priority": "MAJOR" + }, + { + "configKey": "E0103", + "constantDebtMinutes": 15, + "key": "E0103", + "name": "Usage of 'break' or 'continue' outside of a loop", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0104", + "constantDebtMinutes": 5, + "key": "E0104", + "name": "Return outside function", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0105", + "constantDebtMinutes": 10, + "key": "E0105", + "name": "Yield outside function", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0106", + "constantDebtMinutes": 15, + "key": "E0106", + "name": "Return with argument inside generator", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0107", + "constantDebtMinutes": 3, + "key": "E0107", + "name": "Use of a non-existent operator", + "priority": "MAJOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0108", + "constantDebtMinutes": 5, + "key": "E0108", + "name": "Duplicate argument name in function definition", + "priority": "MINOR" + }, + { + "configKey": "E0109", + "constantDebtMinutes": 10, + "key": "E0109", + "name": "Missing argument to reversed()", + "priority": "MINOR" + }, + { + "configKey": "E0110", + "constantDebtMinutes": 5, + "key": "E0110", + "name": "Abstract class instantiated", + "priority": "MINOR" + }, + { + "configKey": "E0111", + "constantDebtMinutes": 10, + "key": "E0111", + "name": "The first reversed() argument is not a sequence", + "priority": "MINOR" + }, + { + "configKey": "E0112", + "constantDebtMinutes": 5, + "key": "E0112", + "name": "More than one starred expression in assignment", + "priority": "MAJOR" + }, + { + "configKey": "E0113", + "constantDebtMinutes": 5, + "key": "E0113", + "name": "Starred assignment target must be in a list or tuple", + "priority": "MAJOR" + }, + { + "configKey": "E0114", + "constantDebtMinutes": 5, + "key": "E0114", + "name": "Can use starred expression only in assignment target", + "priority": "MAJOR" + }, + { + "configKey": "E0115", + "constantDebtMinutes": 15, + "key": "E0115", + "name": "Name is nonlocal and global", + "priority": "MAJOR" + }, + { + "configKey": "E0116", + "constantDebtMinutes": 15, + "key": "E0116", + "name": "'continue' not supported inside 'finally' clause", + "priority": "MINOR" + }, + { + "configKey": "E0117", + "constantDebtMinutes": 5, + "key": "E0117", + "name": "Nonlocal name found without binding", + "priority": "MAJOR" + }, + { + "configKey": "E0119", + "constantDebtMinutes": 5, + "key": "E0119", + "name": "Format function is not called on str", + "priority": "MINOR" + }, + { + "configKey": "E0202", + "constantDebtMinutes": 15, + "key": "E0202", + "name": "Method hidden by attribute of super class", + "priority": "MAJOR" + }, + { + "configKey": "E0203", + "constantDebtMinutes": 15, + "key": "E0203", + "name": "Access to member before its definition", + "priority": "MAJOR" + }, + { + "configKey": "E0211", + "constantDebtMinutes": 10, + "key": "E0211", + "name": "Method has no argument", + "priority": "MAJOR" + }, + { + "configKey": "E0213", + "constantDebtMinutes": 5, + "key": "E0213", + "name": "Method should have \"self\" as first argument", + "priority": "MAJOR" + }, + { + "configKey": "E0221", + "constantDebtMinutes": 20, + "key": "E0221", + "name": "Implemented interface must be a class", + "priority": "MAJOR" + }, + { + "configKey": "E0222", + "constantDebtMinutes": 30, + "key": "E0222", + "name": "Missing method from interface", + "priority": "MAJOR" + }, + { + "configKey": "E0235", + "constantDebtMinutes": 10, + "key": "E0235", + "name": "__exit__ must accept 3 arguments: type, value, traceback", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "E0236", + "constantDebtMinutes": 10, + "key": "E0236", + "name": "Invalid object in __slots__, must contain only non empty strings", + "priority": "MINOR" + }, + { + "configKey": "E0237", + "constantDebtMinutes": 10, + "key": "E0237", + "name": "Assigning to attribute not defined in class slots", + "priority": "MINOR" + }, + { + "configKey": "E0238", + "constantDebtMinutes": 20, + "key": "E0238", + "name": "Invalid __slots__ object", + "priority": "MINOR" + }, + { + "configKey": "E0239", + "constantDebtMinutes": 20, + "key": "E0239", + "name": "Inheriting from non-class", + "priority": "MINOR" + }, + { + "configKey": "E0240", + "constantDebtMinutes": 20, + "key": "E0240", + "name": "Inconsistent method resolution order", + "priority": "MINOR" + }, + { + "configKey": "E0241", + "constantDebtMinutes": 20, + "key": "E0241", + "name": "Duplicate bases", + "priority": "MINOR" + }, + { + "configKey": "E0301", + "constantDebtMinutes": 10, + "key": "E0301", + "name": "__iter__ returns non-iterator", + "priority": "MINOR" + }, + { + "configKey": "E0302", + "constantDebtMinutes": 20, + "key": "E0302", + "name": "Unexpected special method signature", + "priority": "MINOR" + }, + { + "configKey": "E0303", + "constantDebtMinutes": 20, + "key": "E0303", + "name": "__len__ does not return non-negative integer", + "priority": "MINOR" + }, + { + "configKey": "E0401", + "constantDebtMinutes": 10, + "key": "E0401", + "name": "Import error", + "priority": "MINOR" + }, + { + "configKey": "E0402", + "constantDebtMinutes": 20, + "key": "E0402", + "name": "Attempted relative import beyond top-level package", + "priority": "MINOR" + }, + { + "configKey": "E0501", + "constantDebtMinutes": 5, + "key": "E0501", + "name": "Non-ASCII characters found but no encoding specified (PEP 263)", + "priority": "MAJOR" + }, + { + "configKey": "E0502", + "constantDebtMinutes": 5, + "key": "E0502", + "name": "Wrong encoding specified", + "priority": "MAJOR" + }, + { + "configKey": "E0503", + "constantDebtMinutes": 10, + "key": "E0503", + "name": "Unknown encoding specified", + "priority": "MAJOR" + }, + { + "configKey": "E0601", + "constantDebtMinutes": 15, + "key": "E0601", + "name": "Using variable before assignment", + "priority": "MAJOR" + }, + { + "configKey": "E0602", + "constantDebtMinutes": 20, + "key": "E0602", + "name": "Undefined variable", + "priority": "MAJOR" + }, + { + "configKey": "E0603", + "constantDebtMinutes": 20, + "key": "E0603", + "name": "Undefined variable name in __all__", + "priority": "MINOR" + }, + { + "configKey": "E0604", + "constantDebtMinutes": 3, + "key": "E0604", + "name": "Invalid object in __all__, must contain only strings", + "priority": "MINOR" + }, + { + "configKey": "E0611", + "constantDebtMinutes": 10, + "key": "E0611", + "name": "Undefined name in module", + "priority": "MAJOR" + }, + { + "configKey": "E0632", + "constantDebtMinutes": 20, + "key": "E0632", + "name": "Unbalanced tuple unpacking", + "priority": "MINOR" + }, + { + "configKey": "E0633", + "constantDebtMinutes": 20, + "key": "E0633", + "name": "Attempting to unpack a non-sequence", + "priority": "MINOR" + }, + { + "configKey": "E0701", + "constantDebtMinutes": 30, + "key": "E0701", + "name": "Bad except clauses order", + "priority": "MAJOR" + }, + { + "configKey": "E0702", + "constantDebtMinutes": 20, + "key": "E0702", + "name": "Raising only allowed for classes, instances or strings", + "priority": "MAJOR" + }, + { + "configKey": "E0703", + "constantDebtMinutes": 5, + "key": "E0703", + "name": "Exception context set to something which is not an exception, nor None", + "priority": "MINOR" + }, + { + "configKey": "E0704", + "constantDebtMinutes": 10, + "key": "E0704", + "name": "The raise statement is not inside an except clause", + "priority": "MAJOR" + }, + { + "configKey": "E0710", + "constantDebtMinutes": 15, + "key": "E0710", + "name": "Raising a new style class which doesn't inherit from BaseException", + "priority": "MAJOR" + }, + { + "configKey": "E0711", + "constantDebtMinutes": 5, + "key": "E0711", + "name": "NotImplemented raised - should raise NotImplementedError", + "priority": "MAJOR" + }, + { + "configKey": "E0712", + "constantDebtMinutes": 20, + "key": "E0712", + "name": "Catching an exception which doesn't inherit from Exception", + "priority": "MINOR" + }, + { + "configKey": "E1001", + "constantDebtMinutes": 10, + "key": "E1001", + "name": "Use of __slots__ on an old style class", + "priority": "MAJOR" + }, + { + "configKey": "E1002", + "constantDebtMinutes": 10, + "key": "E1002", + "name": "Use of super on an old style class", + "priority": "MAJOR" + }, + { + "configKey": "E1003", + "constantDebtMinutes": 10, + "key": "E1003", + "name": "Bad first argument given to super", + "priority": "MAJOR" + }, + { + "configKey": "E1004", + "constantDebtMinutes": 10, + "key": "E1004", + "name": "Missing argument to super()", + "priority": "MINOR" + }, + { + "configKey": "E1101", + "constantDebtMinutes": 10, + "key": "E1101", + "name": "Access of nonexistent member", + "priority": "MAJOR" + }, + { + "configKey": "E1102", + "constantDebtMinutes": 3, + "key": "E1102", + "name": "Calling of not callable", + "priority": "MAJOR" + }, + { + "configKey": "E1103", + "constantDebtMinutes": 10, + "key": "E1103", + "name": "Accessing nonexistent member (type information incomplete)", + "priority": "MAJOR" + }, + { + "configKey": "E1111", + "constantDebtMinutes": 20, + "key": "E1111", + "name": "Assigning result of a function call, where the function has no return", + "priority": "MAJOR" + }, + { + "configKey": "E1120", + "constantDebtMinutes": 5, + "key": "E1120", + "name": "Too few arguments", + "priority": "MAJOR" + }, + { + "configKey": "E1121", + "constantDebtMinutes": 5, + "key": "E1121", + "name": "Too many positional arguments for function call", + "priority": "MAJOR" + }, + { + "configKey": "E1122", + "constantDebtMinutes": 3, + "key": "E1122", + "name": "Duplicate keyword argument in function call", + "priority": "MAJOR" + }, + { + "configKey": "E1123", + "constantDebtMinutes": 3, + "key": "E1123", + "name": "Passing unexpected keyword argument in function call", + "priority": "MAJOR" + }, + { + "configKey": "E1124", + "constantDebtMinutes": 5, + "key": "E1124", + "name": "Multiple values passed for parameter in function call", + "priority": "MAJOR" + }, + { + "configKey": "E1125", + "constantDebtMinutes": 5, + "key": "E1125", + "name": "Missing mandatory keyword argument in call", + "priority": "MAJOR" + }, + { + "configKey": "E1126", + "constantDebtMinutes": 15, + "key": "E1126", + "name": "Sequence index is not an int, slice, or instance with __index__", + "priority": "MAJOR" + }, + { + "configKey": "E1127", + "constantDebtMinutes": 15, + "key": "E1127", + "name": "Slice index is not an int, None, or instance with __index__", + "priority": "MAJOR" + }, + { + "configKey": "E1128", + "constantDebtMinutes": 15, + "key": "E1128", + "name": "Assigning result of a function call, where the function returns None", + "priority": "MAJOR" + }, + { + "configKey": "E1129", + "constantDebtMinutes": 15, + "key": "E1129", + "name": "Context manager doesn't implement __enter__ and __exit__", + "priority": "MAJOR" + }, + { + "configKey": "E1130", + "constantDebtMinutes": 15, + "key": "E1130", + "name": "Invalid unary operand type", + "priority": "MAJOR" + }, + { + "configKey": "E1131", + "constantDebtMinutes": 15, + "key": "E1131", + "name": "Unsupported binary operation", + "priority": "MAJOR" + }, + { + "configKey": "E1132", + "constantDebtMinutes": 15, + "key": "E1132", + "name": "Multiple values for keyword argument", + "priority": "MAJOR" + }, + { + "configKey": "E1133", + "constantDebtMinutes": 15, + "key": "E1133", + "name": "Non-iterable value used in an iterating context", + "priority": "MAJOR" + }, + { + "configKey": "E1134", + "constantDebtMinutes": 15, + "key": "E1134", + "name": "Non-mapping value used in a mapping context", + "priority": "MAJOR" + }, + { + "configKey": "E1135", + "constantDebtMinutes": 15, + "key": "E1135", + "name": "Unsupported membership test", + "priority": "MAJOR" + }, + { + "configKey": "E1136", + "constantDebtMinutes": 15, + "key": "E1136", + "name": "Subscripted value doesn't support subscription", + "priority": "MAJOR" + }, + { + "configKey": "E1137", + "constantDebtMinutes": 15, + "key": "E1137", + "name": "Object does not support item assignment", + "priority": "MAJOR" + }, + { + "configKey": "E1138", + "constantDebtMinutes": 15, + "key": "E1138", + "name": "Object does not support item deletion", + "priority": "MAJOR" + }, + { + "configKey": "E1139", + "constantDebtMinutes": 15, + "key": "E1139", + "name": "Invalid metaclass used", + "priority": "MAJOR" + }, + { + "configKey": "E1140", + "constantDebtMinutes": 5, + "key": "E1140", + "name": "Dict key is unhashable", + "priority": "MAJOR" + }, + { + "configKey": "E1200", + "constantDebtMinutes": 10, + "key": "E1200", + "name": "Unsupported logging format character", + "priority": "MAJOR" + }, + { + "configKey": "E1201", + "constantDebtMinutes": 10, + "key": "E1201", + "name": "Logging format string ends in middle of conversion specifier", + "priority": "MAJOR" + }, + { + "configKey": "E1205", + "constantDebtMinutes": 10, + "key": "E1205", + "name": "Too many arguments for logging format string", + "priority": "MAJOR" + }, + { + "configKey": "E1206", + "constantDebtMinutes": 10, + "key": "E1206", + "name": "Not enough arguments for logging format string", + "priority": "MAJOR" + }, + { + "configKey": "E1300", + "constantDebtMinutes": 3, + "key": "E1300", + "name": "Unsupported format character", + "priority": "MAJOR" + }, + { + "configKey": "E1301", + "constantDebtMinutes": 3, + "key": "E1301", + "name": "Format string ends in middle of conversion specifier", + "priority": "MAJOR" + }, + { + "configKey": "E1302", + "constantDebtMinutes": 5, + "key": "E1302", + "name": "Mixing named and unnamed conversion specifiers in format string", + "priority": "MAJOR" + }, + { + "configKey": "E1303", + "constantDebtMinutes": 10, + "key": "E1303", + "name": "Expected mapping for format string", + "priority": "MAJOR" + }, + { + "configKey": "E1304", + "constantDebtMinutes": 10, + "key": "E1304", + "name": "Missing key in format string dictionary", + "priority": "MAJOR" + }, + { + "configKey": "E1305", + "constantDebtMinutes": 5, + "key": "E1305", + "name": "Too many arguments for format string", + "priority": "MAJOR" + }, + { + "configKey": "E1306", + "constantDebtMinutes": 5, + "key": "E1306", + "name": "Not enough arguments for format string", + "priority": "MAJOR" + }, + { + "configKey": "E1307", + "constantDebtMinutes": 5, + "key": "E1307", + "name": "Argument does not match format type", + "priority": "MINOR" + }, + { + "configKey": "E1310", + "constantDebtMinutes": 10, + "key": "E1310", + "name": "Suspicious argument in lstrip/rstrip", + "priority": "MINOR" + }, + { + "configKey": "E1507", + "constantDebtMinutes": 5, + "key": "E1507", + "name": "Env manipulation functions does not support type argument", + "priority": "MINOR" + }, + { + "configKey": "E1601", + "constantDebtMinutes": 5, + "key": "E1601", + "name": "print statement used", + "priority": "MAJOR" + }, + { + "configKey": "E1602", + "constantDebtMinutes": 10, + "key": "E1602", + "name": "Parameter unpacking specified", + "priority": "MAJOR" + }, + { + "configKey": "E1603", + "constantDebtMinutes": 10, + "key": "E1603", + "name": "Implicit unpacking of exceptions is not supported in Python 3", + "priority": "MAJOR" + }, + { + "configKey": "E1604", + "constantDebtMinutes": 5, + "key": "E1604", + "name": "Use raise ErrorClass(args) instead of raise ErrorClass, args.", + "priority": "MAJOR" + }, + { + "configKey": "E1605", + "constantDebtMinutes": 5, + "key": "E1605", + "name": "Use of the `` operator", + "priority": "MAJOR" + }, + { + "configKey": "E1606", + "constantDebtMinutes": 10, + "key": "E1606", + "name": "Use of long suffix", + "priority": "MAJOR" + }, + { + "configKey": "E1607", + "constantDebtMinutes": 5, + "key": "E1607", + "name": "Use of the <> operator", + "priority": "MAJOR" + }, + { + "configKey": "E1608", + "constantDebtMinutes": 3, + "key": "E1608", + "name": "Use of old octal literal", + "priority": "MAJOR" + }, + { + "configKey": "E1609", + "constantDebtMinutes": 10, + "key": "E1609", + "name": "Import * only allowed at module level", + "priority": "MAJOR" + }, + { + "configKey": "E1610", + "constantDebtMinutes": 10, + "key": "E1610", + "name": "Non-ascii bytes literals not supported in 3.x", + "priority": "MAJOR" + }, + { + "configKey": "E1700", + "constantDebtMinutes": 15, + "key": "E1700", + "name": "Yield inside async function", + "priority": "MAJOR" + }, + { + "configKey": "E1701", + "constantDebtMinutes": 5, + "key": "E1701", + "name": "Async context manager doesn't implement __aenter__ and __aexit__", + "priority": "MINOR" + }, + { + "configKey": "F0001", + "constantDebtMinutes": 15, + "key": "F0001", + "name": "Analysis failed", + "priority": "MAJOR" + }, + { + "configKey": "F0002", + "constantDebtMinutes": 15, + "key": "F0002", + "name": "Internal Pylint error", + "priority": "MAJOR" + }, + { + "configKey": "F0003", + "constantDebtMinutes": 15, + "key": "F0003", + "name": "Ignored builtin module", + "priority": "MAJOR" + }, + { + "configKey": "F0004", + "constantDebtMinutes": 15, + "key": "F0004", + "name": "Unexpected inferred value", + "priority": "MAJOR" + }, + { + "configKey": "F0010", + "constantDebtMinutes": 15, + "key": "F0010", + "name": "Error while code parsing", + "priority": "MAJOR" + }, + { + "configKey": "F0202", + "constantDebtMinutes": 15, + "key": "F0202", + "name": "Unable to check methods signature", + "priority": "MAJOR" + }, + { + "configKey": "F0220", + "constantDebtMinutes": 15, + "key": "F0220", + "name": "Failed to resolve interfaces", + "priority": "MAJOR" + }, + { + "configKey": "F0321", + "constantDebtMinutes": 15, + "key": "F0321", + "name": "Format detection error", + "priority": "MAJOR" + }, + { + "configKey": "F0401", + "constantDebtMinutes": 15, + "key": "F0401", + "name": "Unable to import module", + "priority": "MAJOR" + }, + { + "configKey": "I0001", + "constantDebtMinutes": 15, + "key": "I0001", + "name": "Unable to run raw checkers on built-in module", + "priority": "INFO" + }, + { + "configKey": "I0010", + "constantDebtMinutes": 15, + "key": "I0010", + "name": "Unable to consider inline option", + "priority": "INFO" + }, + { + "configKey": "I0011", + "constantDebtMinutes": 15, + "key": "I0011", + "name": "Locally disabling message", + "priority": "INFO" + }, + { + "configKey": "I0012", + "constantDebtMinutes": 15, + "key": "I0012", + "name": "Locally enabling message", + "priority": "INFO" + }, + { + "configKey": "I0013", + "constantDebtMinutes": 15, + "key": "I0013", + "name": "Ignoring entire file", + "priority": "INFO" + }, + { + "configKey": "I0020", + "constantDebtMinutes": 15, + "key": "I0020", + "name": "Suppressed message", + "priority": "INFO" + }, + { + "configKey": "I0021", + "constantDebtMinutes": 15, + "key": "I0021", + "name": "Useless suppression of message", + "priority": "INFO" + }, + { + "configKey": "I0022", + "constantDebtMinutes": 15, + "key": "I0022", + "name": "Deprecated pragma", + "priority": "INFO" + }, + { + "configKey": "I0023", + "constantDebtMinutes": 5, + "key": "I0023", + "name": "Use symbolic message", + "priority": "INFO" + }, + { + "configKey": "I1101", + "constantDebtMinutes": 15, + "key": "I1101", + "name": "Non-existent member of C extension", + "priority": "INFO" + }, + { + "configKey": "R0123", + "constantDebtMinutes": 20, + "key": "R0123", + "name": "Comparison to literal", + "priority": "MINOR" + }, + { + "configKey": "R0124", + "constantDebtMinutes": 1, + "key": "R0124", + "name": "Redundant comparison", + "priority": "MINOR" + }, + { + "configKey": "R0201", + "constantDebtMinutes": 20, + "key": "R0201", + "name": "Method could be a function", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "R0202", + "constantDebtMinutes": 15, + "key": "R0202", + "name": "Consider using a decorator instead of calling classmethod", + "priority": "MINOR" + }, + { + "configKey": "R0203", + "constantDebtMinutes": 15, + "key": "R0203", + "name": "Consider using a decorator instead of calling staticmethod", + "priority": "MINOR" + }, + { + "configKey": "R0205", + "constantDebtMinutes": 1, + "key": "R0205", + "name": "Class inherits from object, can be safely removed from bases in python3", + "priority": "MINOR" + }, + { + "configKey": "R0401", + "constantDebtMinutes": 120, + "key": "R0401", + "name": "Cyclic import", + "priority": "MINOR" + }, + { + "configKey": "R0801", + "constantDebtMinutes": 15, + "key": "R0801", + "name": "Similar lines", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "R0901", + "constantDebtMinutes": 180, + "key": "R0901", + "name": "Too many ancestors", + "priority": "MINOR" + }, + { + "configKey": "R0902", + "constantDebtMinutes": 120, + "key": "R0902", + "name": "Too many instance attributes", + "priority": "MINOR" + }, + { + "configKey": "R0903", + "constantDebtMinutes": 60, + "key": "R0903", + "name": "Too few public methods", + "priority": "MINOR" + }, + { + "configKey": "R0904", + "constantDebtMinutes": 120, + "key": "R0904", + "name": "Too many public methods", + "priority": "MINOR" + }, + { + "configKey": "R0911", + "constantDebtMinutes": 20, + "key": "R0911", + "name": "Too many return statements", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "R0912", + "constantDebtMinutes": 15, + "key": "R0912", + "name": "Too many branches", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "R0913", + "constantDebtMinutes": 30, + "key": "R0913", + "name": "Too many arguments", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "R0914", + "constantDebtMinutes": 20, + "key": "R0914", + "name": "Too many local variables", + "priority": "MINOR" + }, + { + "configKey": "R0915", + "constantDebtMinutes": 15, + "key": "R0915", + "name": "Too many statements", + "priority": "MINOR" + }, + { + "configKey": "R0916", + "constantDebtMinutes": 20, + "key": "R0916", + "name": "Too many boolean expressions in if statement", + "priority": "MINOR" + }, + { + "configKey": "R0921", + "constantDebtMinutes": 20, + "key": "R0921", + "name": "Abstract class not referenced", + "priority": "MINOR" + }, + { + "configKey": "R0922", + "constantDebtMinutes": 45, + "key": "R0922", + "name": "Abstract class used too few times", + "priority": "MINOR" + }, + { + "configKey": "R0923", + "constantDebtMinutes": 20, + "key": "R0923", + "name": "Interface not implemented", + "priority": "MINOR" + }, + { + "configKey": "R1701", + "constantDebtMinutes": 20, + "key": "R1701", + "name": "Consider merging isinstance calls", + "priority": "MINOR" + }, + { + "configKey": "R1702", + "constantDebtMinutes": 30, + "key": "R1702", + "name": "Too many nested blocks", + "priority": "MINOR" + }, + { + "configKey": "R1703", + "constantDebtMinutes": 10, + "key": "R1703", + "name": "Simplifiable if statement", + "priority": "MINOR" + }, + { + "configKey": "R1704", + "constantDebtMinutes": 60, + "key": "R1704", + "name": "Redefining argument with local name", + "priority": "MINOR" + }, + { + "configKey": "R1705", + "constantDebtMinutes": 20, + "key": "R1705", + "name": "Unnecessary \"else\" after \"return\"", + "priority": "MINOR" + }, + { + "configKey": "R1706", + "constantDebtMinutes": 20, + "key": "R1706", + "name": "Consider using ternary", + "priority": "MINOR" + }, + { + "configKey": "R1707", + "constantDebtMinutes": 1, + "key": "R1707", + "name": "Disallow trailing comma tuple", + "priority": "MINOR" + }, + { + "configKey": "R1708", + "constantDebtMinutes": 5, + "key": "R1708", + "name": "Do not raise StopIteration in generator, use return statement instead", + "priority": "MINOR" + }, + { + "configKey": "R1709", + "constantDebtMinutes": 20, + "key": "R1709", + "name": "Boolean expression may be simplified", + "priority": "MINOR" + }, + { + "configKey": "R1710", + "constantDebtMinutes": 20, + "key": "R1710", + "name": "Either all return statements in a function should return an expression, or none of them should.", + "priority": "MINOR" + }, + { + "configKey": "R1711", + "constantDebtMinutes": 1, + "key": "R1711", + "name": "Useless return at end of function or method", + "priority": "MINOR" + }, + { + "configKey": "R1712", + "constantDebtMinutes": 1, + "key": "R1712", + "name": "Consider using tuple unpacking for swapping variables", + "priority": "MINOR" + }, + { + "configKey": "R1713", + "constantDebtMinutes": 1, + "key": "R1713", + "name": "Consider using str.join(sequence) for concatenating strings from an iterable", + "priority": "MINOR" + }, + { + "configKey": "R1714", + "constantDebtMinutes": 1, + "key": "R1714", + "name": "Consider using \"in\"", + "priority": "MINOR" + }, + { + "configKey": "R1715", + "constantDebtMinutes": 1, + "key": "R1715", + "name": "Consider using dict.get for getting values from a dict if a key is present or a default if not", + "priority": "MINOR" + }, + { + "configKey": "R1716", + "constantDebtMinutes": 1, + "key": "R1716", + "name": "Simplify chained comparison between the operands", + "priority": "MINOR" + }, + { + "configKey": "R1717", + "constantDebtMinutes": 15, + "key": "R1717", + "name": "Consider using a dictionary comprehension", + "priority": "MINOR" + }, + { + "configKey": "R1718", + "constantDebtMinutes": 15, + "key": "R1718", + "name": "Consider using a set comprehension", + "priority": "MINOR" + }, + { + "configKey": "R1719", + "constantDebtMinutes": 1, + "key": "R1719", + "name": "Simplifiable if expression", + "priority": "MINOR" + }, + { + "configKey": "R1720", + "constantDebtMinutes": 1, + "key": "R1720", + "name": "Unnecessary \"else\" after \"raise\"", + "priority": "MINOR" + }, + { + "configKey": "W0101", + "constantDebtMinutes": 15, + "key": "W0101", + "name": "Unreachable code", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W0102", + "constantDebtMinutes": 15, + "key": "W0102", + "name": "Dangerous default value as argument", + "priority": "MINOR" + }, + { + "configKey": "W0104", + "constantDebtMinutes": 15, + "key": "W0104", + "name": "Statement seems to have no effect", + "priority": "MINOR" + }, + { + "configKey": "W0105", + "constantDebtMinutes": 15, + "key": "W0105", + "name": "String statement has no effect", + "priority": "MINOR" + }, + { + "configKey": "W0106", + "constantDebtMinutes": 15, + "key": "W0106", + "name": "Expression is assigned to nothing", + "priority": "MINOR" + }, + { + "configKey": "W0107", + "constantDebtMinutes": 1, + "key": "W0107", + "name": "Unnecessary pass statement", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W0108", + "constantDebtMinutes": 5, + "key": "W0108", + "name": "Lambda may not be necessary", + "priority": "MINOR" + }, + { + "configKey": "W0109", + "constantDebtMinutes": 10, + "key": "W0109", + "name": "Duplicate key in dictionary", + "priority": "MINOR" + }, + { + "configKey": "W0110", + "constantDebtMinutes": 15, + "key": "W0110", + "name": "map/filter on lambda could be replaced by comprehension", + "priority": "MINOR" + }, + { + "configKey": "W0111", + "constantDebtMinutes": 20, + "key": "W0111", + "name": "Assignments should not be made to new Python keywords", + "priority": "MINOR" + }, + { + "configKey": "W0120", + "constantDebtMinutes": 20, + "key": "W0120", + "name": "Else clause on loop without a break statement", + "priority": "MINOR" + }, + { + "configKey": "W0121", + "constantDebtMinutes": 5, + "key": "W0121", + "name": "Use raise ErrorClass(args) instead of raise ErrorClass, args.", + "priority": "MINOR" + }, + { + "configKey": "W0122", + "constantDebtMinutes": 30, + "key": "W0122", + "name": "Use of exec", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W0123", + "constantDebtMinutes": 30, + "key": "W0123", + "name": "Use of eval", + "priority": "MINOR" + }, + { + "configKey": "W0124", + "constantDebtMinutes": 30, + "key": "W0124", + "name": "Following \"as\" with another context manager looks like a tuple.", + "priority": "MINOR" + }, + { + "configKey": "W0125", + "constantDebtMinutes": 30, + "key": "W0125", + "name": "Using a conditional statement with a constant value", + "priority": "MINOR" + }, + { + "configKey": "W0141", + "constantDebtMinutes": 15, + "key": "W0141", + "name": "Used black listed builtin function", + "priority": "MINOR" + }, + { + "configKey": "W0142", + "constantDebtMinutes": 15, + "key": "W0142", + "name": "Used * or ** magic", + "priority": "MINOR" + }, + { + "configKey": "W0143", + "constantDebtMinutes": 1, + "key": "W0143", + "name": "Comparing against a callable, did you omit the parenthesis?", + "priority": "MINOR" + }, + { + "configKey": "W0150", + "constantDebtMinutes": 20, + "key": "W0150", + "name": "Statement in finally block may swallow exception", + "priority": "MINOR" + }, + { + "configKey": "W0199", + "constantDebtMinutes": 5, + "key": "W0199", + "name": "Assert called on a 2-uple", + "priority": "MINOR" + }, + { + "configKey": "W0201", + "constantDebtMinutes": 5, + "key": "W0201", + "name": "Attribute defined outside __init__", + "priority": "MINOR" + }, + { + "configKey": "W0211", + "constantDebtMinutes": 10, + "key": "W0211", + "name": "Static method with \"self\" or \"cls\" as first argument", + "priority": "MINOR" + }, + { + "configKey": "W0212", + "constantDebtMinutes": 60, + "key": "W0212", + "name": "Access to a protected member of a client class", + "priority": "MINOR" + }, + { + "configKey": "W0221", + "constantDebtMinutes": 15, + "key": "W0221", + "name": "Parameter number discrepancy", + "priority": "MINOR" + }, + { + "configKey": "W0222", + "constantDebtMinutes": 20, + "key": "W0222", + "name": "Method signature discrepancy", + "priority": "MINOR" + }, + { + "configKey": "W0223", + "constantDebtMinutes": 60, + "key": "W0223", + "name": "Abstract method is not overridden", + "priority": "MINOR" + }, + { + "configKey": "W0231", + "constantDebtMinutes": 20, + "key": "W0231", + "name": "__init__ method from base class is not called", + "priority": "MINOR" + }, + { + "configKey": "W0232", + "constantDebtMinutes": 20, + "key": "W0232", + "name": "Class has no __init__ method", + "priority": "MINOR" + }, + { + "configKey": "W0233", + "constantDebtMinutes": 120, + "key": "W0233", + "name": "__init__ method from a non direct base class is called", + "priority": "MINOR" + }, + { + "configKey": "W0234", + "constantDebtMinutes": 15, + "key": "W0234", + "name": "__iter__ returns non-iterator", + "priority": "MINOR" + }, + { + "configKey": "W0235", + "constantDebtMinutes": 1, + "key": "W0235", + "name": "Useless super delegation", + "priority": "MINOR" + }, + { + "configKey": "W0301", + "constantDebtMinutes": 1, + "key": "W0301", + "name": "Unnecessary semicolon", + "priority": "MINOR" + }, + { + "configKey": "W0311", + "constantDebtMinutes": 1, + "key": "W0311", + "name": "Bad indentation", + "priority": "MINOR" + }, + { + "configKey": "W0312", + "constantDebtMinutes": 1, + "key": "W0312", + "name": "Mixed tabs/spaces indentation", + "priority": "MAJOR" + }, + { + "configKey": "W0331", + "constantDebtMinutes": 1, + "key": "W0331", + "name": "Use of the <> operator", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W0332", + "constantDebtMinutes": 1, + "key": "W0332", + "name": "Use of \"l\" as long integer identifier", + "priority": "MINOR" + }, + { + "configKey": "W0333", + "constantDebtMinutes": 1, + "key": "W0333", + "name": "Use of the `` operator", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W0401", + "constantDebtMinutes": 1, + "key": "W0401", + "name": "Wildcard import", + "priority": "MINOR" + }, + { + "configKey": "W0402", + "constantDebtMinutes": 1, + "key": "W0402", + "name": "Uses of a deprecated module", + "priority": "MINOR" + }, + { + "configKey": "W0403", + "constantDebtMinutes": 15, + "key": "W0403", + "name": "Relative import", + "priority": "MINOR" + }, + { + "configKey": "W0404", + "constantDebtMinutes": 1, + "key": "W0404", + "name": "Reimport", + "priority": "MINOR" + }, + { + "configKey": "W0406", + "constantDebtMinutes": 15, + "key": "W0406", + "name": "Module imports itself", + "priority": "MINOR" + }, + { + "configKey": "W0410", + "constantDebtMinutes": 10, + "key": "W0410", + "name": "__future__ import is not the first non docstring statement", + "priority": "MINOR" + }, + { + "configKey": "W0511", + "constantDebtMinutes": 60, + "key": "W0511", + "name": "Task marker found", + "priority": "MINOR" + }, + { + "configKey": "W0512", + "constantDebtMinutes": 15, + "key": "W0512", + "name": "Source line cannot be decoded using the specified source file encoding", + "priority": "MINOR" + }, + { + "configKey": "W0601", + "constantDebtMinutes": 15, + "key": "W0601", + "name": "Global variable undefined at the module level", + "priority": "MINOR" + }, + { + "configKey": "W0602", + "constantDebtMinutes": 15, + "key": "W0602", + "name": "Unassigned global variable", + "priority": "MINOR" + }, + { + "configKey": "W0603", + "constantDebtMinutes": 60, + "key": "W0603", + "name": "Using the global statement", + "priority": "MINOR" + }, + { + "configKey": "W0604", + "constantDebtMinutes": 1, + "key": "W0604", + "name": "Using the global statement at the module level", + "priority": "MINOR" + }, + { + "configKey": "W0611", + "constantDebtMinutes": 1, + "key": "W0611", + "name": "Unused import", + "priority": "MINOR" + }, + { + "configKey": "W0612", + "constantDebtMinutes": 5, + "key": "W0612", + "name": "Unused variable", + "priority": "MINOR" + }, + { + "configKey": "W0613", + "constantDebtMinutes": 15, + "key": "W0613", + "name": "Unused argument", + "priority": "MINOR" + }, + { + "configKey": "W0614", + "constantDebtMinutes": 1, + "key": "W0614", + "name": "Unused import from wildcard import", + "priority": "MINOR" + }, + { + "configKey": "W0621", + "constantDebtMinutes": 15, + "key": "W0621", + "name": "Redefining name from outer scope", + "priority": "MINOR" + }, + { + "configKey": "W0622", + "constantDebtMinutes": 5, + "key": "W0622", + "name": "Redefining built-in", + "priority": "MINOR" + }, + { + "configKey": "W0623", + "constantDebtMinutes": 20, + "key": "W0623", + "name": "Redefining name in exception handler", + "priority": "MINOR" + }, + { + "configKey": "W0631", + "constantDebtMinutes": 15, + "key": "W0631", + "name": "Using possibly undefined loop variable", + "priority": "MINOR" + }, + { + "configKey": "W0632", + "constantDebtMinutes": 15, + "key": "W0632", + "name": "Possible unbalanced tuple unpacking", + "priority": "MINOR" + }, + { + "configKey": "W0633", + "constantDebtMinutes": 15, + "key": "W0633", + "name": "Attempting to unpack a non-sequence", + "priority": "MINOR" + }, + { + "configKey": "W0640", + "constantDebtMinutes": 20, + "key": "W0640", + "name": "Cell variable defined in loop", + "priority": "MINOR" + }, + { + "configKey": "W0641", + "constantDebtMinutes": 1, + "key": "W0641", + "name": "Possibly unused variable", + "priority": "MINOR" + }, + { + "configKey": "W0642", + "constantDebtMinutes": 5, + "key": "W0642", + "name": "Invalid assignment in method", + "priority": "MINOR" + }, + { + "configKey": "W0701", + "constantDebtMinutes": 15, + "key": "W0701", + "name": "Raising a string exception", + "priority": "MINOR" + }, + { + "configKey": "W0702", + "constantDebtMinutes": 15, + "key": "W0702", + "name": "No exception type(s) specified", + "priority": "MINOR" + }, + { + "configKey": "W0703", + "constantDebtMinutes": 20, + "key": "W0703", + "name": "Catching too general exception", + "priority": "MINOR" + }, + { + "configKey": "W0704", + "constantDebtMinutes": 15, + "key": "W0704", + "name": "Except doesn't do anything", + "priority": "MINOR" + }, + { + "configKey": "W0705", + "constantDebtMinutes": 10, + "key": "W0705", + "name": "Catching previously caught exception type", + "priority": "MINOR" + }, + { + "configKey": "W0706", + "constantDebtMinutes": 1, + "key": "W0706", + "name": "The except handler raises immediately", + "priority": "MINOR" + }, + { + "configKey": "W0710", + "constantDebtMinutes": 15, + "key": "W0710", + "name": "Exception doesn't inherit from standard \"Exception\" class", + "priority": "MINOR" + }, + { + "configKey": "W0711", + "constantDebtMinutes": 15, + "key": "W0711", + "name": "Exception to catch is the result of a binary operation", + "priority": "MINOR" + }, + { + "configKey": "W0712", + "constantDebtMinutes": 15, + "key": "W0712", + "name": "Implicit unpacking of exceptions is not supported in Python 3", + "priority": "MINOR" + }, + { + "configKey": "W0715", + "constantDebtMinutes": 5, + "key": "W0715", + "name": "Exception arguments suggest string formatting might be intended", + "priority": "MINOR" + }, + { + "configKey": "W0716", + "constantDebtMinutes": 5, + "key": "W0716", + "name": "Invalid exception operation", + "priority": "MINOR" + }, + { + "configKey": "W1001", + "constantDebtMinutes": 20, + "key": "W1001", + "name": "Use of \"property\" on an old style class", + "priority": "MINOR" + }, + { + "configKey": "W1111", + "constantDebtMinutes": 15, + "key": "W1111", + "name": "Assigning to function call which only returns None", + "priority": "MINOR" + }, + { + "configKey": "W1113", + "constantDebtMinutes": 5, + "key": "W1113", + "name": "Keyword argument before variable positional arguments list", + "priority": "MINOR" + }, + { + "configKey": "W1201", + "constantDebtMinutes": 5, + "key": "W1201", + "name": "Specify string format arguments as logging function parameters", + "priority": "MINOR" + }, + { + "configKey": "W1202", + "constantDebtMinutes": 5, + "key": "W1202", + "name": "Logging format interpolation", + "priority": "MINOR" + }, + { + "configKey": "W1203", + "constantDebtMinutes": 1, + "key": "W1203", + "name": "Usage of % formatting in logging functions", + "priority": "MINOR" + }, + { + "configKey": "W1300", + "constantDebtMinutes": 5, + "key": "W1300", + "name": "Format string dictionary key should be a string", + "priority": "MINOR" + }, + { + "configKey": "W1301", + "constantDebtMinutes": 5, + "key": "W1301", + "name": "Unused key in format string dictionary", + "priority": "MINOR" + }, + { + "configKey": "W1302", + "constantDebtMinutes": 5, + "key": "W1302", + "name": "Invalid format string", + "priority": "MINOR" + }, + { + "configKey": "W1303", + "constantDebtMinutes": 5, + "key": "W1303", + "name": "Missing keyword argument for format string", + "priority": "MINOR" + }, + { + "configKey": "W1304", + "constantDebtMinutes": 5, + "key": "W1304", + "name": "Unused format argument", + "priority": "MINOR" + }, + { + "configKey": "W1305", + "constantDebtMinutes": 5, + "key": "W1305", + "name": "Format string contains both automatic field numbering and manual field specification", + "priority": "MINOR" + }, + { + "configKey": "W1306", + "constantDebtMinutes": 5, + "key": "W1306", + "name": "Missing format attribute", + "priority": "MINOR" + }, + { + "configKey": "W1307", + "constantDebtMinutes": 5, + "key": "W1307", + "name": "Using invalid lookup key in format specifier", + "priority": "MINOR" + }, + { + "configKey": "W1308", + "constantDebtMinutes": 1, + "key": "W1308", + "name": "Duplicate string formatting argument", + "priority": "MINOR" + }, + { + "configKey": "W1401", + "constantDebtMinutes": 5, + "key": "W1401", + "name": "Anomalous backslash escape", + "priority": "MINOR", + "status": "DEPRECATED" + }, + { + "configKey": "W1402", + "constantDebtMinutes": 15, + "key": "W1402", + "name": "Anomalous Unicode escape in byte string", + "priority": "MINOR" + }, + { + "configKey": "W1403", + "constantDebtMinutes": 1, + "key": "W1403", + "name": "Implicit string concatenation", + "priority": "MINOR" + }, + { + "configKey": "W1501", + "constantDebtMinutes": 15, + "key": "W1501", + "name": "Invalid mode for open", + "priority": "MINOR" + }, + { + "configKey": "W1502", + "constantDebtMinutes": 15, + "key": "W1502", + "name": "Using datetime.time in a boolean context.", + "priority": "MINOR" + }, + { + "configKey": "W1503", + "constantDebtMinutes": 15, + "key": "W1503", + "name": "Redundant unittest assert", + "priority": "MINOR" + }, + { + "configKey": "W1505", + "constantDebtMinutes": 15, + "key": "W1505", + "name": "Using deprecated method", + "priority": "MINOR" + }, + { + "configKey": "W1506", + "constantDebtMinutes": 15, + "key": "W1506", + "name": "threading.Thread needs the target function", + "priority": "MINOR" + }, + { + "configKey": "W1507", + "constantDebtMinutes": 15, + "key": "W1507", + "name": "Using copy.copy(os.environ). Use os.environ.copy() instead.", + "priority": "MINOR" + }, + { + "configKey": "W1508", + "constantDebtMinutes": 5, + "key": "W1508", + "name": "Invalid type in env manipulation functions", + "priority": "MINOR" + }, + { + "configKey": "W1509", + "constantDebtMinutes": 15, + "key": "W1509", + "name": "Using preexec_fn keyword which may be unsafe in the presence of threads", + "priority": "MINOR" + }, + { + "configKey": "W1601", + "constantDebtMinutes": 15, + "key": "W1601", + "name": "apply built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1602", + "constantDebtMinutes": 5, + "key": "W1602", + "name": "basestring built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1603", + "constantDebtMinutes": 5, + "key": "W1603", + "name": "buffer built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1604", + "constantDebtMinutes": 5, + "key": "W1604", + "name": "cmp built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1605", + "constantDebtMinutes": 5, + "key": "W1605", + "name": "coerce built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1606", + "constantDebtMinutes": 5, + "key": "W1606", + "name": "execfile built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1607", + "constantDebtMinutes": 5, + "key": "W1607", + "name": "file built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1608", + "constantDebtMinutes": 5, + "key": "W1608", + "name": "long built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1609", + "constantDebtMinutes": 5, + "key": "W1609", + "name": "raw_input built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1610", + "constantDebtMinutes": 5, + "key": "W1610", + "name": "reduce built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1611", + "constantDebtMinutes": 5, + "key": "W1611", + "name": "StandardError built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1612", + "constantDebtMinutes": 5, + "key": "W1612", + "name": "unicode built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1613", + "constantDebtMinutes": 5, + "key": "W1613", + "name": "xrange built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1614", + "constantDebtMinutes": 5, + "key": "W1614", + "name": "__coerce__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1615", + "constantDebtMinutes": 5, + "key": "W1615", + "name": "__delslice__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1616", + "constantDebtMinutes": 5, + "key": "W1616", + "name": "__getslice__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1617", + "constantDebtMinutes": 5, + "key": "W1617", + "name": "__setslice__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1618", + "constantDebtMinutes": 5, + "key": "W1618", + "name": "import missing `from __future__ import absolute_import`", + "priority": "MINOR" + }, + { + "configKey": "W1619", + "constantDebtMinutes": 5, + "key": "W1619", + "name": "division w/o __future__ statement", + "priority": "MINOR" + }, + { + "configKey": "W1620", + "constantDebtMinutes": 5, + "key": "W1620", + "name": "Calling a dict.iter*() method", + "priority": "MINOR" + }, + { + "configKey": "W1621", + "constantDebtMinutes": 5, + "key": "W1621", + "name": "Calling a dict.view*() method", + "priority": "MINOR" + }, + { + "configKey": "W1622", + "constantDebtMinutes": 5, + "key": "W1622", + "name": "Called a next() method on an object", + "priority": "MINOR" + }, + { + "configKey": "W1623", + "constantDebtMinutes": 5, + "key": "W1623", + "name": "Assigning to a class's __metaclass__ attribute", + "priority": "MINOR" + }, + { + "configKey": "W1624", + "constantDebtMinutes": 5, + "key": "W1624", + "name": "Indexing exceptions will not work on Python 3", + "priority": "MINOR" + }, + { + "configKey": "W1625", + "constantDebtMinutes": 5, + "key": "W1625", + "name": "Raising a string exception", + "priority": "MINOR" + }, + { + "configKey": "W1626", + "constantDebtMinutes": 5, + "key": "W1626", + "name": "reload built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1627", + "constantDebtMinutes": 5, + "key": "W1627", + "name": "__oct__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1628", + "constantDebtMinutes": 5, + "key": "W1628", + "name": "__hex__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1629", + "constantDebtMinutes": 5, + "key": "W1629", + "name": "__nonzero__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1630", + "constantDebtMinutes": 5, + "key": "W1630", + "name": "__cmp__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1632", + "constantDebtMinutes": 5, + "key": "W1632", + "name": "input built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1633", + "constantDebtMinutes": 5, + "key": "W1633", + "name": "round built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1634", + "constantDebtMinutes": 5, + "key": "W1634", + "name": "intern built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1635", + "constantDebtMinutes": 5, + "key": "W1635", + "name": "unichr built-in referenced", + "priority": "MINOR" + }, + { + "configKey": "W1636", + "constantDebtMinutes": 5, + "key": "W1636", + "name": "map built-in referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1637", + "constantDebtMinutes": 5, + "key": "W1637", + "name": "zip built-in referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1638", + "constantDebtMinutes": 5, + "key": "W1638", + "name": "range built-in referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1639", + "constantDebtMinutes": 5, + "key": "W1639", + "name": "filter built-in referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1640", + "constantDebtMinutes": 5, + "key": "W1640", + "name": "Using the cmp argument for list.sort / sorted", + "priority": "MINOR" + }, + { + "configKey": "W1641", + "constantDebtMinutes": 5, + "key": "W1641", + "name": "Implementing __eq__ without also implementing __hash__", + "priority": "MINOR" + }, + { + "configKey": "W1642", + "constantDebtMinutes": 5, + "key": "W1642", + "name": "__div__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1643", + "constantDebtMinutes": 5, + "key": "W1643", + "name": "__idiv__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1644", + "constantDebtMinutes": 5, + "key": "W1644", + "name": "__rdiv__ method defined", + "priority": "MINOR" + }, + { + "configKey": "W1645", + "constantDebtMinutes": 5, + "key": "W1645", + "name": "Exception.message removed in Python 3", + "priority": "MINOR" + }, + { + "configKey": "W1646", + "constantDebtMinutes": 5, + "key": "W1646", + "name": "non-text encoding used in str.decode", + "priority": "MINOR" + }, + { + "configKey": "W1647", + "constantDebtMinutes": 5, + "key": "W1647", + "name": "sys.maxint removed in Python 3", + "priority": "MINOR" + }, + { + "configKey": "W1649", + "constantDebtMinutes": 5, + "key": "W1649", + "name": "Accessing a deprecated function on the string module", + "priority": "MINOR" + }, + { + "configKey": "W1650", + "constantDebtMinutes": 5, + "key": "W1650", + "name": "Using str.translate with deprecated deletechars parameters", + "priority": "MINOR" + }, + { + "configKey": "W1651", + "constantDebtMinutes": 5, + "key": "W1651", + "name": "Accessing a deprecated function on the itertools module", + "priority": "MINOR" + }, + { + "configKey": "W1652", + "constantDebtMinutes": 5, + "key": "W1652", + "name": "Accessing a deprecated fields on the types module", + "priority": "MINOR" + }, + { + "configKey": "W1653", + "constantDebtMinutes": 5, + "key": "W1653", + "name": "next method defined", + "priority": "MINOR" + }, + { + "configKey": "W1654", + "constantDebtMinutes": 5, + "key": "W1654", + "name": "dict.items referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1655", + "constantDebtMinutes": 5, + "key": "W1655", + "name": "dict.keys referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1656", + "constantDebtMinutes": 5, + "key": "W1656", + "name": "dict.values referenced when not iterating", + "priority": "MINOR" + }, + { + "configKey": "W1657", + "constantDebtMinutes": 5, + "key": "W1657", + "name": "Accessing a removed attribute on the operator module", + "priority": "MINOR" + }, + { + "configKey": "W1658", + "constantDebtMinutes": 5, + "key": "W1658", + "name": "Accessing a removed attribute on the urllib module", + "priority": "MINOR" + }, + { + "configKey": "W1659", + "constantDebtMinutes": 5, + "key": "W1659", + "name": "Accessing a removed xreadlines attribute", + "priority": "MAJOR" + }, + { + "configKey": "W1660", + "constantDebtMinutes": 5, + "key": "W1660", + "name": "Accessing a removed attribute on the sys module", + "priority": "MINOR" + }, + { + "configKey": "W1661", + "constantDebtMinutes": 5, + "key": "W1661", + "name": "Using an exception object that was bound by an except handler", + "priority": "MINOR" + }, + { + "configKey": "W1662", + "constantDebtMinutes": 5, + "key": "W1662", + "name": "Using a variable that was bound inside a comprehension", + "priority": "MINOR" + } +] diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java index ef92e93ff1..78e2feb982 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonPluginTest.java @@ -35,11 +35,11 @@ public class PythonPluginTest { @Test public void testGetExtensions() { - Version v74 = Version.create(7, 9); - SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v74, SonarQubeSide.SERVER, SonarEdition.DEVELOPER); - assertThat(extensions(runtime)).hasSize(18); + Version v79 = Version.create(7, 9); + SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER); + assertThat(extensions(runtime)).hasSize(21); assertThat(extensions(runtime)).contains(DefaultAnalysisWarningsWrapper.class); - assertThat(extensions(SonarRuntimeImpl.forSonarLint(v74))).hasSize(5); + assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79))).hasSize(5); } private static List extensions(SonarRuntime runtime) { diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintRulesDefinitionTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintRulesDefinitionTest.java new file mode 100644 index 0000000000..f9579a11bf --- /dev/null +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintRulesDefinitionTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.pylint; + +import org.junit.Test; +import org.sonar.api.server.rule.RulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PylintRulesDefinitionTest { + + @Test + public void pylint_external_repository() { + RulesDefinition.Context context = new RulesDefinition.Context(); + PylintRulesDefinition rulesDefinition = new PylintRulesDefinition(); + rulesDefinition.define(context); + + assertThat(context.repositories()).hasSize(1); + RulesDefinition.Repository repository = context.repository("external_pylint"); + assertThat(repository).isNotNull(); + assertThat(repository.name()).isEqualTo("Pylint"); + assertThat(repository.language()).isEqualTo("py"); + assertThat(repository.isExternal()).isTrue(); + assertThat(repository.rules().size()).isEqualTo(370); + + RulesDefinition.Rule rule = repository.rule("C0121"); + assertThat(rule).isNotNull(); + assertThat(rule.name()).isEqualTo("Singleton comparison"); + assertThat(rule.htmlDescription()).isEqualTo("This is external rule pylint:C0121. No details are available."); + assertThat(rule.debtRemediationFunction().type().name()).isEqualTo("CONSTANT_ISSUE"); + assertThat(rule.debtRemediationFunction().baseEffort()).isEqualTo("20min"); + assertThat(rule.tags()).isEmpty(); + } +} diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintSensorTest.java new file mode 100644 index 0000000000..9317d4aaee --- /dev/null +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/pylint/PylintSensorTest.java @@ -0,0 +1,238 @@ +/* + * SonarQube Python Plugin + * Copyright (C) 2011-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.python.pylint; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.ExternalIssue; +import org.sonar.api.batch.sensor.issue.IssueLocation; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.Version; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +public class PylintSensorTest { + + private static final String PYLINT_FILE = "python-project:pylint/file1.py"; + private static final String PYLINT_REPORT_DEFAULT_FORMAT = "pylint_report_default_format.txt"; + private static final String PYLINT_REPORT_NO_COLUMN = "pylint_report_no_column.txt"; + + private static final Path PROJECT_DIR = Paths.get("src", "test", "resources", "org", "sonar", "plugins", "python", "pylint"); + + private static PylintSensor pylintSensor = new PylintSensor(); + + @Rule + public LogTester logTester = new LogTester(); + + @Test + public void test_descriptor() { + DefaultSensorDescriptor sensorDescriptor = new DefaultSensorDescriptor(); + pylintSensor.describe(sensorDescriptor); + assertThat(sensorDescriptor.name()).isEqualTo("Import of Pylint issues"); + assertThat(sensorDescriptor.languages()).containsOnly("py"); + assertThat(sensorDescriptor.configurationPredicate()).isNotNull(); + assertNoErrorWarnLogs(logTester); + } + + @Test + public void issues_default_format() throws IOException { + List externalIssues = executeSensorImporting(7, 9, PYLINT_REPORT_DEFAULT_FORMAT); + assertThat(externalIssues).hasSize(10); + + ExternalIssue first = externalIssues.get(0); + assertThat(first.ruleKey()).hasToString("external_pylint:C0114"); + assertThat(first.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(first.severity()).isEqualTo(Severity.MAJOR); + IssueLocation firstPrimaryLoc = first.primaryLocation(); + assertThat(firstPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(firstPrimaryLoc.message()) + .isEqualTo("Missing module docstring (missing-module-docstring)"); + TextRange firstTextRange = firstPrimaryLoc.textRange(); + assertThat(firstTextRange).isNotNull(); + assertThat(firstTextRange.start().line()).isEqualTo(1); + assertThat(firstTextRange.start().lineOffset()).isZero(); + assertThat(firstTextRange.end().line()).isEqualTo(1); + assertThat(firstTextRange.end().lineOffset()).isEqualTo(1); + + // Issue on column >= last column is reported on whole line instead + ExternalIssue last = externalIssues.get(9); + IssueLocation lastPrimaryLoc = last.primaryLocation(); + TextRange lastTextRange = lastPrimaryLoc.textRange(); + assertThat(lastTextRange).isNotNull(); + assertThat(lastTextRange.start().line()).isEqualTo(1); + assertThat(lastTextRange.start().lineOffset()).isZero(); + assertThat(lastTextRange.end().line()).isEqualTo(1); + assertThat(lastTextRange.end().lineOffset()).isEqualTo(13); + + assertNoErrorWarnLogs(logTester); + } + + @Test + public void issues_no_column_info() throws IOException { + List externalIssues = executeSensorImporting(7, 9, PYLINT_REPORT_NO_COLUMN); + assertThat(externalIssues).hasSize(3); + + ExternalIssue first = externalIssues.get(0); + assertThat(first.ruleKey()).hasToString("external_pylint:C0111"); + assertThat(first.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(first.severity()).isEqualTo(Severity.MAJOR); + IssueLocation firstPrimaryLoc = first.primaryLocation(); + assertThat(firstPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(firstPrimaryLoc.message()) + .isEqualTo("Missing module docstring"); + TextRange firstTextRange = firstPrimaryLoc.textRange(); + assertThat(firstTextRange).isNotNull(); + assertThat(firstTextRange.start().line()).isEqualTo(1); + assertThat(firstTextRange.start().lineOffset()).isZero(); + assertThat(firstTextRange.end().line()).isEqualTo(1); + assertThat(firstTextRange.end().lineOffset()).isEqualTo(13); + + ExternalIssue second = externalIssues.get(1); + assertThat(second.ruleKey()).hasToString("external_pylint:C0103"); + assertThat(second.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(second.severity()).isEqualTo(Severity.MAJOR); + IssueLocation secondPrimaryLoc = second.primaryLocation(); + assertThat(secondPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(secondPrimaryLoc.message()).isEqualTo("Invalid argument name \"n\""); + TextRange secondTextRange = secondPrimaryLoc.textRange(); + assertThat(secondTextRange).isNotNull(); + assertThat(secondTextRange.start().line()).isEqualTo(1); + assertThat(secondTextRange.start().lineOffset()).isZero(); + assertThat(secondTextRange.end().line()).isEqualTo(1); + assertThat(secondTextRange.end().lineOffset()).isEqualTo(13); + + ExternalIssue third = externalIssues.get(2); + assertThat(third.ruleKey()).hasToString("external_pylint:C0111"); + assertThat(third.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(third.severity()).isEqualTo(Severity.MAJOR); + IssueLocation thirdPrimaryLoc = third.primaryLocation(); + assertThat(thirdPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(thirdPrimaryLoc.message()).isEqualTo("Missing function docstring"); + + assertNoErrorWarnLogs(logTester); + assertThat(onlyOneLogElement(logTester.logs(LoggerLevel.DEBUG))) + .isEqualTo("Cannot parse the line: ************* Module src.prod"); + } + + @Test + public void issues_other_formats() throws IOException { + List externalIssues = executeSensorImporting(7, 9, "pylint_brackets.txt"); + assertThat(externalIssues).hasSize(10); + ExternalIssue first = externalIssues.get(0); + assertThat(first.ruleKey()).hasToString("external_pylint:E1300"); + assertThat(first.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(first.severity()).isEqualTo(Severity.MAJOR); + IssueLocation firstPrimaryLoc = first.primaryLocation(); + assertThat(firstPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(firstPrimaryLoc.message()) + .isEqualTo("message"); + + externalIssues = executeSensorImporting(7, 9, "pylint_names_in_brackets.txt"); + assertThat(externalIssues).hasSize(1); + first = externalIssues.get(0); + assertThat(first.ruleKey()).hasToString("external_pylint:C0111"); + assertThat(first.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(first.severity()).isEqualTo(Severity.MAJOR); + firstPrimaryLoc = first.primaryLocation(); + assertThat(firstPrimaryLoc.inputComponent().key()).isEqualTo(PYLINT_FILE); + assertThat(firstPrimaryLoc.message()) + .isEqualTo("Missing docstring"); + } + + @Test + public void no_issues_unknown_files() throws IOException { + List externalIssues = executeSensorImporting(7, 9, "pylint_report_unknown_files.txt"); + assertThat(externalIssues).isEmpty(); + } + + @Test + public void no_issues_with_invalid_report_path() throws IOException { + List externalIssues = executeSensorImporting(7, 9, "invalid-path.txt"); + assertThat(externalIssues).isEmpty(); + assertThat(onlyOneLogElement(logTester.logs(LoggerLevel.ERROR))) + .startsWith("No issues information will be saved as the report file '") + .contains("invalid-path.txt' can't be read."); + } + + private static List executeSensorImporting(int majorVersion, int minorVersion, @Nullable String fileName) throws IOException { + Path baseDir = PROJECT_DIR.getParent(); + SensorContextTester context = SensorContextTester.create(baseDir); + try (Stream fileStream = Files.list(PROJECT_DIR)) { + fileStream.forEach(file -> addFileToContext(context, baseDir, file)); + context.setRuntime(SonarRuntimeImpl.forSonarQube(Version.create(majorVersion, minorVersion), SonarQubeSide.SERVER, SonarEdition.DEVELOPER)); + if (fileName != null) { + String path = PROJECT_DIR.resolve(fileName).toAbsolutePath().toString(); + context.settings().setProperty("sonar.python.pylint.reportPaths", path); + } + pylintSensor.execute(context); + return new ArrayList<>(context.allExternalIssues()); + } + } + + private static void addFileToContext(SensorContextTester context, Path projectDir, Path file) { + try { + String projectId = projectDir.getFileName().toString() + "-project"; + context.fileSystem().add(TestInputFileBuilder.create(projectId, projectDir.toFile(), file.toFile()) + .setCharset(UTF_8) + .setLanguage(language(file)) + .setContents(new String(Files.readAllBytes(file), UTF_8)) + .setType(InputFile.Type.MAIN) + .build()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private static String language(Path file) { + String path = file.toString(); + return path.substring(path.lastIndexOf('.') + 1); + } + + public static String onlyOneLogElement(List elements) { + assertThat(elements).hasSize(1); + return elements.get(0); + } + + public static void assertNoErrorWarnLogs(LogTester logTester) { + assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + } + +} diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/file1.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/file1.py new file mode 100644 index 0000000000..b14d5f7e3e --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/file1.py @@ -0,0 +1,30 @@ +def myfunc(): + def unused() -> str: + smth = 42 + max = 24 + if smth == smth: + print("Hello?") + return smth + + print("Hello!") + + +def my_other_func(): + my_list = [] + if my_list is []: + print("Impossible!") + my_tuple = (1, 2) + try: + my_tuple + my_list + except TypeError: + print("Hello there!") + return 24 + + +def main(): + myfunc() + my_other_func() + + +if __name__ == "__main__": + main() diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report-unknown-rules.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report-unknown-rules.txt deleted file mode 100644 index 5a8f103eb9..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report-unknown-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -************* Module src.prod -src/file1.py:1: [C0111(missing-docstring), ] Missing module docstring -src/file1.py:1: [C0103(invalid-name), factorial] Invalid argument name "n" -src/file1.py:1: [C0111(missing-docstring), factorial] Missing function docstring -src/file1.py:1: [C8888(unknown-rule1), ] This rule is not defined 1 -src/file1.py:1: [C9999(unknown-rule2), ] This rule is not defined 2 -src/file1.py:1: [C9999(unknown-rule2), ] This rule is not defined 2 \ No newline at end of file diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report.txt deleted file mode 100644 index 09c32309a4..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint-report.txt +++ /dev/null @@ -1,4 +0,0 @@ -************* Module src.prod -src/file1.py:1: [C0111(missing-docstring), ] Missing module docstring -src/file1.py:1: [C0103(invalid-name), factorial] Invalid argument name "n" -src/file1.py:1: [C0111(missing-docstring), factorial] Missing function docstring diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_brackets.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_brackets.txt new file mode 100644 index 0000000000..fbc5ff9f90 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_brackets.txt @@ -0,0 +1,10 @@ +pylint/file1.py:1: [E1300] message +pylint/file1.py:1: [E1301] message +pylint/file1.py:1: [E1302] message +pylint/file1.py:1: [E1303] message +pylint/file1.py:1: [E1304] message +pylint/file1.py:1: [E1305] message +pylint/file1.py:1: [E1306] message +pylint/file1.py:1: [W1201] message +pylint/file1.py:1: [W1300] message +pylint/file1.py:1: [W1301] message diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_names_in_brackets.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_names_in_brackets.txt new file mode 100644 index 0000000000..38fcd1767f --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_names_in_brackets.txt @@ -0,0 +1 @@ +pylint/file1.py:1: [C0111] Missing docstring diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_default_format.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_default_format.txt new file mode 100644 index 0000000000..bba1d554d2 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_default_format.txt @@ -0,0 +1,14 @@ +************* Module src.main +pylint/file1.py:1:0: C0114: Missing module docstring (missing-module-docstring) +pylint/file1.py:1:0: C0116: Missing function or method docstring (missing-function-docstring) +pylint/file1.py:4:8: W0622: Redefining built-in 'max' (redefined-builtin) +pylint/file1.py:5:11: R0124: Redundant comparison - smth == smth (comparison-with-itself) +pylint/file1.py:4:8: W0612: Unused variable 'max' (unused-variable) +pylint/file1.py:2:4: W0612: Unused variable 'unused' (unused-variable) +pylint/file1.py:12:0: C0116: Missing function or method docstring (missing-function-docstring) +pylint/file1.py:14:7: R0123: Comparison to literal (literal-comparison) +pylint/file1.py:24:0: C0116: Missing function or method docstring (missing-function-docstring) + +pylint/file1.py:1:13: TEST1: No problem when issue is on last column +------------------------------------------------------------------ +Your code has been rated at 6.09/10 (previous run: 6.09/10, +0.00) diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_no_column.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_no_column.txt new file mode 100644 index 0000000000..8b4ab2d9e4 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_no_column.txt @@ -0,0 +1,4 @@ +************* Module src.prod +pylint/file1.py:1: [C0111(missing-docstring), ] Missing module docstring +pylint/file1.py:1: [C0103(invalid-name), factorial] Invalid argument name "n" +pylint/file1.py:1: [C0111(missing-docstring), factorial] Missing function docstring diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_unknown_files.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_unknown_files.txt new file mode 100644 index 0000000000..5efa46a55e --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/pylint_report_unknown_files.txt @@ -0,0 +1,13 @@ +************* Module src.main +pylint/unknown_file.py:1:0: C0114: Missing module docstring (missing-module-docstring) +pylint/unknown_file.py:1:0: C0116: Missing function or method docstring (missing-function-docstring) +pylint/unknown_file.py:4:8: W0622: Redefining built-in 'max' (redefined-builtin) +pylint/unknown_file.py:5:11: R0124: Redundant comparison - smth == smth (comparison-with-itself) +pylint/unknown_file.py:4:8: W0612: Unused variable 'max' (unused-variable) +pylint/unknown_file.py:2:4: W0612: Unused variable 'unused' (unused-variable) +pylint/unknown_file.py:12:0: C0116: Missing function or method docstring (missing-function-docstring) +pylint/unknown_file.py:14:7: R0123: Comparison to literal (literal-comparison) +pylint/unknown_file.py:24:0: C0116: Missing function or method docstring (missing-function-docstring) + +------------------------------------------------------------------ +Your code has been rated at 6.09/10 (previous run: 6.09/10, +0.00) diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output.txt deleted file mode 100644 index 3c14fc41ad..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output.txt +++ /dev/null @@ -1,21 +0,0 @@ -code_chunks.py:1: [C0111] Missing docstring -code_chunks.py:1: [C0111, if_else] Missing docstring -code_chunks.py:7: [C0111, if_elif_else] Missing docstring -code_chunks.py:15: [C0111, if_compl_cond1] Missing docstring -code_chunks.py:21: [C0111, if_compl_cond2] Missing docstring -code_chunks.py:27: [C0111, for_else] Missing docstring -code_chunks.py:28: [C0103, for_else] Invalid name "x" (should match [a-z_][a-z0-9_]{2,30}$) -code_chunks.py:28: [W0612, for_else] Unused variable 'x' -code_chunks.py:33: [C0111, while_comp_cond] Missing docstring -code_chunks.py:37: [C0111, while_else] Missing docstring -code_chunks.py:43: [C0111, while_else_compl_cond1] Missing docstring -code_chunks.py:49: [C0111, while_else_compl_cond2] Missing docstring -code_chunks.py:55: [C0111, while_else_compl_cond3] Missing docstring -code_chunks.py:61: [C0111, list_compr] Missing docstring -code_chunks.py:62: [W0104, list_compr] Statement seems to have no effect -code_chunks.py:64: [C0111, list_compr_filter] Missing docstring -code_chunks.py:65: [W0104, list_compr_filter] Statement seems to have no effect -code_chunks.py:67: [C0111, gen_expr] Missing docstring -code_chunks.py:68: [W0104, gen_expr] Statement seems to have no effect -code_chunks.py:70: [C0111, gen_expr_filter] Missing docstring -code_chunks.py:71: [W0104, gen_expr_filter] Statement seems to have no effect diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_new_format.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_new_format.txt deleted file mode 100644 index 3332a7553d..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_new_format.txt +++ /dev/null @@ -1,2 +0,0 @@ -code_chunks.py:1: [C0111(missing-docstring)] Missing docstring - diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_newids.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_newids.txt deleted file mode 100644 index 2ec21efe4d..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_newids.txt +++ /dev/null @@ -1,10 +0,0 @@ -code_chunks.py:1: [E1300] message -code_chunks.py:1: [E1301] message -code_chunks.py:1: [E1302] message -code_chunks.py:1: [E1303] message -code_chunks.py:1: [E1304] message -code_chunks.py:1: [E1305] message -code_chunks.py:1: [E1306] message -code_chunks.py:1: [W1201] message -code_chunks.py:1: [W1300] message -code_chunks.py:1: [W1301] message diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_oldids.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_oldids.txt deleted file mode 100644 index d30cf0ac70..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_oldids.txt +++ /dev/null @@ -1,10 +0,0 @@ -code_chunks.py:1: [E9900] message -code_chunks.py:1: [E9901] message -code_chunks.py:1: [E9902] message -code_chunks.py:1: [E9903] message -code_chunks.py:1: [E9904] message -code_chunks.py:1: [E9905] message -code_chunks.py:1: [E9906] message -code_chunks.py:1: [W6501] message -code_chunks.py:1: [W9900] message -code_chunks.py:1: [W9901] message diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_with_win_paths.txt b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_with_win_paths.txt deleted file mode 100644 index 268c5254ff..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/sample_pylint_output_with_win_paths.txt +++ /dev/null @@ -1 +0,0 @@ -C:\code_chunks.py:1: [C0111] Missing docstring diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file1.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file1.py deleted file mode 100644 index 11b15b1a45..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file1.py +++ /dev/null @@ -1 +0,0 @@ -print("hello") diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file2.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file2.py deleted file mode 100644 index 0239905611..0000000000 --- a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/pylint/src/file2.py +++ /dev/null @@ -1 +0,0 @@ -print("hello2")