From ef423dd678c125001f37784b3f867efbda95e4cf Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 22:15:19 +0300 Subject: [PATCH 01/10] #797: add +syntax in meta --- .../org/eolang/lints/metas/unique-metas.xsl | 2 +- .../org/eolang/lints/metas/unknown-metas.xsl | 2 +- .../motives/metas/syntax-version-mismatch.md | 24 +++++++++++++++++++ .../org/eolang/motives/metas/unknown-metas.md | 2 ++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/org/eolang/motives/metas/syntax-version-mismatch.md diff --git a/src/main/resources/org/eolang/lints/metas/unique-metas.xsl b/src/main/resources/org/eolang/lints/metas/unique-metas.xsl index 4d2e64553..26cf6218a 100644 --- a/src/main/resources/org/eolang/lints/metas/unique-metas.xsl +++ b/src/main/resources/org/eolang/lints/metas/unique-metas.xsl @@ -7,7 +7,7 @@ - + diff --git a/src/main/resources/org/eolang/lints/metas/unknown-metas.xsl b/src/main/resources/org/eolang/lints/metas/unknown-metas.xsl index 7b8bb02e0..0cf285ac9 100644 --- a/src/main/resources/org/eolang/lints/metas/unknown-metas.xsl +++ b/src/main/resources/org/eolang/lints/metas/unknown-metas.xsl @@ -12,7 +12,7 @@ - + diff --git a/src/main/resources/org/eolang/motives/metas/syntax-version-mismatch.md b/src/main/resources/org/eolang/motives/metas/syntax-version-mismatch.md new file mode 100644 index 000000000..c0e7b9a8c --- /dev/null +++ b/src/main/resources/org/eolang/motives/metas/syntax-version-mismatch.md @@ -0,0 +1,24 @@ +# Syntax version mismatch + +The `+syntax` meta specifies the version of the EO language that +the source code was written for. If the parser version is older +than the version specified in `+syntax`, the code may use features +that are not supported by the parser, leading to compilation errors. + +The parser will refuse to process such files. + +Incorrect (if parser version is 0.58.0): + +```eo ++syntax 0.59.0 + +[] > foo +``` + +Correct (if parser version is 0.59.0 or newer): + +```eo ++syntax 0.59.0 + +[] > foo +``` diff --git a/src/main/resources/org/eolang/motives/metas/unknown-metas.md b/src/main/resources/org/eolang/motives/metas/unknown-metas.md index 0ee1a4d3d..ef58e2a9b 100644 --- a/src/main/resources/org/eolang/motives/metas/unknown-metas.md +++ b/src/main/resources/org/eolang/motives/metas/unknown-metas.md @@ -10,6 +10,7 @@ The following metas are supported: * `+home` * `+unlint` * `+probe` +* `+syntax` Incorrect: @@ -29,6 +30,7 @@ Correct: +architect yegor256@gmail.com +rt jvm +home https://earth.com ++syntax 0.59.0 +unlint unsorted-metas [] > foo From f518d309e35ab6e99bf822034a1f76c32929c993 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 22:15:54 +0300 Subject: [PATCH 02/10] #797: add checking parser version --- .../org/eolang/lints/LtSyntaxVersion.java | 151 +++++++++++++++++ .../java/org/eolang/lints/MonoLints copy.java | 66 ++++++++ .../org/eolang/lints/LtSyntaxVersionTest.java | 158 ++++++++++++++++++ 3 files changed, 375 insertions(+) create mode 100644 src/main/java/org/eolang/lints/LtSyntaxVersion.java create mode 100644 src/main/java/org/eolang/lints/MonoLints copy.java create mode 100644 src/test/java/org/eolang/lints/LtSyntaxVersionTest.java diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java new file mode 100644 index 000000000..e0f367074 --- /dev/null +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2026 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.IoCheckedText; +import org.cactoos.text.TextOf; +import org.eolang.parser.OnDefault; + +/** + * Checks that the version specified in the +syntax meta is not + * newer than the current parser version. + * + *

If the +syntax meta specifies a version higher than the parser's + * version, it means the code requires a newer parser, and this + * lint reports an error.

+ * + * @since 0.1.0 + */ +final class LtSyntaxVersion implements Lint { + + /** + * The parser version. + */ + private final String parserVersion; + + /** + * Default ctor. + */ + LtSyntaxVersion() { + this.parserVersion = "0.0.0"; + } + + /** + * Ctor. + * @param parserVersion The parser version. + */ + LtSyntaxVersion(final String parserVersion) { + this.parserVersion = parserVersion; + } + + @Override + public Collection defects(final XML xmir) throws IOException { + final Collection defects = new ArrayList<>(0); + final Xnav xml = new Xnav(xmir.inner()); + final List metas = xml.path("/object/metas/meta[head='syntax']") + .collect(Collectors.toList()); + for (final Xnav meta : metas) { + final String tail = meta.element("tail").text().orElse(""); + final String line = meta.attribute("line").text().orElse("0"); + if (!LtSyntaxVersion.valid(tail)) { + defects.add( + new Defect.Default( + this.name(), + Severity.ERROR, + new OnDefault(xmir).get(), + Integer.parseInt(line), + String.format( + "The format of the +syntax meta is wrong: %s (SemVer expected instead)", + tail + ) + ) + ); + continue; + } + if (this.compareVersions(tail) < 0) { + defects.add( + new Defect.Default( + this.name(), + Severity.ERROR, + new OnDefault(xmir).get(), + Integer.parseInt(line), + String.format( + "The +syntax meta requires version %s, but the current parser version is %s (older)", + tail, + this.parserVersion + ) + ) + ); + } + } + return defects; + } + + @Override + public String name() { + return "syntax-version-mismatch"; + } + + @Override + public String motive() throws IOException { + return new IoCheckedText( + new TextOf( + new ResourceOf( + "org/eolang/motives/metas/syntax-version-mismatch.md" + ) + ) + ).asString(); + } + + /** + * Check if the version string is a valid SemVer. + * @param version The version to validate. + * @return True if valid. + */ + private static boolean valid(final String version) { + return version.matches("^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9-]+)?$"); + } + + /** + * Compare parser version to the given syntax version. + * @param syntaxVersion The version from +syntax meta. + * @return -1 if parser is older, 0 if equal, 1 if parser is newer. + */ + private int compareVersions(final String syntaxVersion) { + final int[] syntaxVer = LtSyntaxVersion.parts(syntaxVersion); + final int[] parserVer = LtSyntaxVersion.parts(this.parserVersion); + for (int i = 0; i < syntaxVer.length; i++) { + if (parserVer[i] < syntaxVer[i]) { + return -1; + } + if (parserVer[i] > syntaxVer[i]) { + return 1; + } + } + return 0; + } + + /** + * Parse the numeric parts of a SemVer string. + * @param version The version string. + * @return Array of [major, minor, patch]. + */ + private static int[] parts(final String version) { + final String[] segments = version.split("-", 2)[0].split("\\."); + return new int[] { + segments.length > 0 ? Integer.parseInt(segments[0]) : 0, + segments.length > 1 ? Integer.parseInt(segments[1]) : 0, + segments.length > 2 ? Integer.parseInt(segments[2]) : 0 + }; + } +} diff --git a/src/main/java/org/eolang/lints/MonoLints copy.java b/src/main/java/org/eolang/lints/MonoLints copy.java new file mode 100644 index 000000000..d3c6a6908 --- /dev/null +++ b/src/main/java/org/eolang/lints/MonoLints copy.java @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints; + +import com.jcabi.xml.XML; +import java.util.List; +import java.util.stream.Collectors; +import org.cactoos.iterable.IterableEnvelope; +import org.cactoos.iterable.Joined; +import org.cactoos.iterable.Shuffled; +import org.cactoos.list.ListOf; + +/** + * Mono lints. + * Mono lints represent a list of lints for single XMIR scope. This class is required + * in order to provide more fine-grained access and be reused by other classes, including + * {@link PkMono} and {@link PkWpa} via {@link MonoLintNames}, to mutually ignore other + * scope lints in both: {@link LtUnlintNonExistingDefect} and {@link LtUnlintNonExistingDefectWpa} + * without causing recursion errors. + * @since 0.0.43 + */ +final class MonoLints extends IterableEnvelope> { + + /** + * All XML-based lints. + */ + private static final Iterable> LINTS = new Shuffled<>( + new Joined>( + new PkByXsl(), + List.of( + new LtAsciiOnly(), + new LtReservedName(), + new LtSyntaxVersion() + ) + ) + ); + + /** + * Ctor. + */ + MonoLints() { + super( + new Joined>( + MonoLints.LINTS, + List.of( + new LtIncorrectUnlint( + new ListOf<>( + new Joined<>( + MonoLints.LINTS, new WpaLints(), + new ListOf<>( + new LtUnlintNonExistingDefect( + MonoLints.LINTS, new ListOf<>(new WpaLintNames()) + ) + ) + ) + ).stream() + .map(Lint::name) + .collect(Collectors.toList()) + ) + ) + ) + ); + } +} diff --git a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java new file mode 100644 index 000000000..b121c64b1 --- /dev/null +++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com + * SPDX-License-Identifier: MIT + */ +package org.eolang.lints; + +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 LtSyntaxVersion}. + * + * @since 0.1.0 + */ +final class LtSyntaxVersionTest { + + @Test + void catchesMismatchWhenSyntaxIsNewer() throws IOException { + MatcherAssert.assertThat( + "should report error when +syntax version is newer than parser", + new LtSyntaxVersion("0.58.0").defects( + new EoSyntax( + "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.iterableWithSize(1) + ); + } + + @Test + void allowsWhenParserIsNewer() throws IOException { + MatcherAssert.assertThat( + "should not report error when parser version is newer", + new LtSyntaxVersion("0.60.0").defects( + new EoSyntax( + "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.emptyIterable() + ); + } + + @Test + void allowsWhenVersionsMatch() throws IOException { + MatcherAssert.assertThat( + "should not report error when versions match exactly", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.emptyIterable() + ); + } + + @Test + void usesDefaultVersionWhenNoArgCtor() throws IOException { + MatcherAssert.assertThat( + "with default ctor (0.0.0), +syntax 0.0.0 should not report error", + new LtSyntaxVersion().defects( + new EoSyntax( + "+syntax 0.0.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.emptyIterable() + ); + MatcherAssert.assertThat( + "with default ctor (0.0.0), +syntax 0.0.1 should report error", + new LtSyntaxVersion().defects( + new EoSyntax( + "+syntax 0.0.1\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.iterableWithSize(1) + ); + } + + @Test + void ignoresWhenNoSyntaxMeta() throws IOException { + MatcherAssert.assertThat( + "should not report error when no +syntax meta is present", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.emptyIterable() + ); + } + + @Test + void reportsErrorOnInvalidSyntaxFormat() throws IOException { + MatcherAssert.assertThat( + "should report error for invalid +syntax format", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "+syntax alpha\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.iterableWithSize(1) + ); + } + + @Test + void reportsErrorSeverityForInvalidFormat() throws IOException { + MatcherAssert.assertThat( + "invalid format defect should have error severity", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "+syntax alpha\n\n# Foo.\n[] > foo\n" + ).parsed() + ).iterator().next().severity(), + Matchers.equalTo(Severity.ERROR) + ); + } + + @Test + void reportsErrorSeverity() throws IOException { + MatcherAssert.assertThat( + "defect should have error severity", + new LtSyntaxVersion("0.58.0").defects( + new EoSyntax( + "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ).iterator().next().severity(), + Matchers.equalTo(Severity.ERROR) + ); + } + + @Test + void catchesMajorVersionMismatch() throws IOException { + MatcherAssert.assertThat( + "should detect when major version is newer", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "+syntax 1.0.0\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.iterableWithSize(1) + ); + } + + @Test + void catchesPatchVersionMismatch() throws IOException { + MatcherAssert.assertThat( + "should detect when patch version is newer", + new LtSyntaxVersion("0.59.0").defects( + new EoSyntax( + "+syntax 0.59.1\n\n# Foo.\n[] > foo\n" + ).parsed() + ), + Matchers.iterableWithSize(1) + ); + } +} From 11f6230e085b75a6b8e87b6810da561c6141ac17 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 22:29:44 +0300 Subject: [PATCH 03/10] remove copy file --- .../java/org/eolang/lints/MonoLints copy.java | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 src/main/java/org/eolang/lints/MonoLints copy.java diff --git a/src/main/java/org/eolang/lints/MonoLints copy.java b/src/main/java/org/eolang/lints/MonoLints copy.java deleted file mode 100644 index d3c6a6908..000000000 --- a/src/main/java/org/eolang/lints/MonoLints copy.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com - * SPDX-License-Identifier: MIT - */ -package org.eolang.lints; - -import com.jcabi.xml.XML; -import java.util.List; -import java.util.stream.Collectors; -import org.cactoos.iterable.IterableEnvelope; -import org.cactoos.iterable.Joined; -import org.cactoos.iterable.Shuffled; -import org.cactoos.list.ListOf; - -/** - * Mono lints. - * Mono lints represent a list of lints for single XMIR scope. This class is required - * in order to provide more fine-grained access and be reused by other classes, including - * {@link PkMono} and {@link PkWpa} via {@link MonoLintNames}, to mutually ignore other - * scope lints in both: {@link LtUnlintNonExistingDefect} and {@link LtUnlintNonExistingDefectWpa} - * without causing recursion errors. - * @since 0.0.43 - */ -final class MonoLints extends IterableEnvelope> { - - /** - * All XML-based lints. - */ - private static final Iterable> LINTS = new Shuffled<>( - new Joined>( - new PkByXsl(), - List.of( - new LtAsciiOnly(), - new LtReservedName(), - new LtSyntaxVersion() - ) - ) - ); - - /** - * Ctor. - */ - MonoLints() { - super( - new Joined>( - MonoLints.LINTS, - List.of( - new LtIncorrectUnlint( - new ListOf<>( - new Joined<>( - MonoLints.LINTS, new WpaLints(), - new ListOf<>( - new LtUnlintNonExistingDefect( - MonoLints.LINTS, new ListOf<>(new WpaLintNames()) - ) - ) - ) - ).stream() - .map(Lint::name) - .collect(Collectors.toList()) - ) - ) - ) - ); - } -} From 26fb994d25f63084bc6f7bb618a1bb0f92efcbc3 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 22:34:29 +0300 Subject: [PATCH 04/10] fix linter --- src/main/java/org/eolang/lints/LtSyntaxVersion.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java index e0f367074..4503c8dd1 100644 --- a/src/main/java/org/eolang/lints/LtSyntaxVersion.java +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -5,6 +5,7 @@ package org.eolang.lints; import com.github.lombrozo.xnav.Xnav; +import com.google.common.base.Splitter; import com.jcabi.xml.XML; import java.io.IOException; import java.util.ArrayList; @@ -141,11 +142,11 @@ private int compareVersions(final String syntaxVersion) { * @return Array of [major, minor, patch]. */ private static int[] parts(final String version) { - final String[] segments = version.split("-", 2)[0].split("\\."); + final List segments = Splitter.on('.').splitToList(version.split("-", 2)[0]); return new int[] { - segments.length > 0 ? Integer.parseInt(segments[0]) : 0, - segments.length > 1 ? Integer.parseInt(segments[1]) : 0, - segments.length > 2 ? Integer.parseInt(segments[2]) : 0 + segments.size() > 0 ? Integer.parseInt(segments.get(0)) : 0, + segments.size() > 1 ? Integer.parseInt(segments.get(1)) : 0, + segments.size() > 2 ? Integer.parseInt(segments.get(2)) : 0 }; } } From a8fe0958ecc1c3d837ff2238df8521e27cebf903 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 22:57:13 +0300 Subject: [PATCH 05/10] fix parserVersion checking --- .../java/org/eolang/lints/LtSyntaxVersion.java | 11 ++++++++++- .../org/eolang/lints/LtSyntaxVersionTest.java | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java index 4503c8dd1..6a3a30a9d 100644 --- a/src/main/java/org/eolang/lints/LtSyntaxVersion.java +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -43,9 +43,18 @@ final class LtSyntaxVersion implements Lint { /** * Ctor. - * @param parserVersion The parser version. + * @param parserVersion The parser version (must be valid SemVer, e.g. 1.2.3). + * @throws IllegalArgumentException If parserVersion is not valid SemVer. */ LtSyntaxVersion(final String parserVersion) { + if (parserVersion == null || !LtSyntaxVersion.valid(parserVersion)) { + throw new IllegalArgumentException( + String.format( + "parser version must be valid SemVer (e.g. 1.2.3), got: %s", + parserVersion == null ? "null" : String.format("\"%s\"", parserVersion) + ) + ); + } this.parserVersion = parserVersion; } diff --git a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java index b121c64b1..c3404d1e4 100644 --- a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java +++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java @@ -8,6 +8,7 @@ import org.eolang.parser.EoSyntax; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -155,4 +156,20 @@ void catchesPatchVersionMismatch() throws IOException { Matchers.iterableWithSize(1) ); } + + @Test + void rejectsInvalidParserVersion() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> new LtSyntaxVersion("latest") + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> new LtSyntaxVersion("") + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> new LtSyntaxVersion(null) + ); + } } From 8d6597255146b25766847cf35f378ea6e876851c Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Sun, 15 Feb 2026 23:42:32 +0300 Subject: [PATCH 06/10] fix linter --- src/test/java/org/eolang/lints/LtSyntaxVersionTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java index c3404d1e4..eaff6b839 100644 --- a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java +++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java @@ -161,15 +161,18 @@ void catchesPatchVersionMismatch() throws IOException { void rejectsInvalidParserVersion() { Assertions.assertThrows( IllegalArgumentException.class, - () -> new LtSyntaxVersion("latest") + () -> new LtSyntaxVersion("latest"), + "should reject non-semver parser version 'latest'" ); Assertions.assertThrows( IllegalArgumentException.class, - () -> new LtSyntaxVersion("") + () -> new LtSyntaxVersion(""), + "should reject empty string as parser version" ); Assertions.assertThrows( IllegalArgumentException.class, - () -> new LtSyntaxVersion(null) + () -> new LtSyntaxVersion(null), + "should reject null as parser version" ); } } From d40a44ff6d0f59a9f79b066500af170046bdff04 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Mon, 16 Feb 2026 00:07:52 +0300 Subject: [PATCH 07/10] again try fix linter --- .../org/eolang/lints/LtSyntaxVersion.java | 92 +++++++++++-------- .../org/eolang/lints/LtSyntaxVersionTest.java | 37 +++----- 2 files changed, 71 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java index 6a3a30a9d..c6bb2fd62 100644 --- a/src/main/java/org/eolang/lints/LtSyntaxVersion.java +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -22,7 +22,7 @@ * newer than the current parser version. * *

If the +syntax meta specifies a version higher than the parser's - * version, it means the code requires a newer parser, and this + * version, it means the code requires a newer parser, and this * lint reports an error.

* * @since 0.1.0 @@ -32,30 +32,30 @@ final class LtSyntaxVersion implements Lint { /** * The parser version. */ - private final String parserVersion; + private final String parser; /** * Default ctor. */ LtSyntaxVersion() { - this.parserVersion = "0.0.0"; + this("0.0.0"); } /** * Ctor. - * @param parserVersion The parser version (must be valid SemVer, e.g. 1.2.3). - * @throws IllegalArgumentException If parserVersion is not valid SemVer. + * @param ver The parser version (must be valid SemVer). */ - LtSyntaxVersion(final String parserVersion) { - if (parserVersion == null || !LtSyntaxVersion.valid(parserVersion)) { + @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") + LtSyntaxVersion(final String ver) { + if (ver == null || !LtSyntaxVersion.valid(ver)) { throw new IllegalArgumentException( String.format( "parser version must be valid SemVer (e.g. 1.2.3), got: %s", - parserVersion == null ? "null" : String.format("\"%s\"", parserVersion) + LtSyntaxVersion.display(ver) ) ); } - this.parserVersion = parserVersion; + this.parser = ver; } @Override @@ -82,7 +82,7 @@ public Collection defects(final XML xmir) throws IOException { ); continue; } - if (this.compareVersions(tail) < 0) { + if (this.newer(tail)) { defects.add( new Defect.Default( this.name(), @@ -92,7 +92,7 @@ public Collection defects(final XML xmir) throws IOException { String.format( "The +syntax meta requires version %s, but the current parser version is %s (older)", tail, - this.parserVersion + this.parser ) ) ); @@ -117,45 +117,65 @@ public String motive() throws IOException { ).asString(); } + /** + * Display a version value for error messages. + * @param ver The version value, possibly null. + * @return Formatted display string. + */ + private static String display(final String ver) { + final String result; + if (ver == null) { + result = "null"; + } else { + result = String.format("\"%s\"", ver); + } + return result; + } + /** * Check if the version string is a valid SemVer. - * @param version The version to validate. + * @param ver The version to validate. * @return True if valid. */ - private static boolean valid(final String version) { - return version.matches("^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9-]+)?$"); + private static boolean valid(final String ver) { + return ver.matches("^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9-]+)?$"); } /** - * Compare parser version to the given syntax version. - * @param syntaxVersion The version from +syntax meta. - * @return -1 if parser is older, 0 if equal, 1 if parser is newer. + * Check if the syntax version is newer than the parser version. + * @param syntax The version from +syntax meta. + * @return True if parser version is older than syntax version. */ - private int compareVersions(final String syntaxVersion) { - final int[] syntaxVer = LtSyntaxVersion.parts(syntaxVersion); - final int[] parserVer = LtSyntaxVersion.parts(this.parserVersion); - for (int i = 0; i < syntaxVer.length; i++) { - if (parserVer[i] < syntaxVer[i]) { - return -1; - } - if (parserVer[i] > syntaxVer[i]) { - return 1; - } + private boolean newer(final String syntax) { + final int[] left = LtSyntaxVersion.parts(this.parser); + final int[] right = LtSyntaxVersion.parts(syntax); + final int major = Integer.compare(left[0], right[0]); + final int minor = Integer.compare(left[1], right[1]); + final int patch = Integer.compare(left[2], right[2]); + final boolean result; + if (major != 0) { + result = major < 0; + } else if (minor != 0) { + result = minor < 0; + } else { + result = patch < 0; } - return 0; + return result; } /** * Parse the numeric parts of a SemVer string. - * @param version The version string. - * @return Array of [major, minor, patch]. + * @param ver The version string. + * @return Array of major, minor, patch. */ - private static int[] parts(final String version) { - final List segments = Splitter.on('.').splitToList(version.split("-", 2)[0]); - return new int[] { - segments.size() > 0 ? Integer.parseInt(segments.get(0)) : 0, - segments.size() > 1 ? Integer.parseInt(segments.get(1)) : 0, - segments.size() > 2 ? Integer.parseInt(segments.get(2)) : 0 + private static int[] parts(final String ver) { + final List segments = Splitter.on('.').splitToList( + ver.split("-", 2)[0] + ); + return new int[]{ + Integer.parseInt(segments.get(0)), + Integer.parseInt(segments.get(1)), + Integer.parseInt(segments.get(2)), }; } } diff --git a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java index eaff6b839..4220ecbd7 100644 --- a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java +++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java @@ -16,15 +16,21 @@ * * @since 0.1.0 */ +@SuppressWarnings("PMD.TooManyMethods") final class LtSyntaxVersionTest { + /** + * Common version used in tests. + */ + private static final String VER = "0.59.0"; + @Test void catchesMismatchWhenSyntaxIsNewer() throws IOException { MatcherAssert.assertThat( "should report error when +syntax version is newer than parser", new LtSyntaxVersion("0.58.0").defects( new EoSyntax( - "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER) ).parsed() ), Matchers.iterableWithSize(1) @@ -37,7 +43,7 @@ void allowsWhenParserIsNewer() throws IOException { "should not report error when parser version is newer", new LtSyntaxVersion("0.60.0").defects( new EoSyntax( - "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER) ).parsed() ), Matchers.emptyIterable() @@ -48,9 +54,9 @@ void allowsWhenParserIsNewer() throws IOException { void allowsWhenVersionsMatch() throws IOException { MatcherAssert.assertThat( "should not report error when versions match exactly", - new LtSyntaxVersion("0.59.0").defects( + new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects( new EoSyntax( - "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER) ).parsed() ), Matchers.emptyIterable() @@ -83,7 +89,7 @@ void usesDefaultVersionWhenNoArgCtor() throws IOException { void ignoresWhenNoSyntaxMeta() throws IOException { MatcherAssert.assertThat( "should not report error when no +syntax meta is present", - new LtSyntaxVersion("0.59.0").defects( + new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects( new EoSyntax( "# Foo.\n[] > foo\n" ).parsed() @@ -96,7 +102,7 @@ void ignoresWhenNoSyntaxMeta() throws IOException { void reportsErrorOnInvalidSyntaxFormat() throws IOException { MatcherAssert.assertThat( "should report error for invalid +syntax format", - new LtSyntaxVersion("0.59.0").defects( + new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects( new EoSyntax( "+syntax alpha\n\n# Foo.\n[] > foo\n" ).parsed() @@ -105,26 +111,13 @@ void reportsErrorOnInvalidSyntaxFormat() throws IOException { ); } - @Test - void reportsErrorSeverityForInvalidFormat() throws IOException { - MatcherAssert.assertThat( - "invalid format defect should have error severity", - new LtSyntaxVersion("0.59.0").defects( - new EoSyntax( - "+syntax alpha\n\n# Foo.\n[] > foo\n" - ).parsed() - ).iterator().next().severity(), - Matchers.equalTo(Severity.ERROR) - ); - } - @Test void reportsErrorSeverity() throws IOException { MatcherAssert.assertThat( "defect should have error severity", new LtSyntaxVersion("0.58.0").defects( new EoSyntax( - "+syntax 0.59.0\n\n# Foo.\n[] > foo\n" + String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER) ).parsed() ).iterator().next().severity(), Matchers.equalTo(Severity.ERROR) @@ -135,7 +128,7 @@ void reportsErrorSeverity() throws IOException { void catchesMajorVersionMismatch() throws IOException { MatcherAssert.assertThat( "should detect when major version is newer", - new LtSyntaxVersion("0.59.0").defects( + new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects( new EoSyntax( "+syntax 1.0.0\n\n# Foo.\n[] > foo\n" ).parsed() @@ -148,7 +141,7 @@ void catchesMajorVersionMismatch() throws IOException { void catchesPatchVersionMismatch() throws IOException { MatcherAssert.assertThat( "should detect when patch version is newer", - new LtSyntaxVersion("0.59.0").defects( + new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects( new EoSyntax( "+syntax 0.59.1\n\n# Foo.\n[] > foo\n" ).parsed() From 03a54ac8fdbe5b65d0ab3555572187381a2bb9e7 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Mon, 16 Feb 2026 00:18:38 +0300 Subject: [PATCH 08/10] finally fix --- src/main/java/org/eolang/lints/LtSyntaxVersion.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java index c6bb2fd62..57656bbda 100644 --- a/src/main/java/org/eolang/lints/LtSyntaxVersion.java +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -153,12 +153,12 @@ private boolean newer(final String syntax) { final int minor = Integer.compare(left[1], right[1]); final int patch = Integer.compare(left[2], right[2]); final boolean result; - if (major != 0) { - result = major < 0; - } else if (minor != 0) { + if (major == 0 && minor == 0) { + result = patch < 0; + } else if (major == 0) { result = minor < 0; } else { - result = patch < 0; + result = major < 0; } return result; } From 5b8eb1b9a75beffa58670f1ca09a873d865f1824 Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Mon, 16 Feb 2026 00:38:27 +0300 Subject: [PATCH 09/10] fix linter and refactor --- .../org/eolang/lints/LtSyntaxVersion.java | 73 +++++++------------ .../org/eolang/lints/LtSyntaxVersionTest.java | 6 +- 2 files changed, 31 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/eolang/lints/LtSyntaxVersion.java b/src/main/java/org/eolang/lints/LtSyntaxVersion.java index 57656bbda..685c3b96a 100644 --- a/src/main/java/org/eolang/lints/LtSyntaxVersion.java +++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java @@ -9,8 +9,10 @@ import com.jcabi.xml.XML; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.cactoos.io.ResourceOf; import org.cactoos.text.IoCheckedText; @@ -51,7 +53,9 @@ final class LtSyntaxVersion implements Lint { throw new IllegalArgumentException( String.format( "parser version must be valid SemVer (e.g. 1.2.3), got: %s", - LtSyntaxVersion.display(ver) + Optional.ofNullable(ver) + .map(v -> String.format("\"%s\"", v)) + .orElse("null") ) ); } @@ -67,7 +71,23 @@ public Collection defects(final XML xmir) throws IOException { for (final Xnav meta : metas) { final String tail = meta.element("tail").text().orElse(""); final String line = meta.attribute("line").text().orElse("0"); - if (!LtSyntaxVersion.valid(tail)) { + if (LtSyntaxVersion.valid(tail)) { + if (this.newer(tail)) { + defects.add( + new Defect.Default( + this.name(), + Severity.ERROR, + new OnDefault(xmir).get(), + Integer.parseInt(line), + String.format( + "The +syntax meta requires version %s, but the current parser version is %s (older)", + tail, + this.parser + ) + ) + ); + } + } else { defects.add( new Defect.Default( this.name(), @@ -80,22 +100,6 @@ public Collection defects(final XML xmir) throws IOException { ) ) ); - continue; - } - if (this.newer(tail)) { - defects.add( - new Defect.Default( - this.name(), - Severity.ERROR, - new OnDefault(xmir).get(), - Integer.parseInt(line), - String.format( - "The +syntax meta requires version %s, but the current parser version is %s (older)", - tail, - this.parser - ) - ) - ); } } return defects; @@ -117,21 +121,6 @@ public String motive() throws IOException { ).asString(); } - /** - * Display a version value for error messages. - * @param ver The version value, possibly null. - * @return Formatted display string. - */ - private static String display(final String ver) { - final String result; - if (ver == null) { - result = "null"; - } else { - result = String.format("\"%s\"", ver); - } - return result; - } - /** * Check if the version string is a valid SemVer. * @param ver The version to validate. @@ -147,20 +136,10 @@ private static boolean valid(final String ver) { * @return True if parser version is older than syntax version. */ private boolean newer(final String syntax) { - final int[] left = LtSyntaxVersion.parts(this.parser); - final int[] right = LtSyntaxVersion.parts(syntax); - final int major = Integer.compare(left[0], right[0]); - final int minor = Integer.compare(left[1], right[1]); - final int patch = Integer.compare(left[2], right[2]); - final boolean result; - if (major == 0 && minor == 0) { - result = patch < 0; - } else if (major == 0) { - result = minor < 0; - } else { - result = major < 0; - } - return result; + return Arrays.compare( + LtSyntaxVersion.parts(this.parser), + LtSyntaxVersion.parts(syntax) + ) < 0; } /** diff --git a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java index 4220ecbd7..b1bface0f 100644 --- a/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java +++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java @@ -64,7 +64,7 @@ void allowsWhenVersionsMatch() throws IOException { } @Test - void usesDefaultVersionWhenNoArgCtor() throws IOException { + void allowsMatchingDefaultVersion() throws IOException { MatcherAssert.assertThat( "with default ctor (0.0.0), +syntax 0.0.0 should not report error", new LtSyntaxVersion().defects( @@ -74,6 +74,10 @@ void usesDefaultVersionWhenNoArgCtor() throws IOException { ), Matchers.emptyIterable() ); + } + + @Test + void catchesNewerThanDefaultVersion() throws IOException { MatcherAssert.assertThat( "with default ctor (0.0.0), +syntax 0.0.1 should report error", new LtSyntaxVersion().defects( From 7e00764dc127a1e33ebcf476a02b1ead41a8193c Mon Sep 17 00:00:00 2001 From: "da.sadovnikov" Date: Mon, 16 Feb 2026 10:29:51 +0300 Subject: [PATCH 10/10] Add syntax version to MonoLints --- src/main/java/org/eolang/lints/MonoLints.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/eolang/lints/MonoLints.java b/src/main/java/org/eolang/lints/MonoLints.java index b4d814d17..d3c6a6908 100644 --- a/src/main/java/org/eolang/lints/MonoLints.java +++ b/src/main/java/org/eolang/lints/MonoLints.java @@ -31,7 +31,8 @@ final class MonoLints extends IterableEnvelope> { new PkByXsl(), List.of( new LtAsciiOnly(), - new LtReservedName() + new LtReservedName(), + new LtSyntaxVersion() ) ) );