Skip to content

Commit

Permalink
#3592: LintMojo
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor256 committed Dec 3, 2024
1 parent 30cbae2 commit a9cfaad
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 209 deletions.
2 changes: 1 addition & 1 deletion eo-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ SOFTWARE.
<dependency>
<groupId>org.eolang</groupId>
<artifactId>lints</artifactId>
<version>0.0.13</version>
<version>0.0.15</version>
</dependency>
<dependency>
<groupId>com.yegor256</groupId>
Expand Down
4 changes: 2 additions & 2 deletions eo-maven-plugin/src/it/duplicate_classes/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ SOFTWARE.
<goals>
<goal>register</goal>
<goal>assemble</goal>
<goal>verify</goal>
<goal>lint</goal>
<goal>transpile</goal>
<goal>copy</goal>
<goal>unplace</goal>
Expand All @@ -87,7 +87,7 @@ SOFTWARE.
<goals>
<goal>register</goal>
<goal>assemble</goal>
<goal>verify</goal>
<goal>lint</goal>
<goal>transpile</goal>
<goal>binarize</goal>
</goals>
Expand Down
2 changes: 1 addition & 1 deletion eo-maven-plugin/src/it/simple/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ SOFTWARE.
<goals>
<goal>register</goal>
<goal>assemble</goal>
<goal>verify</goal>
<goal>lint</goal>
<goal>transpile</goal>
</goals>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public Collection<Buildable> exec(final Collection<ForeignTojo> tojos) throws IO
final Collection<Buildable> res = new ArrayList<>(0);
new File(this.targetDir.toPath().resolve("Lib/").toString()).mkdirs();
for (final ForeignTojo tojo : tojos) {
final Path file = tojo.verified();
final Path file = tojo.optimized();
for (final FFINode ffi: this.getFFIs(new XMLDocument(file))) {
ffi.generateUnchecked();
if (ffi instanceof Buildable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
import com.jcabi.log.Logger;
import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
Expand All @@ -38,30 +40,37 @@
import org.cactoos.iterable.Mapped;
import org.cactoos.list.ListOf;
import org.cactoos.number.SumOf;
import org.eolang.lints.Defect;
import org.eolang.lints.Program;
import org.eolang.lints.Severity;
import org.eolang.maven.footprint.FpDefault;
import org.eolang.maven.tojos.ForeignTojo;
import org.eolang.maven.tojos.TojoHash;
import org.w3c.dom.Node;
import org.xembly.Directives;
import org.xembly.Xembler;

/**
* Mojo that checks errors and warnings after "assemble" phase.
* Mojo that runs all lints and checks errors and warnings,
* preferably after the {@code assemble} goal.
*
* @since 0.31.0
*/
@Mojo(
name = "verify",
name = "lint",
defaultPhase = LifecyclePhase.PROCESS_SOURCES,
threadSafe = true
)
public final class VerifyMojo extends SafeMojo {
public final class LintMojo extends SafeMojo {
/**
* The directory where to transpile to.
*/
public static final String DIR = "6-verify";
public static final String DIR = "6-lint";

/**
* Subdirectory for optimized cache.
*/
static final String CACHE = "verified";
static final String CACHE = "linted";

/**
* Whether we should fail on warning.
Expand All @@ -80,37 +89,37 @@ public final class VerifyMojo extends SafeMojo {
void exec() {
final Collection<ForeignTojo> tojos = this.scopedTojos().withShaken();
final ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
counts.putIfAbsent("critical", 0);
counts.putIfAbsent("error", 0);
counts.putIfAbsent("warning", 0);
counts.putIfAbsent(Severity.CRITICAL.name(), 0);
counts.putIfAbsent(Severity.ERROR.name(), 0);
counts.putIfAbsent(Severity.WARNING.name(), 0);
final Collection<ForeignTojo> must = new ListOf<>(
new Filtered<>(
ForeignTojo::notVerified,
ForeignTojo::notLinted,
tojos
)
);
final int passed = new SumOf(
new Threads<>(
Runtime.getRuntime().availableProcessors(),
new Mapped<>(
tojo -> () -> this.verify(tojo, counts),
tojo -> () -> this.lintOne(tojo, counts),
must
)
)
).intValue();
if (must.isEmpty()) {
Logger.info(this, "No XMIR programs out of %d verified", tojos.size());
Logger.info(this, "No XMIR programs out of %d linted", tojos.size());
} else if (tojos.isEmpty()) {
Logger.info(this, "There are no XMIR programs, nothing to verify");
Logger.info(this, "There are no XMIR programs, nothing to lint");
} else {
final String sum = VerifyMojo.summary(counts);
final String sum = LintMojo.summary(counts);
Logger.info(
this,
"Verified %d out of %d XMIR program(s) that needed verification (out of %d total programs): %s",
"Linted %d out of %d XMIR program(s) that needed this (out of %d total programs): %s",
passed, must.size(), tojos.size(), sum
);
if (counts.get("error") > 0 || counts.get("critical") > 0
|| counts.get("warning") > 0 && this.failOnWarning) {
if (counts.get(Severity.ERROR.name()) > 0 || counts.get(Severity.CRITICAL.name()) > 0
|| counts.get(Severity.WARNING.name()) > 0 && this.failOnWarning) {
throw new IllegalStateException(
String.format("In %d XMIR files, we found %s", must.size(), sum)
);
Expand All @@ -123,33 +132,28 @@ void exec() {
* @param tojo Foreign tojo
* @param counts Counts of errors, warnings, and critical
* @return Amount of passed tojos (1 if passed, 0 if errors)
* @throws Exception If failed to verify
* @throws Exception If failed to lint
*/
private int verify(final ForeignTojo tojo,
private int lintOne(final ForeignTojo tojo,
final ConcurrentHashMap<String, Integer> counts) throws Exception {
final Path source = tojo.shaken();
final XML xmir = new XMLDocument(source);
final String name = xmir.xpath("/program/@name").get(0);
final Path base = this.targetDir.toPath().resolve(VerifyMojo.DIR);
final Path base = this.targetDir.toPath().resolve(LintMojo.DIR);
final Path target = new Place(name).make(base, AssembleMojo.XMIR);
int verified = 1;
try {
tojo.withVerified(
tojo.withLinted(
new FpDefault(
src -> {
if (!this.good(xmir, counts)) {
throw new SkipException();
}
return xmir.toString();
},
this.cache.toPath().resolve(VerifyMojo.CACHE),
src -> this.counted(LintMojo.lint(xmir), counts).toString(),
this.cache.toPath().resolve(LintMojo.CACHE),
this.plugin.getVersion(),
new TojoHash(tojo),
base.relativize(target)
).apply(source, target)
);
} catch (final SkipException ex) {
Logger.debug(this, "Failed to verify %[file]s", source);
Logger.debug(this, "Failed to lint %[file]s", source);
verified = 0;
}
return verified;
Expand All @@ -161,8 +165,7 @@ private int verify(final ForeignTojo tojo,
* @param counts Counts of errors, warnings, and critical
* @return TRUE if it's good
*/
private boolean good(final XML xml, final ConcurrentHashMap<String, Integer> counts) {
boolean good = true;
private XML counted(final XML xml, final ConcurrentHashMap<String, Integer> counts) {
for (final XML error : xml.nodes("/program/errors/error")) {
final List<String> line = error.xpath("@line");
final StringBuilder message = new StringBuilder()
Expand All @@ -174,28 +177,26 @@ private boolean good(final XML xml, final ConcurrentHashMap<String, Integer> cou
.append(error.xpath("text()").get(0))
.append(" (")
.append(error.xpath("@check").get(0))
.append(' ')
.append(error.xpath("@severity").get(0))
.append(')');
final String severity = error.xpath("@severity").get(0);
counts.compute(severity, (sev, before) -> before + 1);
switch (severity) {
case "warning":
case Severity.WARNING.name():
Logger.warn(this, message.toString());
if (this.failOnWarning) {
good = false;
}
break;
case "error":
case "critical":
case Severity.ERROR.name():
case Severity.CRITICAL.name():
Logger.error(this, message.toString());
good = false;
break;
default:
throw new IllegalArgumentException(
String.format("Incorrect severity: %s", severity)
);
}
}
return good;
return xml;
}

/**
Expand All @@ -205,14 +206,14 @@ private boolean good(final XML xml, final ConcurrentHashMap<String, Integer> cou
*/
private static String summary(final ConcurrentHashMap<String, Integer> counts) {
final StringBuilder sum = new StringBuilder(100);
final int criticals = counts.get("critical");
final int criticals = counts.get(Severity.CRITICAL.name());
if (criticals > 0) {
sum.append(criticals).append(" critical error");
if (criticals > 1) {
sum.append('s');
}
}
final int errors = counts.get("error");
final int errors = counts.get(Severity.ERROR.name());
if (errors > 0) {
if (sum.length() > 0) {
sum.append(", ");
Expand All @@ -222,7 +223,7 @@ private static String summary(final ConcurrentHashMap<String, Integer> counts) {
sum.append('s');
}
}
final int warnings = counts.get("warning");
final int warnings = counts.get(Severity.WARNING.name());
if (warnings > 0) {
if (sum.length() > 0) {
sum.append(", ");
Expand All @@ -238,11 +239,57 @@ private static String summary(final ConcurrentHashMap<String, Integer> counts) {
return sum.toString();
}

/**
* Find all possible linting defects.
* @param xmir The XML before linting
* @return XML after linting
* @throws IOException If fails
*/
private static XML lint(final XML xmir) throws IOException {
final Directives dirs = new Directives();
final Collection<Defect> defects = new Program(xmir).defects();
if (!defects.isEmpty()) {
dirs.xpath("/program").addIf("errors").strict(1);
for (final Defect defect : defects) {
if (LintMojo.suppressed(xmir, defect)) {
continue;
}
dirs.add("error")
.attr("check", defect.rule())
.attr("severity", defect.severity().toString().toLowerCase(Locale.ENGLISH))
.set(defect.text());
if (defect.line() > 0) {
dirs.attr("line", defect.line());
}
dirs.up();
}
}
final Node node = xmir.node();
new Xembler(dirs).applyQuietly(node);
return new XMLDocument(node);
}

/**
* This defect is suppressed?
* @param xmir The XMIR
* @param defect The defect
* @return TRUE if suppressed
*/
private static boolean suppressed(final XML xmir, final Defect defect) {
return !xmir.nodes(
String.format(
"/program/metas/meta[head='unlint' and tail='%s']",
defect.rule()
)
).isEmpty();
}

/**
* If this file must be skipped.
*
* @since 0.43.0
*/
public static class SkipException extends RuntimeException {
}

}
Loading

0 comments on commit a9cfaad

Please sign in to comment.