-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #895 from madelaney/add-cargo-clippy-support
Adding support for cargo clippy output
- Loading branch information
Showing
6 changed files
with
632 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
331 changes: 331 additions & 0 deletions
331
src/main/java/edu/hm/hafner/analysis/parser/CargoClippyParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,331 @@ | ||
package edu.hm.hafner.analysis.parser; | ||
|
||
import java.util.Optional; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import edu.hm.hafner.analysis.Issue; | ||
import edu.hm.hafner.analysis.IssueBuilder; | ||
import edu.hm.hafner.analysis.LookaheadParser; | ||
import edu.hm.hafner.analysis.Severity; | ||
import edu.hm.hafner.analysis.util.IntegerParser; | ||
import edu.hm.hafner.util.LookaheadStream; | ||
import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
|
||
import static j2html.TagCreator.*; | ||
|
||
/** | ||
* A parser that will attempt to parser for `cargo clippy` warnings/errors/help statements. | ||
* | ||
* @author Mike Delaney | ||
*/ | ||
public class CargoClippyParser extends LookaheadParser { | ||
private static final long serialVersionUID = -2677728927938443703L; | ||
|
||
/** First line in a cargo-clippy message should just be the level and summary of the issue. */ | ||
private static final String CARGO_CLIPPY_REGEX_STRING = "^(?<level>.+):\\s+(?<summary>(?!.+generated [0-9]+ warning).+)"; | ||
|
||
/** | ||
* Find the line that contains the offending file, start line number, and starting column number. | ||
*/ | ||
private static final Pattern CARGO_CLIPPY_FILE_PATTERN = Pattern | ||
.compile("^\\s+-->\\s(?<file>.+):(?<line>\\d+):(?<column>\\d+)"); | ||
|
||
/** Find the lines that are the rustc recommendation on what action should be taken. */ | ||
private static final Pattern CARGO_CLIPPY_REC_PATTERN = Pattern.compile("^(\\s+(\\d+\\s+)?)\\|(.+|\\n)"); | ||
|
||
/** Find the line that is a note ine from the rust compiler. */ | ||
private static final Pattern CARGO_CLIPPY_NOTE_PATTERN = Pattern | ||
.compile("^\\s+=\\snote:\\s`#\\[(?<level>.+)\\((?<category>.+)\\)]`.+"); | ||
|
||
/** Find the line that is a help line from the rust compiler. */ | ||
private static final Pattern CARGO_CLIPPY_HELP_PATTERN = Pattern.compile("^\\s+=\\shelp:(.+?)(?<url>http?s:.*)?"); | ||
|
||
/** RegEx to determine if the current issue continues to the next line. */ | ||
private static final String CARGO_CLIPP_CONTEXT_CONTINUES = "^(\\s+|help\\:|[0-9]+).+"; | ||
|
||
/** Creates a new instance of {@link CargoClippyParser}. */ | ||
public CargoClippyParser() { | ||
super(CARGO_CLIPPY_REGEX_STRING); | ||
} | ||
|
||
@Override | ||
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead, | ||
final IssueBuilder builder) { | ||
final Severity defaultSeverity = Severity.guessFromString(matcher.group("level").trim()); | ||
|
||
FileInformation description = createRecommendationMessage(lookahead); | ||
description.setLevel(matcher.group("level")); | ||
description.setSummary(matcher.group("summary")); | ||
|
||
builder.setFileName(description.getFile()) | ||
.setLineStart(description.getLine()) | ||
.setSeverity(defaultSeverity) | ||
.setCategory(description.getCategory()) | ||
.setColumnStart(description.getColumnStart()) | ||
.setColumnEnd(description.getColumnEnd()) | ||
.setDescription(description.getHelp()) | ||
.setType(description.getLevel()) | ||
.setMessage(description.getSummary()); | ||
|
||
return builder.buildOptional(); | ||
} | ||
|
||
/** | ||
* Look ahead and try to pull out the pertinent information. | ||
* | ||
* @param lookahead | ||
* input stream | ||
* | ||
* @return the collected information about the fix recommendation. | ||
*/ | ||
@SuppressWarnings("PMD.CognitiveComplexity") | ||
private FileInformation createRecommendationMessage(final LookaheadStream lookahead) { | ||
StringBuilder description = new StringBuilder(); | ||
FileInformation fileInformation = new FileInformation(); | ||
|
||
while (lookahead.hasNext(CARGO_CLIPP_CONTEXT_CONTINUES)) { | ||
final String line = lookahead.next(); | ||
|
||
Matcher fileInfoMatcher = CARGO_CLIPPY_FILE_PATTERN.matcher(line); | ||
if (fileInfoMatcher.matches()) { | ||
fileInformation.setFile(fileInfoMatcher.group("file")); | ||
fileInformation.setLine(IntegerParser.parseInt(fileInfoMatcher.group("line"))); | ||
fileInformation.setColumnStart(IntegerParser.parseInt(fileInfoMatcher.group("column"))); | ||
} | ||
else { | ||
Matcher clippyRecommendationMatcher = CARGO_CLIPPY_REC_PATTERN.matcher(line); | ||
if (clippyRecommendationMatcher.matches()) { | ||
description.append(line); | ||
continue; | ||
} | ||
|
||
Matcher clippyHelpMatcher = CARGO_CLIPPY_HELP_PATTERN.matcher(line); | ||
if (clippyHelpMatcher.matches()) { | ||
fileInformation.setHelp(clippyHelpMatcher.group(1), clippyHelpMatcher.group(2)); | ||
continue; | ||
} | ||
|
||
Matcher clippyNotePatcher = CARGO_CLIPPY_NOTE_PATTERN.matcher(line); | ||
if (clippyNotePatcher.matches()) { | ||
fileInformation.setCategory(clippyNotePatcher.group(2)); | ||
fileInformation.setLevel(clippyNotePatcher.group(1)); | ||
} | ||
} | ||
} | ||
|
||
if (description.toString().indexOf('^') != -1) { | ||
fileInformation.setColumnEnd(StringUtils.countMatches(description.toString(), '^') | ||
+ fileInformation.getColumnStart()); | ||
} | ||
|
||
fileInformation.setRecommendation(description.toString()); | ||
return fileInformation; | ||
} | ||
|
||
/** Class to return the additional content of a cargo-clippy message. */ | ||
@SuppressWarnings("PMD.DataClass") | ||
private static final class FileInformation { | ||
private String fileName; | ||
private Integer fileLine; | ||
private String recommendation; | ||
private String category; | ||
private String level; | ||
private Integer columnStart; | ||
private Integer columnEnd; | ||
private String summary; | ||
private String help; | ||
|
||
FileInformation() { | ||
this.fileName = ""; | ||
this.fileLine = 0; | ||
this.recommendation = ""; | ||
this.category = ""; | ||
this.level = ""; | ||
this.columnStart = 0; | ||
this.columnEnd = 0; | ||
this.summary = ""; | ||
this.help = ""; | ||
} | ||
|
||
/** | ||
* Set the filename where the recommendation originated. | ||
* | ||
* @param filename | ||
* the filename of the recommendation. | ||
*/ | ||
public void setFile(final String filename) { | ||
this.fileName = filename; | ||
} | ||
|
||
/** | ||
* Get the filename. | ||
* | ||
* @return The filename (relative to the workspace) | ||
*/ | ||
public String getFile() { | ||
return this.fileName; | ||
} | ||
|
||
/** | ||
* Set the file line where the recommendation originated. | ||
* | ||
* @param fileline | ||
* The line number. | ||
*/ | ||
public void setLine(final Integer fileline) { | ||
this.fileLine = fileline; | ||
} | ||
|
||
/** | ||
* Get the file line. | ||
* | ||
* @return The line where the recommendation happened. | ||
*/ | ||
public Integer getLine() { | ||
return this.fileLine; | ||
} | ||
|
||
/** | ||
* Set the clippy recommendation. | ||
* | ||
* @param recommendation | ||
* The recommendation string. | ||
*/ | ||
public void setRecommendation(final String recommendation) { | ||
this.recommendation = recommendation; | ||
} | ||
|
||
/** | ||
* Returns the current clippy recommendation. | ||
* | ||
* @return The recommendation. | ||
*/ | ||
public String getRecommendation() { | ||
return this.recommendation; | ||
} | ||
|
||
/** | ||
* Sets the category of the recommendation. | ||
* | ||
* @param category | ||
* The category name. | ||
*/ | ||
public void setCategory(final String category) { | ||
this.category = category; | ||
} | ||
|
||
/** | ||
* Returns the category name. | ||
* | ||
* @return The category name. | ||
*/ | ||
public String getCategory() { | ||
return this.category; | ||
} | ||
|
||
/** | ||
* Set the level for the recommendation. | ||
* | ||
* @param level | ||
* The component level. | ||
*/ | ||
public void setLevel(final String level) { | ||
this.level = level; | ||
} | ||
|
||
/** | ||
* Get the issue level. | ||
* | ||
* @return The current recommendation level. | ||
*/ | ||
public String getLevel() { | ||
return this.level; | ||
} | ||
|
||
/** | ||
* Get the column number where the recommendation. | ||
* | ||
* @param column | ||
* The column number. | ||
*/ | ||
public void setColumnStart(final Integer column) { | ||
this.columnStart = column; | ||
} | ||
|
||
/** | ||
* Get the column where the recommendation occurred. | ||
* | ||
* @return The column. | ||
*/ | ||
public Integer getColumnStart() { | ||
return this.columnStart; | ||
} | ||
|
||
/** | ||
* Set column the end column for the current issue. | ||
* | ||
* @param column | ||
* The value for set to notiate the endinging column. | ||
*/ | ||
public void setColumnEnd(final Integer column) { | ||
this.columnEnd = column; | ||
} | ||
|
||
/** | ||
* Get the end column of the current issue. | ||
* | ||
* @return The column number that ends the issue. | ||
*/ | ||
public Integer getColumnEnd() { | ||
return this.columnEnd; | ||
} | ||
|
||
/** | ||
* Set the recommendation summary. | ||
* | ||
* @param summary | ||
* The summary itself. | ||
*/ | ||
public void setSummary(final String summary) { | ||
this.summary = summary; | ||
} | ||
|
||
/** | ||
* Get the recommendation summary. | ||
* | ||
* @return The summary. | ||
*/ | ||
public String getSummary() { | ||
return this.summary; | ||
} | ||
|
||
/** | ||
* Set the help context. | ||
* | ||
* @param text | ||
* the help text | ||
* @param url | ||
* the optional URL | ||
*/ | ||
public void setHelp(final String text, @CheckForNull final String url) { | ||
if (StringUtils.isBlank(url)) { | ||
help = text; | ||
} | ||
else { | ||
help = text + a().withHref(url).withText("cargo clippy documentation").render(); | ||
} | ||
} | ||
|
||
/** | ||
* Get the help context. | ||
* | ||
* @return The help context. | ||
*/ | ||
public String getHelp() { | ||
return this.help; | ||
} | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/edu/hm/hafner/analysis/registry/CargoClippyDescriptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package edu.hm.hafner.analysis.registry; | ||
|
||
import edu.hm.hafner.analysis.IssueParser; | ||
import edu.hm.hafner.analysis.parser.CargoClippyParser; | ||
|
||
/** | ||
* A descriptor for Cargo Clippy. | ||
* | ||
* @author Ullrich Hafner | ||
*/ | ||
class CargoClippyDescriptor extends ParserDescriptor { | ||
private static final String ID = "clippy"; | ||
private static final String NAME = "Cargo Clippy"; | ||
|
||
CargoClippyDescriptor() { | ||
super(ID, NAME); | ||
} | ||
|
||
@Override | ||
public IssueParser createParser(final Option... options) { | ||
return new CargoClippyParser(); | ||
} | ||
|
||
@Override | ||
public String getUrl() { | ||
return "https://github.com/rust-lang/rust-clippy"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.