diff --git a/src/main/java/org/eolang/lints/Defect.java b/src/main/java/org/eolang/lints/Defect.java index 554bf7bdb..86c2c0b52 100644 --- a/src/main/java/org/eolang/lints/Defect.java +++ b/src/main/java/org/eolang/lints/Defect.java @@ -6,6 +6,7 @@ import com.jcabi.manifests.Manifests; import java.util.Objects; +import org.eolang.lints.fix.Fix; /** * A single defect found. @@ -102,6 +103,13 @@ public interface Defect { */ boolean experimental(); + /** + * Information on how to fix this defect. + * + * @return Fix info. + */ + Fix fix(); + /** * Default implementation of {@link Defect}. *

@@ -142,6 +150,11 @@ final class Default implements Defect { */ private final boolean experiment; + /** + * How to fix. + */ + private final Fix fix; + /** * Ctor. * @param rule Rule name @@ -155,7 +168,7 @@ public Default( final String rule, final Severity severity, final String object, final int line, final String text ) { - this(rule, severity, object, line, text, false); + this(rule, severity, object, line, text, false, new Fix()); } /** @@ -175,7 +188,7 @@ public Default( public Default( final String rule, final Severity severity, final String object, final int line, final String text, - final boolean exprmnt + final boolean exprmnt, final Fix fix ) { this.rle = rule; this.sev = severity; @@ -183,6 +196,7 @@ public Default( this.lineno = line; this.txt = text; this.experiment = exprmnt; + this.fix = fix; } @Override @@ -237,6 +251,11 @@ public boolean experimental() { return this.experiment; } + @Override + public Fix fix() { + return this.fix; + } + @Override public boolean equals(final Object obj) { final boolean result; diff --git a/src/main/java/org/eolang/lints/DfContext.java b/src/main/java/org/eolang/lints/DfContext.java index 2036b72f5..23ca32b0b 100644 --- a/src/main/java/org/eolang/lints/DfContext.java +++ b/src/main/java/org/eolang/lints/DfContext.java @@ -4,6 +4,8 @@ */ package org.eolang.lints; +import org.eolang.lints.fix.Fix; + /** * Defect with context. * @since 0.0.40 @@ -60,6 +62,11 @@ public String version() { return this.origin.version(); } + @Override + public Fix fix() { + return this.origin.fix(); + } + @Override public String context() { return this.ctxt.replace("<", "<") diff --git a/src/main/java/org/eolang/lints/LtByXsl.java b/src/main/java/org/eolang/lints/LtByXsl.java index bd06eacf8..832cab7da 100644 --- a/src/main/java/org/eolang/lints/LtByXsl.java +++ b/src/main/java/org/eolang/lints/LtByXsl.java @@ -20,6 +20,9 @@ import org.cactoos.io.ResourceOf; import org.cactoos.text.IoCheckedText; import org.cactoos.text.TextOf; +import org.eolang.lints.fix.Edit; +import org.eolang.lints.fix.File; +import org.eolang.lints.fix.Fix; import org.eolang.parser.ObjectName; /** @@ -98,6 +101,7 @@ public Collection defects(final XML xmir) { String.format("No severity reported by %s", this.rule) ); } + final Optional message = xml.element("message").text(); defects.add( new DfContext( new Defect.Default( @@ -105,8 +109,18 @@ public Collection defects(final XML xmir) { Severity.parsed(sever.get()), new ObjectName(xmir).get(), this.lineno(xml), - xml.text().get(), - LtByXsl.experimental(xml) + message.orElseGet(() -> xml.text().get()), + LtByXsl.experimental(xml), + message.map( + ignored -> + new Fix( + new File( + xmir.xpath("/@source/text()").get(0), + new Edit(xml.element("edit")) + ) + ) + ) + .orElseGet(Fix::new) ), xml.attribute("context").text().orElse("") ) diff --git a/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java b/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java index 5386565df..5edd781a5 100644 --- a/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java +++ b/src/main/java/org/eolang/lints/LtUnlintNonExistingDefect.java @@ -19,6 +19,10 @@ import org.cactoos.list.ListOf; import org.cactoos.text.IoCheckedText; import org.cactoos.text.TextOf; +import org.eolang.lints.fix.Edit; +import org.eolang.lints.fix.File; +import org.eolang.lints.fix.Fix; +import org.eolang.lints.fix.Position; import org.eolang.parser.ObjectName; /** @@ -82,7 +86,7 @@ public Collection defects(final XML xmir) throws IOException { ) ) .map(xnav -> xnav.text().get()) - .collect(Collectors.toList()) + .map(Integer::parseInt) .forEach( line -> defects.add( @@ -90,10 +94,21 @@ public Collection defects(final XML xmir) throws IOException { this.name(), Severity.WARNING, new ObjectName(xmir).get(), - Integer.parseInt(line), + line, String.format( "Unlinting rule '%s' doesn't make sense, since there are no defects with it", unlint + ), + false, + new Fix( + new File( + new ObjectSource(xmir).get(), + new Edit( + new Position(line, 0), + new Position(line + 1, 0), + "" + ) + ) ) ) ) diff --git a/src/main/java/org/eolang/lints/ObjectSource.java b/src/main/java/org/eolang/lints/ObjectSource.java new file mode 100644 index 000000000..4c2ae934f --- /dev/null +++ b/src/main/java/org/eolang/lints/ObjectSource.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints; + +import com.github.lombrozo.xnav.Xnav; +import com.jcabi.xml.XML; + +final class ObjectSource { + private final Xnav xnav; + + ObjectSource(XML xml) { + this.xnav = new Xnav(xml.inner()); + } + + String get() { + return xnav.attribute("source").text().orElse(""); + } +} diff --git a/src/main/java/org/eolang/lints/ScopedDefects.java b/src/main/java/org/eolang/lints/ScopedDefects.java index 2fe86a40b..c3d650f31 100644 --- a/src/main/java/org/eolang/lints/ScopedDefects.java +++ b/src/main/java/org/eolang/lints/ScopedDefects.java @@ -31,7 +31,8 @@ final class ScopedDefects extends CollectionEnvelope { defect.object(), defect.line(), defect.text(), - defect.experimental() + defect.experimental(), + defect.fix() ), defect.context() ), diff --git a/src/main/java/org/eolang/lints/fix/Create.java b/src/main/java/org/eolang/lints/fix/Create.java new file mode 100644 index 000000000..9b41a7f54 --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Create.java @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; + +public class Create { + private final String path; + + private final boolean overwrite; + + private final boolean ignoreIfExists; + + public Create(String path, boolean overwrite, boolean ignoreIfExists) { + this.path = path; + this.overwrite = overwrite; + this.ignoreIfExists = ignoreIfExists; + } + + public Create(Xnav xnav) { + this( + xnav.element("path").text().get(), + Boolean.parseBoolean(xnav.element("overwrite").text().orElse("")), + Boolean.parseBoolean(xnav.element("ignoreIfExists").text().orElse("")) + ); + } + + public String path() { + return this.path; + } + + public boolean overwrite() { + return this.overwrite; + } + + public boolean ignoreIfExists() { + return this.ignoreIfExists; + } +} diff --git a/src/main/java/org/eolang/lints/fix/Delete.java b/src/main/java/org/eolang/lints/fix/Delete.java new file mode 100644 index 000000000..e3e100056 --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Delete.java @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; + +public class Delete { + private final String path; + + private final boolean recursive; + + private final boolean ignoreIfNotExists; + + public Delete(String path, boolean recursive, boolean ignoreIfNotExists) { + this.path = path; + this.recursive = recursive; + this.ignoreIfNotExists = ignoreIfNotExists; + } + + public Delete(final Xnav xnav) { + this( + xnav.element("path").text().get(), + Boolean.parseBoolean(xnav.element("recursive").text().orElse("")), + Boolean.parseBoolean(xnav.element("ignoreIfNotExists").text().orElse("")) + ); + } + + public String path() { + return this.path; + } + + public boolean recursive() { + return this.recursive; + } + + public boolean ignoreIfNotExists() { + return this.ignoreIfNotExists; + } +} diff --git a/src/main/java/org/eolang/lints/fix/Edit.java b/src/main/java/org/eolang/lints/fix/Edit.java new file mode 100644 index 000000000..d0c2998cb --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Edit.java @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; + +public class Edit { + private final Position start; + + private final Position end; + + private final String newText; + + public Edit(final Position start, final Position end, final String newText) { + this.start = start; + this.end = end; + this.newText = newText; + } + + public Edit(Xnav xnav) { + this( + new Position(xnav.element("start")), + new Position(xnav.element("end")), + xnav.element("newText").text().get() + ); + } + + public Position start() { + return this.start; + } + + public Position end() { + return this.end; + } + + public String newText() { + return this.newText; + } +} diff --git a/src/main/java/org/eolang/lints/fix/File.java b/src/main/java/org/eolang/lints/fix/File.java new file mode 100644 index 000000000..eb1ccbaee --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/File.java @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; +import java.util.Arrays; +import java.util.Collection; + +public class File { + private final String path; + private final Collection edits; + + public File(final String path, final Edit... edits) { + this.path = path; + this.edits = Arrays.asList(edits); + } + + public File(final Xnav xnav) { + this( + xnav.attribute("path").text().get(), + xnav.path("edit").map(Edit::new).toArray(Edit[]::new) + ); + } + + public String path() { + return this.path; + } + + public Collection edits() { + return this.edits; + } +} diff --git a/src/main/java/org/eolang/lints/fix/Fix.java b/src/main/java/org/eolang/lints/fix/Fix.java new file mode 100644 index 000000000..eaaa9446f --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Fix.java @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class Fix { + private final Collection files; + + private final Collection creates; + + private final Collection deletes; + + private final Collection renames; + + public Fix(Collection files, Collection creates, Collection deletes, Collection renames) { + this.files = files; + this.creates = creates; + this.deletes = deletes; + this.renames = renames; + } + + public Fix() { + this( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + public Fix(File file) { + this( + Collections.singletonList(file), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + public Fix(Xnav fix) { + this( + fix.path("file").map(File::new).collect(Collectors.toList()), + fix.path("create").map(Create::new).collect(Collectors.toList()), + fix.path("delete").map(Delete::new).collect(Collectors.toList()), + fix.path("rename").map(Rename::new).collect(Collectors.toList()) + ); + } + + public Collection files() { + return this.files; + } + + public Collection creates() { + return this.creates; + } + + public Collection deletes() { + return this.deletes; + } + + public Collection renames() { + return this.renames; + } +} diff --git a/src/main/java/org/eolang/lints/fix/Position.java b/src/main/java/org/eolang/lints/fix/Position.java new file mode 100644 index 000000000..8bc4d1bc8 --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Position.java @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; + +public class Position { + private final int line; + + private final int character; + + public Position(final int line, final int character) { + this.line = line; + this.character = character; + } + + public Position(Xnav xnav) { + this( + Integer.parseInt(xnav.attribute("line").text().get()), + Integer.parseInt(xnav.attribute("character").text().get()) + ); + } + + public int line() { + return this.line; + } + + public int character() { + return this.character; + } +} diff --git a/src/main/java/org/eolang/lints/fix/Rename.java b/src/main/java/org/eolang/lints/fix/Rename.java new file mode 100644 index 000000000..7f70ef087 --- /dev/null +++ b/src/main/java/org/eolang/lints/fix/Rename.java @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints.fix; + +import com.github.lombrozo.xnav.Xnav; + +public class Rename { + private final String oldPath; + private final String newPath; + private final boolean overwrite; + private final boolean ignoreIfExists; + + public Rename(String oldPath, String newPath, boolean overwrite, boolean ignoreIfExists) { + this.oldPath = oldPath; + this.newPath = newPath; + this.overwrite = overwrite; + this.ignoreIfExists = ignoreIfExists; + } + + public Rename(Xnav xnav) { + this( + xnav.element("oldPath").text().get(), + xnav.element("newPath").text().get(), + Boolean.parseBoolean(xnav.element("overwrite").text().orElse("")), + Boolean.parseBoolean(xnav.element("ignoreIfExists").text().orElse("")) + ); + } + + public String oldPath() { + return this.oldPath; + } + + public String newPath() { + return this.newPath; + } + + public boolean overwrite() { + return this.overwrite; + } + + public boolean ignoreIfExists() { + return this.ignoreIfExists; + } +} diff --git a/src/main/resources/org/eolang/lints/aliases/empty-alias.xsl b/src/main/resources/org/eolang/lints/aliases/empty-alias.xsl index 47edd70cb..3e2c2a776 100644 --- a/src/main/resources/org/eolang/lints/aliases/empty-alias.xsl +++ b/src/main/resources/org/eolang/lints/aliases/empty-alias.xsl @@ -10,7 +10,7 @@ - + @@ -23,8 +23,19 @@ error - The alias doesn't have a tail - + + The alias doesn't have a tail + + + + + + + + + + + diff --git a/src/test/java/matchers/DefectsMatcher.java b/src/test/java/matchers/DefectsMatcher.java index 33bd9316a..6e1a9267c 100644 --- a/src/test/java/matchers/DefectsMatcher.java +++ b/src/test/java/matchers/DefectsMatcher.java @@ -30,13 +30,17 @@ public final class DefectsMatcher extends BaseMatcher { public boolean matches(final Object xml) { final Collection defects = new ArrayList<>(0); for (final XML defect : ((XML) xml).nodes("/defects/defect")) { + final String text = defect.xpath("text()") + .stream() + .findFirst() + .orElseGet(() -> defect.xpath("message/text()").get(0)); defects.add( new Defect.Default( "unknown", Severity.parsed(defect.xpath("@severity").get(0)), "unknown", Integer.parseInt(defect.xpath("@line").get(0)), - defect.xpath("text()").get(0) + text ) ); } diff --git a/src/test/java/org/eolang/lints/DefectTest.java b/src/test/java/org/eolang/lints/DefectTest.java index a1602d6e2..17e5fccc4 100644 --- a/src/test/java/org/eolang/lints/DefectTest.java +++ b/src/test/java/org/eolang/lints/DefectTest.java @@ -4,6 +4,7 @@ */ package org.eolang.lints; +import org.eolang.lints.fix.Fix; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -72,7 +73,8 @@ void returnsExperimental() { "f.12", 42, "This is wrong", - true + true, + new Fix() ).experimental(), Matchers.equalTo(true) );