Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
181 changes: 181 additions & 0 deletions src/main/java/org/eolang/lints/LtSyntaxVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* 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.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.
*
* <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",
LtSyntaxVersion.display(ver)
)
);
}
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)) {
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.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;
}

@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();
}

/**
* 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.
* @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) {
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 result;
}
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)),
};
}
}
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
Loading