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..685c3b96a
--- /dev/null
+++ b/src/main/java/org/eolang/lints/LtSyntaxVersion.java
@@ -0,0 +1,160 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
+ * SPDX-License-Identifier: MIT
+ */
+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;
+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;
+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 parser;
+
+ /**
+ * Default ctor.
+ */
+ LtSyntaxVersion() {
+ this("0.0.0");
+ }
+
+ /**
+ * Ctor.
+ * @param ver The parser version (must be valid SemVer).
+ */
+ @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",
+ Optional.ofNullable(ver)
+ .map(v -> String.format("\"%s\"", v))
+ .orElse("null")
+ )
+ );
+ }
+ this.parser = ver;
+ }
+
+ @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)) {
+ 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(),
+ Severity.ERROR,
+ new OnDefault(xmir).get(),
+ Integer.parseInt(line),
+ String.format(
+ "The format of the +syntax meta is wrong: %s (SemVer expected instead)",
+ tail
+ )
+ )
+ );
+ }
+ }
+ 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 ver The version to validate.
+ * @return True if valid.
+ */
+ private static boolean valid(final String ver) {
+ return ver.matches("^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9-]+)?$");
+ }
+
+ /**
+ * 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 boolean newer(final String syntax) {
+ return Arrays.compare(
+ LtSyntaxVersion.parts(this.parser),
+ LtSyntaxVersion.parts(syntax)
+ ) < 0;
+ }
+
+ /**
+ * Parse the numeric parts of a SemVer string.
+ * @param ver The version string.
+ * @return Array of major, minor, patch.
+ */
+ 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/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()
)
)
);
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
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..b1bface0f
--- /dev/null
+++ b/src/test/java/org/eolang/lints/LtSyntaxVersionTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link LtSyntaxVersion}.
+ *
+ * @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(
+ String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER)
+ ).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(
+ String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER)
+ ).parsed()
+ ),
+ Matchers.emptyIterable()
+ );
+ }
+
+ @Test
+ void allowsWhenVersionsMatch() throws IOException {
+ MatcherAssert.assertThat(
+ "should not report error when versions match exactly",
+ new LtSyntaxVersion(LtSyntaxVersionTest.VER).defects(
+ new EoSyntax(
+ String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER)
+ ).parsed()
+ ),
+ Matchers.emptyIterable()
+ );
+ }
+
+ @Test
+ void allowsMatchingDefaultVersion() 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()
+ );
+ }
+
+ @Test
+ void catchesNewerThanDefaultVersion() throws IOException {
+ 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(LtSyntaxVersionTest.VER).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(LtSyntaxVersionTest.VER).defects(
+ new EoSyntax(
+ "+syntax alpha\n\n# Foo.\n[] > foo\n"
+ ).parsed()
+ ),
+ Matchers.iterableWithSize(1)
+ );
+ }
+
+ @Test
+ void reportsErrorSeverity() throws IOException {
+ MatcherAssert.assertThat(
+ "defect should have error severity",
+ new LtSyntaxVersion("0.58.0").defects(
+ new EoSyntax(
+ String.format("+syntax %s\n\n# Foo.\n[] > foo\n", LtSyntaxVersionTest.VER)
+ ).parsed()
+ ).iterator().next().severity(),
+ Matchers.equalTo(Severity.ERROR)
+ );
+ }
+
+ @Test
+ void catchesMajorVersionMismatch() throws IOException {
+ MatcherAssert.assertThat(
+ "should detect when major version is newer",
+ new LtSyntaxVersion(LtSyntaxVersionTest.VER).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(LtSyntaxVersionTest.VER).defects(
+ new EoSyntax(
+ "+syntax 0.59.1\n\n# Foo.\n[] > foo\n"
+ ).parsed()
+ ),
+ Matchers.iterableWithSize(1)
+ );
+ }
+
+ @Test
+ void rejectsInvalidParserVersion() {
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> new LtSyntaxVersion("latest"),
+ "should reject non-semver parser version 'latest'"
+ );
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> new LtSyntaxVersion(""),
+ "should reject empty string as parser version"
+ );
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> new LtSyntaxVersion(null),
+ "should reject null as parser version"
+ );
+ }
+}