Skip to content

Commit 6d611fc

Browse files
authored
Merge pull request #243 from h1alexbel/117
feat(#117): atom-is-not-unique
2 parents 5bd8e7f + 951d7ca commit 6d611fc

File tree

24 files changed

+1078
-88
lines changed

24 files changed

+1078
-88
lines changed

src/main/java/org/eolang/lints/PkWpa.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.cactoos.iterable.Shuffled;
3030
import org.cactoos.list.ListOf;
3131
import org.eolang.lints.critical.LtIncorrectAlias;
32+
import org.eolang.lints.errors.LtAtomIsNotUnique;
3233
import org.eolang.lints.errors.LtObjectIsNotUnique;
3334
import org.eolang.lints.units.LtUnitTestMissing;
3435
import org.eolang.lints.units.LtUnitTestWithoutLiveFile;
@@ -53,7 +54,8 @@ public PkWpa() {
5354
new LtUnitTestMissing(),
5455
new LtUnitTestWithoutLiveFile(),
5556
new LtIncorrectAlias(),
56-
new LtObjectIsNotUnique()
57+
new LtObjectIsNotUnique(),
58+
new LtAtomIsNotUnique()
5759
)
5860
)
5961
);
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016-2025 Objectionary.com
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included
14+
* in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package org.eolang.lints.errors;
25+
26+
import com.jcabi.xml.XML;
27+
import com.jcabi.xml.XSL;
28+
import com.jcabi.xml.XSLDocument;
29+
import java.util.Collection;
30+
import java.util.HashSet;
31+
import java.util.LinkedList;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Objects;
35+
import java.util.function.Function;
36+
import java.util.stream.Collectors;
37+
import java.util.stream.IntStream;
38+
import org.cactoos.io.ResourceOf;
39+
import org.cactoos.io.UncheckedInput;
40+
import org.cactoos.list.ListOf;
41+
import org.cactoos.text.TextOf;
42+
import org.cactoos.text.UncheckedText;
43+
import org.eolang.lints.Defect;
44+
import org.eolang.lints.Lint;
45+
import org.eolang.lints.Severity;
46+
47+
/**
48+
* All FQNs that have `@atom` in the entire scope must be unique.
49+
* This lint firstly transforms the original XMIR into XMIR that contains `@fqn`
50+
* attributes for each atom `o`, and then lints it.
51+
*
52+
* @since 0.0.31
53+
*/
54+
public final class LtAtomIsNotUnique implements Lint<Map<String, XML>> {
55+
56+
/**
57+
* Stylesheet for adding `@fqn` attribute for atoms.
58+
*/
59+
private final XSL pre;
60+
61+
/**
62+
* Ctor.
63+
*/
64+
public LtAtomIsNotUnique() {
65+
this(
66+
new XSLDocument(
67+
new UncheckedInput(
68+
new ResourceOf("org/eolang/funcs/atom-fqns.xsl")
69+
).stream()
70+
)
71+
);
72+
}
73+
74+
/**
75+
* Ctor.
76+
*
77+
* @param sheet Sheet
78+
*/
79+
public LtAtomIsNotUnique(final XSL sheet) {
80+
this.pre = sheet;
81+
}
82+
83+
@Override
84+
public String name() {
85+
return "atom-is-not-unique";
86+
}
87+
88+
@Override
89+
public Collection<Defect> defects(final Map<String, XML> pkg) {
90+
final Collection<Defect> defects = new LinkedList<>();
91+
final Map<XML, List<String>> index = pkg.values().stream()
92+
.map(this.pre::transform)
93+
.collect(Collectors.toMap(Function.identity(), LtAtomIsNotUnique::fqns));
94+
final Collection<String> checked = new HashSet<>(0);
95+
index.forEach(
96+
(xmir, fqns) -> {
97+
fqns.stream()
98+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
99+
.entrySet()
100+
.stream()
101+
.filter(entry -> entry.getValue() > 1L)
102+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
103+
.forEach(
104+
(aname, count) ->
105+
IntStream.range(0, Math.toIntExact(count)).forEach(
106+
pos -> defects.add(this.singleDefect(xmir, aname, pos))
107+
)
108+
);
109+
index.forEach(
110+
(next, names) -> {
111+
if (!Objects.equals(next, xmir)) {
112+
final String pair = LtAtomIsNotUnique.pairHash(xmir, next);
113+
if (!checked.contains(pair)) {
114+
checked.add(pair);
115+
names.stream()
116+
.filter(fqns::contains)
117+
.forEach(
118+
aname -> {
119+
defects.add(this.sharedDefect(next, xmir, aname));
120+
defects.add(this.sharedDefect(xmir, next, aname));
121+
}
122+
);
123+
}
124+
}
125+
}
126+
);
127+
}
128+
);
129+
return defects;
130+
}
131+
132+
@Override
133+
public String motive() {
134+
return new UncheckedText(
135+
new TextOf(
136+
new ResourceOf(
137+
String.format(
138+
"org/eolang/motives/errors/%s.md", this.name()
139+
)
140+
)
141+
)
142+
).asString();
143+
}
144+
145+
private Defect singleDefect(final XML xmir, final String fqn, final int pos) {
146+
return new Defect.Default(
147+
this.name(),
148+
Severity.ERROR,
149+
xmir.xpath("/program/@name").stream()
150+
.findFirst().orElse("unknown"),
151+
Integer.parseInt(
152+
xmir.xpath(
153+
String.format(
154+
"//o[@atom and @name='%s']/@line",
155+
LtAtomIsNotUnique.oname(fqn)
156+
)
157+
).get(pos)
158+
),
159+
String.format("Atom '%s' is duplicated", fqn)
160+
);
161+
}
162+
163+
private Defect sharedDefect(final XML xmir, final XML original, final String fqn) {
164+
return new Defect.Default(
165+
this.name(),
166+
Severity.ERROR,
167+
xmir.xpath("/program/@name").stream().findFirst().orElse("unknown"),
168+
Integer.parseInt(
169+
xmir.xpath(
170+
String.format(
171+
"//o[@atom and @name='%s']/@line",
172+
LtAtomIsNotUnique.oname(fqn)
173+
)
174+
).get(0)
175+
),
176+
String.format(
177+
"Atom with FQN '%s' is duplicated, original was found in '%s'",
178+
fqn, original.xpath("/program/@name").stream().findFirst().orElse("unknown")
179+
)
180+
);
181+
}
182+
183+
private static List<String> fqns(final XML xmir) {
184+
final List<String> result;
185+
final List<String> fqns = xmir.xpath("//o[@fqn]/@fqn");
186+
if (xmir.nodes("/program/metas/meta[head='package']").size() == 1) {
187+
final String pack = xmir.xpath("/program/metas/meta[head='package']/tail/text()")
188+
.get(0);
189+
result = fqns.stream().map(fqn -> String.format("%s.%s", pack, fqn))
190+
.collect(Collectors.toList());
191+
} else {
192+
result = fqns;
193+
}
194+
return result.stream()
195+
.map(fqn -> String.format("Ф.%s", fqn))
196+
.collect(Collectors.toList());
197+
}
198+
199+
private static String oname(final String fqn) {
200+
final String result;
201+
final List<String> parts = new ListOf<>(fqn.split("\\."));
202+
if (parts.size() > 1) {
203+
result = parts.get(parts.size() - 1);
204+
} else {
205+
result = parts.get(0);
206+
}
207+
return result;
208+
}
209+
210+
private static String pairHash(final XML first, final XML second) {
211+
final String pair;
212+
if (first.hashCode() < second.hashCode()) {
213+
pair = String.format("%d:%d", first.hashCode(), second.hashCode());
214+
} else {
215+
pair = String.format("%d:%d", second.hashCode(), first.hashCode());
216+
}
217+
return pair;
218+
}
219+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
The MIT License (MIT)
4+
5+
Copyright (c) 2016-2025 Objectionary.com
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included
15+
in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
-->
25+
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" id="atom-fqns" version="2.0">
26+
<xsl:template match="node()|@*">
27+
<xsl:copy>
28+
<xsl:apply-templates select="@*|node()"/>
29+
</xsl:copy>
30+
</xsl:template>
31+
<xsl:template match="o">
32+
<xsl:copy>
33+
<xsl:apply-templates select="@*"/>
34+
<xsl:if test="@atom">
35+
<xsl:attribute name="fqn">
36+
<xsl:call-template name="generate-fqn"/>
37+
</xsl:attribute>
38+
</xsl:if>
39+
<xsl:apply-templates select="node()"/>
40+
</xsl:copy>
41+
</xsl:template>
42+
<xsl:template name="generate-fqn">
43+
<xsl:for-each select="ancestor-or-self::o">
44+
<xsl:if test="position() != 1">.</xsl:if>
45+
<xsl:value-of select="@name"/>
46+
</xsl:for-each>
47+
</xsl:template>
48+
</xsl:stylesheet>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Atom is not unique
2+
3+
All object FQNs that have `@atom` attribute across al `.eo` files must not be
4+
duplicated.
5+
6+
Incorrect:
7+
8+
`foo.eo`:
9+
10+
```eo
11+
+package xyz
12+
13+
# A.
14+
[] > a /number
15+
```
16+
17+
`bar.eo`:
18+
19+
```eo
20+
+package xyz
21+
22+
# A.
23+
[] > a /number
24+
```
25+
26+
To fix this, rename duplicated object.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016-2025 Objectionary.com
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included
14+
* in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package org.eolang.lints;
25+
26+
import com.jcabi.xml.XML;
27+
import java.io.File;
28+
import org.cactoos.Scalar;
29+
import org.cactoos.io.ResourceOf;
30+
import org.eolang.parser.EoSyntax;
31+
32+
/**
33+
* Parsed EO syntax to XMIR.
34+
* @since 0.0.31
35+
*/
36+
public final class ParsedEo implements Scalar<XML> {
37+
38+
/**
39+
* Path to the EO program.
40+
*/
41+
private final String path;
42+
43+
/**
44+
* Ctor.
45+
* @param pth Path to EO program
46+
*/
47+
public ParsedEo(final String pth) {
48+
this.path = pth;
49+
}
50+
51+
@Override
52+
public XML value() throws Exception {
53+
return new EoSyntax(
54+
new File(this.path).getName(),
55+
new ResourceOf(this.path)
56+
).parsed();
57+
}
58+
}

0 commit comments

Comments
 (0)