Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions src/main/java/org/eolang/lints/LtSyntaxVersion.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.</p>
*
* @since 0.1.0
*/
final class LtSyntaxVersion implements Lint<XML> {

/**
* 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<Defect> defects(final XML xmir) throws IOException {
final Collection<Defect> defects = new ArrayList<>(0);
final Xnav xml = new Xnav(xmir.inner());
final List<Xnav> 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;
}
Comment thread
Daniilmipt marked this conversation as resolved.

/**
* 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<String> 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)),
};
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/eolang/lints/MonoLints.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ final class MonoLints extends IterableEnvelope<Lint<XML>> {
new PkByXsl(),
List.of(
new LtAsciiOnly(),
new LtReservedName()
new LtReservedName(),
new LtSyntaxVersion()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Daniilmipt this will set the version to 0.0.0, which is not what we need, right? Maybe, you can get current version of from Manifests.read("EO-Version").

)
)
);
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/org/eolang/lints/metas/unique-metas.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<xsl:import href="/org/eolang/funcs/lineno.xsl"/>
<xsl:import href="/org/eolang/funcs/defect-context.xsl"/>
<xsl:output encoding="UTF-8"/>
<xsl:variable name="unique" select="('version', 'architect', 'home', 'package')"/>
<xsl:variable name="unique" select="('version', 'architect', 'home', 'package', 'syntax')"/>
<xsl:variable name="metas" select="/object/metas/meta"/>
<xsl:variable name="heads" select="/object/metas/meta/head"/>
<xsl:template match="/">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<defects>
<xsl:for-each select="/object/metas/meta">
<xsl:variable name="meta-head" select="head"/>
<xsl:variable name="predefined" select="('package', 'alias', 'version', 'rt', 'architect', 'home', 'unlint', 'probe', 'spdx')"/>
<xsl:variable name="predefined" select="('package', 'alias', 'version', 'rt', 'architect', 'home', 'unlint', 'probe', 'spdx', 'syntax')"/>
<xsl:if test="not($meta-head = $predefined)">
<xsl:element name="defect">
<xsl:variable name="line" select="eo:lineno(@line)"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 2 additions & 0 deletions src/main/resources/org/eolang/motives/metas/unknown-metas.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The following metas are supported:
* `+home`
* `+unlint`
* `+probe`
* `+syntax`

Incorrect:

Expand All @@ -29,6 +30,7 @@ Correct:
+architect yegor256@gmail.com
+rt jvm
+home https://earth.com
+syntax 0.59.0
+unlint unsorted-metas

[] > foo
Expand Down
Loading