diff --git a/src/main/java/org/eolang/lints/LtAsciiOnly.java b/src/main/java/org/eolang/lints/LtAsciiOnly.java
index ba2d5f383..555f39f91 100644
--- a/src/main/java/org/eolang/lints/LtAsciiOnly.java
+++ b/src/main/java/org/eolang/lints/LtAsciiOnly.java
@@ -16,10 +16,6 @@
/**
* A comment must include only ASCII characters.
* @since 0.1.0
- * @todo #14:35min Calculate comment line number with abusive character.
- * For now we just reusing object line number (via @line), which is not correct
- * for specifying on which line of the program comment is located. This issue
- * can be solved after this one.
* @todo #402:15min Replace the creation of new ArrayList<>(0) with the creation of
* ArrayList<>() without a constructor argument in whole project. Add ignore warning
* ConditionalRegexpMultilineCheck from Checkstyle (it doesn't seem to be possible at the moment
@@ -32,6 +28,8 @@ final class LtAsciiOnly implements Lint {
public Collection defects(final XML xmir) throws IOException {
final Collection defects = new ArrayList<>(0);
final Xnav xml = new Xnav(xmir.inner());
+ final Optional listing = xml.path("//listing")
+ .findFirst().map(elem -> elem.text().get());
final List comments = xml.path("/object/comments/comment")
.collect(Collectors.toList());
for (final Xnav comment : comments) {
@@ -42,8 +40,23 @@ public Collection defects(final XML xmir) throws IOException {
if (!abusive.isPresent()) {
continue;
}
- final int line = new LineOf(comment).value();
final Character chr = abusive.get();
+ final String text = comment.text().get();
+ final int pos = text.indexOf(chr);
+ final int line;
+ if (listing.isPresent()) {
+ final Optional found = LtAsciiOnly.locate(
+ listing.get(), text
+ );
+ if (found.isPresent()) {
+ line = found.get() + (int) text.substring(0, pos).chars()
+ .filter(c -> c == '\n').count();
+ } else {
+ line = new LineOf(comment).value();
+ }
+ } else {
+ line = new LineOf(comment).value();
+ }
defects.add(
new Defect.Default(
"ascii-only",
@@ -53,7 +66,7 @@ public Collection defects(final XML xmir) throws IOException {
"Only ASCII characters are allowed in comments, while \"%s\" is used at the line no.%s at the position no.%s",
chr,
line,
- comment.text().get().indexOf(chr) + 1
+ pos + 1
)
)
);
@@ -75,4 +88,34 @@ public String motive() throws IOException {
public Fix fix() {
return new FxEmpty();
}
+
+ /**
+ * Real source line of the abusive character.
+ * The comment text is located in the program listing, where each line
+ * starts with the {@code #} sign. The line of the character is the line
+ * of the first line of the comment.
+ * @param listing Full program listing
+ * @param text Comment text
+ * @return Real source line of the first line of the comment, if found
+ */
+ private static Optional locate(final String listing, final String text) {
+ final int newline = text.indexOf('\n');
+ final String headline;
+ if (newline < 0) {
+ headline = text;
+ } else {
+ headline = text.substring(0, newline);
+ }
+ final int index = listing.indexOf("# ".concat(headline));
+ final Optional result;
+ if (index < 0) {
+ result = Optional.empty();
+ } else {
+ result = Optional.of(
+ (int) listing.substring(0, index).chars()
+ .filter(chr -> chr == '\n').count() + 1
+ );
+ }
+ return result;
+ }
}
diff --git a/src/main/resources/org/eolang/lints/misc/redundant-attachment.xsl b/src/main/resources/org/eolang/lints/misc/redundant-attachment.xsl
index 9288c9536..ed70c2a89 100644
--- a/src/main/resources/org/eolang/lints/misc/redundant-attachment.xsl
+++ b/src/main/resources/org/eolang/lints/misc/redundant-attachment.xsl
@@ -21,13 +21,6 @@
-
diff --git a/src/main/resources/org/eolang/motives/misc/redundant-attachment.md b/src/main/resources/org/eolang/motives/misc/redundant-attachment.md
index 6b1ae81fc..57f67ebed 100644
--- a/src/main/resources/org/eolang/motives/misc/redundant-attachment.md
+++ b/src/main/resources/org/eolang/motives/misc/redundant-attachment.md
@@ -62,16 +62,3 @@ written in the source, is left alone too. The `!` suffix on a nameless
argument is such a case: `m.plus m!` makes the parser wrap `m` into a named
`.as-bytes` over `Φ.dataized`, and no `>>` exists in the source to be
removed.
-
-A void attribute declared as `? >> name` is left alone as well. The name
-after `>>` is only a local alias, while the published name is generated by
-the parser. The `>>` cannot be dropped, because the grammar has no nameless
-void, and switching to `? > name` would publish the attribute under `name`
-and change dispatch:
-
-```eo
-[] > choice
- ? >> left
- ? >> right
- left > @
-```
diff --git a/src/test/java/org/eolang/lints/LtAsciiOnlyTest.java b/src/test/java/org/eolang/lints/LtAsciiOnlyTest.java
index f7f6f0cb4..28c0efadc 100644
--- a/src/test/java/org/eolang/lints/LtAsciiOnlyTest.java
+++ b/src/test/java/org/eolang/lints/LtAsciiOnlyTest.java
@@ -87,4 +87,30 @@ void doesNotFlagNewlinesInMultilineComment() throws IOException {
Matchers.emptyIterable()
);
}
+
+ @Test
+ void reportsCorrectLineInMultilineComment() throws IOException {
+ MatcherAssert.assertThat(
+ "The abusive character is on the second line of the comment",
+ new ListOf<>(
+ new LtAsciiOnly().defects(
+ new EoProgram("org/eolang/lints/non-ascii-multiline.eo").parse()
+ )
+ ).get(0).line(),
+ Matchers.equalTo(2)
+ );
+ }
+
+ @Test
+ void reportsCorrectLineInTheMiddle() throws IOException {
+ MatcherAssert.assertThat(
+ "The abusive character is on the second line of a three-line comment",
+ new ListOf<>(
+ new LtAsciiOnly().defects(
+ new EoProgram("org/eolang/lints/non-ascii-middle.eo").parse()
+ )
+ ).get(0).line(),
+ Matchers.equalTo(2)
+ );
+ }
}
diff --git a/src/test/resources/org/eolang/lints/non-ascii-middle.eo b/src/test/resources/org/eolang/lints/non-ascii-middle.eo
new file mode 100644
index 000000000..f6cff4185
--- /dev/null
+++ b/src/test/resources/org/eolang/lints/non-ascii-middle.eo
@@ -0,0 +1,8 @@
+# first
+# привет middle
+# last
+
++spdx SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
++spdx SPDX-License-Identifier: MIT
+
+[] > foo
diff --git a/src/test/resources/org/eolang/lints/non-ascii-multiline.eo b/src/test/resources/org/eolang/lints/non-ascii-multiline.eo
new file mode 100644
index 000000000..b2cdbf1ce
--- /dev/null
+++ b/src/test/resources/org/eolang/lints/non-ascii-multiline.eo
@@ -0,0 +1,7 @@
+# This comment is fine
+# but here привет
+
++spdx SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
++spdx SPDX-License-Identifier: MIT
+
+[] > foo
diff --git a/src/test/resources/org/eolang/lints/unlint-ascii-only-line.eo b/src/test/resources/org/eolang/lints/unlint-ascii-only-line.eo
index 007363169..30a52fde6 100644
--- a/src/test/resources/org/eolang/lints/unlint-ascii-only-line.eo
+++ b/src/test/resources/org/eolang/lints/unlint-ascii-only-line.eo
@@ -2,6 +2,6 @@
+spdx SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
+spdx SPDX-License-Identifier: MIT
-+unlint ascii-only:3
++unlint ascii-only:1
[] > hello