CI/CD support for rust projects

feat: Able to analyze and publish rust clippy report (OD-2113)
feat: Job template for rust projects (OD-2112)
This commit is contained in:
Robin Shen 2024-10-05 16:36:19 +08:00
parent 4ef6a697d5
commit f84faed69d
27 changed files with 545 additions and 49 deletions

View File

@ -350,6 +350,11 @@
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>com.moandjiezana.toml</groupId>
<artifactId>toml4j</artifactId>
<version>0.7.2</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-hibernate53</artifactId>

View File

@ -3,8 +3,11 @@ package io.onedev.server.util;
import org.dom4j.io.SAXReader;
import org.xml.sax.SAXException;
import java.util.regex.Pattern;
public class XmlUtils {
private static final Pattern DOC_TYPE = Pattern.compile("<!DOCTYPE\\s.*?>", Pattern.DOTALL);
// Prevent XXE attack as the xml might be provided by malicious users
public static void disallowDocTypeDecl(SAXReader reader) {
try {
@ -15,7 +18,7 @@ public class XmlUtils {
}
public static String stripDoctype(String xml) {
return xml.replaceFirst("<!DOCTYPE\\s.*?>", "");
return DOC_TYPE.matcher(xml).replaceFirst("");
}
}

View File

@ -1,5 +1,20 @@
.problem-popover.popover {
max-width: 370px;
max-width: 360px;
}
@media(min-width: 600px) {
.problem-popover.popover {
max-width: 400px;
}
}
@media(min-width: 900px) {
.problem-popover.popover {
max-width: 600px;
}
}
@media(min-width: 1200px) {
.problem-popover.popover {
max-width: 900px;
}
}
.problem-popover .problem-content+.problem-content {
margin-top: 0.75rem;

View File

@ -848,4 +848,4 @@ span.keycap {
border-radius: 2px;
background: #C9F7F5;
color: #1BC5BD;
}
}

View File

@ -27,6 +27,7 @@
<module>server-plugin-buildspec-cmake</module>
<module>server-plugin-buildspec-bazel</module>
<module>server-plugin-buildspec-golang</module>
<module>server-plugin-buildspec-rust</module>
<module>server-plugin-authenticator-ldap</module>
<module>server-plugin-mailservice-smtpimap</module>
<module>server-plugin-mailservice-office365</module>
@ -63,6 +64,7 @@
<module>server-plugin-report-ruff</module>
<module>server-plugin-report-mypy</module>
<module>server-plugin-report-cppcheck</module>
<module>server-plugin-report-clippy</module>
<module>server-plugin-sso-discord</module>
<module>server-plugin-notification-slack</module>
<module>server-plugin-notification-discord</module>

View File

@ -23,11 +23,6 @@
<artifactId>server-plugin-report-ruff</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.moandjiezana.toml</groupId>
<artifactId>toml4j</artifactId>
<version>0.7.2</version>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.buildspec.python.PythonModule</moduleClass>

View File

@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-buildspec-rust</artifactId>
<parent>
<groupId>io.onedev</groupId>
<artifactId>server-plugin</artifactId>
<version>11.2.1</version>
</parent>
<dependencies>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-junit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-cobertura</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-clippy</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.buildspec.rust.RustModule</moduleClass>
</properties>
</project>

View File

@ -0,0 +1,137 @@
package io.onedev.server.plugin.buildspec.rust;
import com.google.common.collect.Lists;
import com.moandjiezana.toml.Toml;
import io.onedev.k8shelper.ExecuteCondition;
import io.onedev.server.buildspec.job.Job;
import io.onedev.server.buildspec.job.JobSuggestion;
import io.onedev.server.buildspec.job.trigger.BranchUpdateTrigger;
import io.onedev.server.buildspec.job.trigger.PullRequestUpdateTrigger;
import io.onedev.server.buildspec.step.*;
import io.onedev.server.git.Blob;
import io.onedev.server.git.BlobIdent;
import io.onedev.server.model.Project;
import io.onedev.server.plugin.report.clippy.PublishClippyReportStep;
import io.onedev.server.plugin.report.cobertura.PublishCoberturaReportStep;
import io.onedev.server.plugin.report.junit.PublishJUnitReportStep;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class RustJobSuggestion implements JobSuggestion {
private GenerateChecksumStep newChecksumGenerateStep(String name, String files) {
var generateChecksum = new GenerateChecksumStep();
generateChecksum.setName(name);
generateChecksum.setFiles(files);
generateChecksum.setTargetFile("checksum");
return generateChecksum;
}
private Job newJob() {
Job job = new Job();
job.setName("rust ci");
return job;
}
private void addCommonJobsAndTriggers(Job job) {
var checkout = new CheckoutStep();
checkout.setName("checkout code");
job.getSteps().add(0, checkout);
job.getSteps().add(newUnitTestReportPublishStep());
job.getSteps().add(newCoverageReportPublishStep());
job.getSteps().add(newClippyReportPublishStep());
job.getTriggers().add(new BranchUpdateTrigger());
job.getTriggers().add(new PullRequestUpdateTrigger());
}
private PublishJUnitReportStep newUnitTestReportPublishStep() {
var publishUnitTestReport = new PublishJUnitReportStep();
publishUnitTestReport.setName("publish unit test report");
publishUnitTestReport.setReportName("Unit Test");
publishUnitTestReport.setFilePatterns("target/nextest/default/test-result.xml");
publishUnitTestReport.setCondition(ExecuteCondition.ALWAYS);
return publishUnitTestReport;
}
private PublishCoberturaReportStep newCoverageReportPublishStep() {
var publishCoverageReportStep = new PublishCoberturaReportStep();
publishCoverageReportStep.setName("publish coverage report");
publishCoverageReportStep.setReportName("Coverage");
publishCoverageReportStep.setFilePatterns("coverage.xml");
publishCoverageReportStep.setCondition(ExecuteCondition.ALWAYS);
return publishCoverageReportStep;
}
private PublishClippyReportStep newClippyReportPublishStep() {
var publishClippyReportStep = new PublishClippyReportStep();
publishClippyReportStep.setName("publish clippy report");
publishClippyReportStep.setReportName("Clippy");
publishClippyReportStep.setFilePatterns("check-result.json");
publishClippyReportStep.setCondition(ExecuteCondition.ALWAYS);
return publishClippyReportStep;
}
@Override
public Collection<Job> suggestJobs(Project project, ObjectId commitId) {
List<Job> jobs = new ArrayList<>();
Blob blob;
if ((blob = project.getBlob(new BlobIdent(commitId.name(), "Cargo.toml", FileMode.TYPE_FILE), false)) != null) {
Job job = newJob();
var cargoToml = new Toml().read(blob.getText().getContent());
if (cargoToml.getString("package.version") != null) {
CommandStep detectBuildVersion = new CommandStep();
detectBuildVersion.setName("detect build version");
detectBuildVersion.setImage("1dev/yq:1.0.0");
detectBuildVersion.getInterpreter().setCommands("yq '.package.version' Cargo.toml > buildVersion");
job.getSteps().add(detectBuildVersion);
SetBuildVersionStep setBuildVersion = new SetBuildVersionStep();
setBuildVersion.setName("set build version");
setBuildVersion.setBuildVersion("@file:buildVersion@");
job.getSteps().add(setBuildVersion);
}
job.getSteps().add(newChecksumGenerateStep("generate dependency checksum", "**/Cargo.toml **/Cargo.lock"));
var setupCache = new SetupCacheStep();
setupCache.setName("set up dependency cache");
setupCache.setKey("rust_cache_@file:checksum@");
setupCache.setPaths(Lists.newArrayList("/root/.cache/cargo"));
setupCache.getLoadKeys().add("rust_cache");
job.getSteps().add(setupCache);
CommandStep buildAndTest = new CommandStep();
buildAndTest.setName("build and test");
buildAndTest.setImage("1dev/rust:1.0.3");
buildAndTest.getInterpreter().setCommands("" +
"set -e\n" +
"\n" +
"# Set CARGO_HOME to cache folder so that downloaded artifacts can be populated there. Note that we cannot cache '/root/.cargo' directly as cache folder will be mounted from an empty folder initially\n" +
"export CARGO_HOME=/root/.cache/cargo\n" +
"mkdir -p .config\n" +
"cat << EOF > .config/nextest.toml\n" +
"[profile.default.junit]\n" +
"path = \"test-result.xml\"\n" +
"store-success-output = true\n" +
"store-failure-output = true\n" +
"EOF\n" +
"cargo llvm-cov nextest --lcov --output-path lcov.info\n" +
"lcov_cobertura lcov.info -o coverage.xml\n" +
"# cargo clippy --message-format=json > check-result.json\n" +
"\n" +
"# Make sure all files inside $CARGO_HOME is accessible by OneDev outside of container for cache upload\n" +
"chmod -R o+r $CARGO_HOME");
job.getSteps().add(buildAndTest);
addCommonJobsAndTriggers(job);
jobs.add(job);
}
return jobs;
}
}

View File

@ -0,0 +1,23 @@
package io.onedev.server.plugin.buildspec.rust;
import com.google.common.collect.Lists;
import io.onedev.commons.loader.AbstractPluginModule;
import io.onedev.server.buildspec.job.JobSuggestion;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.util.ScriptContribution;
/**
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
*
*/
public class RustModule extends AbstractPluginModule {
@Override
protected void configure() {
super.configure();
// put your guice bindings here
contribute(JobSuggestion.class, RustJobSuggestion.class);
}
}

View File

@ -9,9 +9,9 @@ import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.CodeProblem.Severity;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.problem.PublishProblemReportStep;
import io.onedev.server.util.XmlUtils;
@ -19,7 +19,6 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.unbescape.html.HtmlEscape;
import javax.validation.constraints.NotEmpty;
import java.io.File;
@ -29,6 +28,8 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
@Editable(order=10000, group=StepGroup.PUBLISH, name="Checkstyle Report")
public class PublishCheckstyleReportStep extends PublishProblemReportStep {
@ -85,8 +86,7 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
else
severity = Severity.LOW;
String message = violationElement.attributeValue("source") + ": "
+ HtmlEscape.escapeHtml5(violationElement.attributeValue("message"));
String message = violationElement.attributeValue("source") + ": " + violationElement.attributeValue("message");
int lineNo = Integer.parseInt(violationElement.attributeValue("line"))-1;
String column = violationElement.attributeValue("column");
@ -98,7 +98,7 @@ public class PublishCheckstyleReportStep extends PublishProblemReportStep {
location = new PlanarRange(lineNo, -1, lineNo, -1, tabWidth);
}
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), message));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), escapeHtml5(message)));
}
} else {
logger.warning("Unable to find blob path for file: " + filePath);

View File

@ -0,0 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-report-clippy</artifactId>
<parent>
<groupId>io.onedev</groupId>
<artifactId>server-plugin</artifactId>
<version>11.2.1</version>
</parent>
<dependencies>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-problem</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.report.clippy.ClippyModule</moduleClass>
</properties>
</project>

View File

@ -0,0 +1,39 @@
package io.onedev.server.plugin.report.clippy;
import java.util.Collection;
import com.google.common.collect.Sets;
import io.onedev.commons.loader.AbstractPluginModule;
import io.onedev.commons.loader.ImplementationProvider;
import io.onedev.server.buildspec.step.PublishReportStep;
/**
* NOTE: Do not forget to rename moduleClass property defined in the pom if you've renamed this class.
*
*/
public class ClippyModule extends AbstractPluginModule {
@Override
protected void configure() {
super.configure();
// put your guice bindings here
contribute(ImplementationProvider.class, new ImplementationProvider() {
@Override
public Class<?> getAbstractClass() {
return PublishReportStep.class;
}
@Override
public Collection<Class<?>> getImplementations() {
return Sets.newHashSet(PublishClippyReportStep.class);
}
});
}
}

View File

@ -0,0 +1,69 @@
package io.onedev.server.plugin.report.clippy;
import com.fasterxml.jackson.databind.JsonNode;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import javax.annotation.Nullable;
import java.util.*;
import static java.lang.Integer.parseInt;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
public class ClippyReportParser {
@Nullable
private static String getBlobPath(Build build, Map<String, Optional<String>> blobPaths,
String filePath, TaskLogger logger) {
var blobPath = blobPaths.get(filePath);
if (blobPath == null) {
blobPath = Optional.ofNullable(build.getBlobPath(filePath));
if (blobPath.isEmpty())
logger.warning("Unable to find blob path for file: " + filePath);
blobPaths.put(filePath, blobPath);
}
return blobPath.orElse(null);
}
public static List<CodeProblem> parse(Build build, List<JsonNode> problemNodes, TaskLogger logger) {
var problems = new ArrayList<CodeProblem>();
Map<String, Optional<String>> blobPaths = new HashMap<>();
for (var problemNode: problemNodes) {
var messageNode = problemNode.get("message");
if (messageNode != null && messageNode.hasNonNull("spans")
&& !messageNode.get("spans").isEmpty() && messageNode.hasNonNull("rendered")) {
CodeProblem.Severity severity = CodeProblem.Severity.LOW;
var levelNode = messageNode.get("level");
if (levelNode != null) {
switch (levelNode.asText()) {
case "error":
severity = CodeProblem.Severity.HIGH;
break;
case "warning":
severity = CodeProblem.Severity.MEDIUM;
}
}
var message = new StringBuilder();
message.append(messageNode.get("rendered").asText());
var spanNode = messageNode.get("spans").iterator().next();
var blobPath = getBlobPath(build, blobPaths, spanNode.get("file_name").asText(), logger);
if (blobPath != null) {
PlanarRange location;
int lineStart = parseInt(spanNode.get("line_start").asText());
int columnStart = parseInt(spanNode.get("column_start").asText());
int lineEnd = parseInt(spanNode.get("line_end").asText());
int columnEnd = parseInt(spanNode.get("column_end").asText());
if (lineStart == lineEnd && columnStart == columnEnd)
columnEnd++;
location = new PlanarRange(lineStart - 1, columnStart - 1, lineEnd - 1, columnEnd - 1);
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), escapeHtml5(message.toString())));
}
}
}
return problems;
}
}

View File

@ -0,0 +1,70 @@
package io.onedev.server.plugin.report.clippy;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.problem.PublishProblemReportStep;
import javax.validation.constraints.NotEmpty;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Editable(order=10000, group=StepGroup.PUBLISH, name="Clippy Report")
public class PublishClippyReportStep extends PublishProblemReportStep {
private static final long serialVersionUID = 1L;
@Editable(order=100, description="Specify <a href='https://github.com/rust-lang/rust-clippy'>rust clippy</a> json output file relative to <a href='https://docs.onedev.io/concepts#job-workspace'>job workspace</a>. "
+ "This file can be generated with clippy json output option, for instance <code>cargo clippy --message-format json>check-result.json</code>. Use * or ? for pattern match")
@Interpolative(variableSuggester="suggestVariables")
@Patterns(path=true)
@NotEmpty
@Override
public String getFilePatterns() {
return super.getFilePatterns();
}
@Override
public void setFilePatterns(String filePatterns) {
super.setFilePatterns(filePatterns);
}
@SuppressWarnings("unused")
private static List<InputSuggestion> suggestVariables(String matchWith) {
return BuildSpec.suggestVariables(matchWith, true, true, false);
}
@Override
protected List<CodeProblem> process(Build build, File inputDir, File reportDir, TaskLogger logger) {
try {
var mapper = OneDev.getInstance(ObjectMapper.class);
List<CodeProblem> problems = new ArrayList<>();
int baseLen = inputDir.getAbsolutePath().length()+1;
for (File file: FileUtils.listFiles(inputDir, Lists.newArrayList("**"), Lists.newArrayList())) {
logger.log("Processing clippy report: " + file.getAbsolutePath().substring(baseLen));
var problemNodes = new ArrayList<JsonNode>();
for (var line: FileUtils.readLines(file, StandardCharsets.UTF_8))
problemNodes.add(mapper.readTree(line));
problems.addAll(ClippyReportParser.parse(build, problemNodes, logger));
}
return problems;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,55 @@
package io.onedev.server.plugin.report.clippy;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import io.onedev.server.util.IOUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
public class ClippyReportParserTest {
@Test
public void testParse() {
try (InputStream is = Resources.getResource(ClippyReportParserTest.class, "check-result.json").openStream()) {
Build build = new Build() {
@Override
public String getBlobPath(String filePath) {
return filePath;
}
};
build.setCommitHash(ObjectId.zeroId().name());
var mapper = new ObjectMapper();
var problemNodes = new ArrayList<JsonNode>();
for (var line: IOUtils.readLines(is, StandardCharsets.UTF_8))
problemNodes.add(mapper.readTree(line));
var problems = ClippyReportParser.parse(build, problemNodes, new TaskLogger() {
@Override
public void log(String message, @Nullable String sessionId) {
}
});
assertEquals(5, problems.size());
assertEquals(CodeProblem.Severity.MEDIUM, problems.get(0).getSeverity());
assertEquals("29.23-29.54-1", ((BlobTarget)problems.get(0).getTarget()).getLocation().toString());
assertEquals(CodeProblem.Severity.MEDIUM, problems.get(3).getSeverity());
assertEquals("78.5-78.6-1", ((BlobTarget)problems.get(3).getTarget()).getLocation().toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,33 @@
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.85","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/proc-macro2-1.0.85/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/proc-macro2-1.0.85/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/Users/robin/temp/zola/target/debug/build/proc-macro2-f4040980c7bda631/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/utils#0.1.0","manifest_path":"/Users/robin/temp/zola/components/utils/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"utils","src_path":"/Users/robin/temp/zola/components/utils/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: length comparison to zero\n --> components/utils/src/de.rs:29:23\n |\n29 | ... fraction = if fraction_intermediate.len() > 0 { fraction_intermediat...\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!fraction_intermediate.is_empty()`\n |\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero\n = note: `#[warn(clippy::len_zero)]` on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"help","message":"for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero","rendered":null,"spans":[]},{"children":[],"code":null,"level":"note","message":"`#[warn(clippy::len_zero)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"using `!is_empty` is clearer and more explicit","rendered":null,"spans":[{"byte_end":1646,"byte_start":1615,"column_end":54,"column_start":23,"expansion":null,"file_name":"components/utils/src/de.rs","is_primary":true,"label":null,"line_end":29,"line_start":29,"suggested_replacement":"!fraction_intermediate.is_empty()","suggestion_applicability":"MachineApplicable","text":[{"highlight_end":54,"highlight_start":23,"text":" let fraction = if fraction_intermediate.len() > 0 { fraction_intermediate } else { \"0\" };"}]}]}],"code":{"code":"clippy::len_zero","explanation":null},"level":"warning","message":"length comparison to zero","spans":[{"byte_end":1646,"byte_start":1615,"column_end":54,"column_start":23,"expansion":null,"file_name":"components/utils/src/de.rs","is_primary":true,"label":null,"line_end":29,"line_start":29,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":54,"highlight_start":23,"text":" let fraction = if fraction_intermediate.len() > 0 { fraction_intermediate } else { \"0\" };"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/utils#0.1.0","manifest_path":"/Users/robin/temp/zola/components/utils/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"utils","src_path":"/Users/robin/temp/zola/components/utils/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: this expression creates a reference which is immediately dereferenced by the compiler\n --> components/utils/src/fs.rs:80:19\n |\n80 | create_parent(&dest)?;\n | ^^^^^ help: change this to: `dest`\n |\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow\n = note: `#[warn(clippy::needless_borrow)]` on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"help","message":"for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow","rendered":null,"spans":[]},{"children":[],"code":null,"level":"note","message":"`#[warn(clippy::needless_borrow)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"change this to","rendered":null,"spans":[{"byte_end":2812,"byte_start":2807,"column_end":24,"column_start":19,"expansion":null,"file_name":"components/utils/src/fs.rs","is_primary":true,"label":null,"line_end":80,"line_start":80,"suggested_replacement":"dest","suggestion_applicability":"MachineApplicable","text":[{"highlight_end":24,"highlight_start":19,"text":" create_parent(&dest)?;"}]}]}],"code":{"code":"clippy::needless_borrow","explanation":null},"level":"warning","message":"this expression creates a reference which is immediately dereferenced by the compiler","spans":[{"byte_end":2812,"byte_start":2807,"column_end":24,"column_start":19,"expansion":null,"file_name":"components/utils/src/fs.rs","is_primary":true,"label":null,"line_end":80,"line_start":80,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":24,"highlight_start":19,"text":" create_parent(&dest)?;"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/utils#0.1.0","manifest_path":"/Users/robin/temp/zola/components/utils/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"utils","src_path":"/Users/robin/temp/zola/components/utils/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: this manual char comparison can be written more succinctly\n --> components/utils/src/slugs.rs:26:38\n |\n26 | let trimmed = s.trim_end_matches(|c| c == ' ' || c == '.');\n | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `[' ', '.']`\n |\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison\n = note: `#[warn(clippy::manual_pattern_char_comparison)]` on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"help","message":"for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison","rendered":null,"spans":[]},{"children":[],"code":null,"level":"note","message":"`#[warn(clippy::manual_pattern_char_comparison)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"consider using an array of `char`","rendered":null,"spans":[{"byte_end":873,"byte_start":849,"column_end":62,"column_start":38,"expansion":null,"file_name":"components/utils/src/slugs.rs","is_primary":true,"label":null,"line_end":26,"line_start":26,"suggested_replacement":"[' ', '.']","suggestion_applicability":"MachineApplicable","text":[{"highlight_end":62,"highlight_start":38,"text":" let trimmed = s.trim_end_matches(|c| c == ' ' || c == '.');"}]}]}],"code":{"code":"clippy::manual_pattern_char_comparison","explanation":null},"level":"warning","message":"this manual char comparison can be written more succinctly","spans":[{"byte_end":873,"byte_start":849,"column_end":62,"column_start":38,"expansion":null,"file_name":"components/utils/src/slugs.rs","is_primary":true,"label":null,"line_end":26,"line_start":26,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":62,"highlight_start":38,"text":" let trimmed = s.trim_end_matches(|c| c == ' ' || c == '.');"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/utils#0.1.0","manifest_path":"/Users/robin/temp/zola/components/utils/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"utils","src_path":"/Users/robin/temp/zola/components/utils/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: 3 warnings emitted\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"warning","message":"3 warnings emitted","spans":[]}}
{"reason":"compiler-artifact","package_id":"path+file:///Users/robin/temp/zola/components/utils#0.1.0","manifest_path":"/Users/robin/temp/zola/components/utils/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"utils","src_path":"/Users/robin/temp/zola/components/utils/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libutils-c09278f6e65bd41f.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"path+file:///Users/robin/temp/zola/components/console#0.1.0","manifest_path":"/Users/robin/temp/zola/components/console/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"console","src_path":"/Users/robin/temp/zola/components/console/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libconsole-465709f8f3e18ca5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.1.16","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/getrandom-0.1.16/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"getrandom","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/getrandom-0.1.16/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libgetrandom-7813f0a8d28687a3.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.7","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-1.0.7/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"anstyle","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-1.0.7/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libanstyle-19c86a7d68421148.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.0","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/is_terminal_polyfill-1.70.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"is_terminal_polyfill","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/is_terminal_polyfill-1.70.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libis_terminal_polyfill-1c35c4e83590019b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/powerfmt-0.2.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"powerfmt","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/powerfmt-0.2.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libpowerfmt-6998240b76618d9b.rlib","/Users/robin/temp/zola/target/debug/deps/libpowerfmt-6998240b76618d9b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#cfg-if@0.1.10","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cfg-if-0.1.10/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cfg_if","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cfg-if-0.1.10/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libcfg_if-5f7cbaaf39cc4df9.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#nix@0.28.0","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/nix-0.28.0/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/nix-0.28.0/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["fs","process","signal"],"filenames":["/Users/robin/temp/zola/target/debug/build/nix-f8ac794f013e692c/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"path+file:///Users/robin/temp/zola/components/config#0.1.0","manifest_path":"/Users/robin/temp/zola/components/config/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"config","src_path":"/Users/robin/temp/zola/components/config/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libconfig-f8438e411985c0b9.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rand_core@0.5.1","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rand_core-0.5.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand_core","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rand_core-0.5.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","getrandom","std"],"filenames":["/Users/robin/temp/zola/target/debug/deps/librand_core-213d5b82d2159aa0.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.14","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstream-0.6.14/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"anstream","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstream-0.6.14/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["auto","default","wincon"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libanstream-d0b0ebe9353fae29.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/deranged-0.3.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"deranged","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/deranged-0.3.11/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","powerfmt","std"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libderanged-14fa1f7dc06bb337.rlib","/Users/robin/temp/zola/target/debug/deps/libderanged-14fa1f7dc06bb337.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#net2@0.2.39","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/net2-0.2.39/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"net2","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/net2-0.2.39/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","duration"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libnet2-4a27462e4f22df38.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#block-padding@0.1.5","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/block-padding-0.1.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"block_padding","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/block-padding-0.1.5/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libblock_padding-abdeba7f674c2ab5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#kamadak-exif@0.5.5","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/kamadak-exif-0.5.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"exif","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/kamadak-exif-0.5.5/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libexif-435c9b5453728dd5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#unicase@2.7.0","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/unicase-2.7.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicase","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/unicase-2.7.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libunicase-f28739690847d4bb.rlib","/Users/robin/temp/zola/target/debug/deps/libunicase-f28739690847d4bb.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"path+file:///Users/robin/temp/zola/components/markdown#0.1.0","manifest_path":"/Users/robin/temp/zola/components/markdown/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"markdown","src_path":"/Users/robin/temp/zola/components/markdown/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libmarkdown-2af279526114c711.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#toml@0.5.11","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/toml-0.5.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"toml","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/toml-0.5.11/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/Users/robin/temp/zola/target/debug/deps/libtoml-4a03a679e33f15d7.rlib","/Users/robin/temp/zola/target/debug/deps/libtoml-4a03a679e33f15d7.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/heck-0.5.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"heck","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/heck-0.5.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libheck-c7f34bf60343d28b.rlib","/Users/robin/temp/zola/target/debug/deps/libheck-c7f34bf60343d28b.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.1","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/clap_lex-0.7.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"clap_lex","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/clap_lex-0.7.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libclap_lex-ba1a9257ad086ed8.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/strsim-0.11.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"strsim","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/strsim-0.11.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libstrsim-450171a639ada5f5.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.4","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/mime_guess-2.0.4/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/mime_guess-2.0.4/build.rs","edition":"2015","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","rev-mappings"],"filenames":["/Users/robin/temp/zola/target/debug/build/mime_guess-b5eed588b8a20482/build-script-build"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rand_chacha@0.2.2","manifest_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rand_chacha-0.2.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rand_chacha","src_path":"/Users/robin/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rand_chacha-0.2.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/Users/robin/temp/zola/target/debug/deps/librand_chacha-2d378126a001264e.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-artifact","package_id":"path+file:///Users/robin/temp/zola/components/imageproc#0.1.0","manifest_path":"/Users/robin/temp/zola/components/imageproc/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"imageproc","src_path":"/Users/robin/temp/zola/components/imageproc/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/Users/robin/temp/zola/target/debug/deps/libimageproc-3d67d1373cb34e71.rmeta"],"executable":null,"fresh":true}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/content#0.1.0","manifest_path":"/Users/robin/temp/zola/components/content/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"content","src_path":"/Users/robin/temp/zola/components/content/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: doc list item without indentation\n --> components/content/src/front_matter/page.rs:78:5\n |\n78 | /// This tries each in order.\n | ^\n |\n = help: if this is supposed to be its own paragraph, add a blank line\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation\n = note: `#[warn(clippy::doc_lazy_continuation)]` on by default\nhelp: indent this line\n |\n78 | /// This tries each in order.\n | +++\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"help","message":"if this is supposed to be its own paragraph, add a blank line","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation","rendered":null,"spans":[]},{"children":[],"code":null,"level":"note","message":"`#[warn(clippy::doc_lazy_continuation)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"indent this line","rendered":null,"spans":[{"byte_end":3155,"byte_start":3155,"column_end":5,"column_start":5,"expansion":null,"file_name":"components/content/src/front_matter/page.rs","is_primary":true,"label":null,"line_end":78,"line_start":78,"suggested_replacement":" ","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":5,"highlight_start":5,"text":"/// This tries each in order."}]}]}],"code":{"code":"clippy::doc_lazy_continuation","explanation":null},"level":"warning","message":"doc list item without indentation","spans":[{"byte_end":3155,"byte_start":3155,"column_end":5,"column_start":5,"expansion":null,"file_name":"components/content/src/front_matter/page.rs","is_primary":true,"label":null,"line_end":78,"line_start":78,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":5,"highlight_start":5,"text":"/// This tries each in order."}]}]}}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/content#0.1.0","manifest_path":"/Users/robin/temp/zola/components/content/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"content","src_path":"/Users/robin/temp/zola/components/content/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: the borrowed expression implements the required traits\n --> components/content/src/page.rs:271:35\n |\n271 | .strip_prefix(&base_path.join(\"content\"))\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `base_path.join(\"content\")`\n |\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args\n = note: `#[warn(clippy::needless_borrows_for_generic_args)]` on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"help","message":"for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args","rendered":null,"spans":[]},{"children":[],"code":null,"level":"note","message":"`#[warn(clippy::needless_borrows_for_generic_args)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"change this to","rendered":null,"spans":[{"byte_end":10380,"byte_start":10354,"column_end":61,"column_start":35,"expansion":null,"file_name":"components/content/src/page.rs","is_primary":true,"label":null,"line_end":271,"line_start":271,"suggested_replacement":"base_path.join(\"content\")","suggestion_applicability":"MachineApplicable","text":[{"highlight_end":61,"highlight_start":35,"text":" .strip_prefix(&base_path.join(\"content\"))"}]}]}],"code":{"code":"clippy::needless_borrows_for_generic_args","explanation":null},"level":"warning","message":"the borrowed expression implements the required traits","spans":[{"byte_end":10380,"byte_start":10354,"column_end":61,"column_start":35,"expansion":null,"file_name":"components/content/src/page.rs","is_primary":true,"label":null,"line_end":271,"line_start":271,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":61,"highlight_start":35,"text":" .strip_prefix(&base_path.join(\"content\"))"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///Users/robin/temp/zola/components/content#0.1.0","manifest_path":"/Users/robin/temp/zola/components/content/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"content","src_path":"/Users/robin/temp/zola/components/content/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: 2 warnings emitted\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"warning","message":"2 warnings emitted","spans":[]}}
{"reason":"build-finished","success":true}

View File

@ -13,12 +13,6 @@
<artifactId>server-plugin-report-coverage</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>commons-loader</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.report.cobertura.CoberturaModule</moduleClass>

View File

@ -1,7 +1,6 @@
package io.onedev.server.plugin.report.cobertura;
import com.google.common.io.Resources;
import io.onedev.commons.loader.AppLoaderMocker;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.CoverageStatus;
@ -18,7 +17,7 @@ import java.util.HashMap;
import static org.junit.Assert.*;
public class PublishCoberturaReportStepTest extends AppLoaderMocker {
public class PublishCoberturaReportStepTest {
@Test
public void test() {
@ -127,13 +126,4 @@ public class PublishCoberturaReportStepTest extends AppLoaderMocker {
}
}
@Override
protected void setup() {
}
@Override
protected void teardown() {
}
}

View File

@ -11,6 +11,7 @@ import javax.annotation.Nullable;
import java.util.*;
import static java.lang.Integer.parseInt;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
public class CppcheckReportParser {
@ -69,7 +70,7 @@ public class CppcheckReportParser {
location = new PlanarRange(line-1, column-1, line-1, column);
else
location = new PlanarRange(line-1, -1, line-1, -1);
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), message.toString()));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), escapeHtml5(message.toString())));
}
}
}

View File

@ -5,8 +5,8 @@ import com.google.common.base.Splitter;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import org.apache.commons.lang3.math.NumberUtils;
@ -16,6 +16,7 @@ import java.util.*;
import static io.onedev.server.codequality.CodeProblem.Severity.LOW;
import static io.onedev.server.codequality.CodeProblem.Severity.MEDIUM;
import static java.lang.Integer.parseInt;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
public class MypyReportParser {
@ -75,7 +76,7 @@ public class MypyReportParser {
if (blobPath.isPresent()) {
var location = new PlanarRange(parsedLine.fromRow-1, parsedLine.fromColumn-1, parsedLine.toRow-1, parsedLine.toColumn, 1);
var severity = parsedLine.error?MEDIUM:LOW;
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), parsedLine.message));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), escapeHtml5(parsedLine.message)));
}
}

View File

@ -9,9 +9,9 @@ import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.CodeProblem.Severity;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.problem.PublishProblemReportStep;
import io.onedev.server.util.XmlUtils;
@ -19,7 +19,6 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.unbescape.html.HtmlEscape;
import javax.validation.constraints.NotEmpty;
import java.io.File;
@ -29,6 +28,8 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
@Editable(order=10000, group=StepGroup.PUBLISH, name="PMD Report")
public class PublishPMDReportStep extends PublishProblemReportStep {
@ -92,7 +93,7 @@ public class PublishPMDReportStep extends PublishProblemReportStep {
else
severity = Severity.LOW;
String message = type + ": " + HtmlEscape.escapeHtml5(violationElement.getText());
String message = escapeHtml5(type + ": " + violationElement.getText());
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), message));
}
} else {

View File

@ -319,7 +319,7 @@ public class ProblemReportPage extends BuildReportPage {
severityLabel.add(AttributeAppender.append("class", "badge-warning"));
else
severityLabel.add(AttributeAppender.append("class", "badge-secondary"));
item.add(new Label("message", problem.getMessage()).setEscapeModelStrings(false));
if (problem.getTarget() instanceof BlobTarget

View File

@ -3,13 +3,14 @@ package io.onedev.server.plugin.report.pylint;
import com.fasterxml.jackson.databind.JsonNode;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import java.util.*;
import static java.lang.Integer.parseInt;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
public class PylintReportParser {
@ -48,7 +49,7 @@ public class PylintReportParser {
if (problemNode.hasNonNull("endColumn"))
endColumn = parseInt(problemNode.get("endColumn").asText());
var location = new PlanarRange(line-1, column, endLine-1, endColumn);
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), message));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), escapeHtml5(message)));
}
}
return problems;

View File

@ -10,9 +10,9 @@ import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.CodeProblem.Severity;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.problem.PublishProblemReportStep;
import io.onedev.server.util.XmlUtils;
@ -75,10 +75,10 @@ public class PublishRoslynatorReportStep extends PublishProblemReportStep {
Map<String, String> messages = new HashMap<>();
for (var diagnosticElement: codeAnalysisElement.element("Summary").elements("Diagnostic")) {
var id = diagnosticElement.attributeValue("Id");
var message = escapeHtml5(diagnosticElement.attributeValue("Title"));
var message = diagnosticElement.attributeValue("Title");
var description = diagnosticElement.elementText("Description");
if (description != null)
message += "<br><br>" + escapeHtml5(description);
message += "\n\n" + description;
messages.put(id, message);
}
for (var projectElement: codeAnalysisElement.element("Projects").elements("Project")) {
@ -112,7 +112,7 @@ public class PublishRoslynatorReportStep extends PublishProblemReportStep {
int line = parseInt(locationElement.attributeValue("Line"));
int character = parseInt(locationElement.attributeValue("Character"));
var location = new PlanarRange(line-1, character-1, line-1, character);
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), message));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath.get(), location), escapeHtml5(message)));
}
}
}

View File

@ -3,13 +3,14 @@ package io.onedev.server.plugin.report.ruff;
import com.fasterxml.jackson.databind.JsonNode;
import io.onedev.commons.utils.PlanarRange;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.model.Build;
import java.util.*;
import static java.lang.Integer.parseInt;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
public class RuffReportParser {
@ -33,7 +34,7 @@ public class RuffReportParser {
int endColumn = parseInt(problemNode.get("end_location").get("column").asText());
var location = new PlanarRange(row-1, column-1, endRow-1, endColumn-1);
problems.add(new CodeProblem(CodeProblem.Severity.MEDIUM, new BlobTarget(blobPath.get(), location), message));
problems.add(new CodeProblem(CodeProblem.Severity.MEDIUM, new BlobTarget(blobPath.get(), location), escapeHtml5(message)));
}
}
return problems;

View File

@ -9,9 +9,9 @@ import io.onedev.server.annotation.Interpolative;
import io.onedev.server.annotation.Patterns;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.buildspec.step.StepGroup;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.codequality.CodeProblem;
import io.onedev.server.codequality.CodeProblem.Severity;
import io.onedev.server.codequality.BlobTarget;
import io.onedev.server.model.Build;
import io.onedev.server.plugin.report.problem.PublishProblemReportStep;
import io.onedev.server.util.XmlUtils;
@ -20,7 +20,6 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.unbescape.html.HtmlEscape;
import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
@ -31,6 +30,8 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.unbescape.html.HtmlEscape.escapeHtml5;
@Editable(order=10000, group=StepGroup.PUBLISH, name="SpotBugs Report")
public class PublishSpotBugsReportStep extends PublishProblemReportStep {
@ -92,7 +93,7 @@ public class PublishSpotBugsReportStep extends PublishProblemReportStep {
if (StringUtils.isBlank(message))
message = bugElement.elementText("ShortMessage");
message = type + ": " + HtmlEscape.escapeHtml5(message);
message = type + ": " + message;
PlanarRange location = getLocation(bugElement, true);
@ -105,7 +106,7 @@ public class PublishSpotBugsReportStep extends PublishProblemReportStep {
if (location == null)
location = new PlanarRange(0, -1, 0, -1);
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), message));
problems.add(new CodeProblem(severity, new BlobTarget(blobPath, location), escapeHtml5(message)));
} else {
logger.warning("Unable to find blob path for file: " + filePath);
}

View File

@ -85,6 +85,11 @@
<artifactId>server-plugin-buildspec-golang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-buildspec-rust</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-buildspec-node</artifactId>
@ -160,6 +165,11 @@
<artifactId>server-plugin-report-cppcheck</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-clippy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.onedev</groupId>
<artifactId>server-plugin-report-pmd</artifactId>