diff --git a/src/main/java/org/eolang/lints/LtRedundantRho.java b/src/main/java/org/eolang/lints/LtRedundantRho.java new file mode 100644 index 000000000..966b39045 --- /dev/null +++ b/src/main/java/org/eolang/lints/LtRedundantRho.java @@ -0,0 +1,93 @@ +/* + * 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; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.cactoos.io.ResourceOf; +import org.cactoos.text.TextOf; +import org.cactoos.text.UncheckedText; +import org.eolang.parser.OnDefault; + +/** + * Lint that warns if a redundant {@code ^} is used. + * @since 0.0.59 + */ +final class LtRedundantRho implements Lint { + @Override + public Collection defects(final XML xmir) throws IOException { + final Collection defects = new ArrayList<>(0); + final Xnav xml = new Xnav(xmir.inner()); + final List objs = xml + .path("//o[starts-with(@base,'ξ.ρ')]") + .collect(Collectors.toList()); + for (final Xnav obj : objs) { + final String name = obj.attribute("name").text().orElse(""); + final List matches = xml + .path(String.format("//o[@name='%s']", name)) + .collect(Collectors.toList()); + final List ancestors = obj + .path(String.format("ancestor::o[@name='%s']", name)) + .collect(Collectors.toList()); + if (matches.size() == 1) { + defects.add( + new Defect.Default( + this.name(), + Severity.WARNING, + new OnDefault(xmir).get(), + Integer.parseInt(obj.attribute("line").text().orElse("0")), + String.format( + "Redundant 'ξ.ρ' notation: '%s' can be accessed without it", + name + ) + ) + ); + } else if (!ancestors.isEmpty() && matches.size() > 1) { + final Xnav target = ancestors.get(0); + for (final Xnav match : matches) { + if (match.equals(target)) { + defects.add( + new Defect.Default( + this.name(), + Severity.WARNING, + new OnDefault(xmir).get(), + Integer.parseInt(obj.attribute("line").text().orElse("0")), + String.format( + "Redundant 'ξ.ρ' notation: '%s' resolves to the same object without it", + name + ) + ) + ); + break; + } + } + } + } + return defects; + } + + @Override + public String motive() throws IOException { + return new UncheckedText( + new TextOf( + new ResourceOf( + String.format( + "org/eolang/motives/errors/%s.md", this.name() + ) + ) + ) + ).asString(); + } + + @Override + public String name() { + return "redundant-hat"; + } +} diff --git a/src/test/java/org/eolang/lints/LtRedundantRhoTest.java b/src/test/java/org/eolang/lints/LtRedundantRhoTest.java new file mode 100644 index 000000000..c2e614fe0 --- /dev/null +++ b/src/test/java/org/eolang/lints/LtRedundantRhoTest.java @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2025 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints; + +import com.jcabi.xml.XMLDocument; +import java.io.IOException; +import org.eolang.parser.EoSyntax; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +/** + * Test for {@link LtRedundantRho}. + * + * @since 0.0.1 + */ +final class LtRedundantRhoTest { + + @Test + void reportsNoDefectsForNecessaryHat() throws Exception { + MatcherAssert.assertThat( + "Should not report a defect when '^' is necessary to resolve ambiguity", + new LtRedundantRho().defects( + new EoSyntax("[] > foo\n+test > @\n^foo > bar").parsed() + ), + Matchers.allOf( + Matchers.iterableWithSize(Matchers.equalTo(0)) + ) + ); + } + + @Test + void namesStableId() { + MatcherAssert.assertThat( + "Rule id must be 'redundant-hat'", + new LtRedundantRho().name(), + Matchers.equalTo("redundant-hat") + ); + } + + @Test + void doesNotReportDefectWhenCaretIsNecessary() throws IOException { + MatcherAssert.assertThat( + "Should not report a defect when '^' is necessary due to ambiguity", + new LtRedundantRho().defects( + new EoSyntax("[] > foo\n+test > @\n^foo > bar").parsed() + ), + Matchers.empty() + ); + } + + @Test + void reportsDefectWhenCaretIsRedundantWithMultipleMatches() throws IOException { + MatcherAssert.assertThat( + "Should report a defect when '^' is redundant among multiple matches", + new LtRedundantRho().defects( + new XMLDocument("") + ), + Matchers.hasSize(1) + ); + } + +}