Skip to content

Commit 72c2594

Browse files
committed
Issues I18n #69
1 parent 1504b64 commit 72c2594

File tree

8 files changed

+122
-27
lines changed

8 files changed

+122
-27
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"cross-env": "^7.0.3",
138138
"ecore": "^0.12.0",
139139
"eslint": "^8.55.0",
140+
"i18next": "^23.11.5",
140141
"jest": "^29.7.0",
141142
"merge-options": "^2.0.0",
142143
"rimraf": "^3.0.0",

src/interop/starlasu-v2-metamodel.ts

+16
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,22 @@ THE_ISSUE_ECLASS.get("eStructuralFeatures").add(ECore.EReference.create({
243243
eType: THE_POSITION_ECLASS,
244244
containment: true
245245
}));
246+
THE_ISSUE_ECLASS.get("eStructuralFeatures").add(ECore.EReference.create({
247+
name: "node",
248+
eType: THE_NODE_ECLASS,
249+
containment: false
250+
}));
251+
THE_ISSUE_ECLASS.get("eStructuralFeatures").add(ECore.EAttribute.create({
252+
name: "code",
253+
eType: ECore.EString,
254+
lowerBound: 1
255+
}));
256+
THE_ISSUE_ECLASS.get("eStructuralFeatures").add(ECore.EAttribute.create({
257+
name: "args",
258+
eType: ECore.EString,
259+
lowerBound: 0,
260+
upperBound: -1
261+
}));
246262

247263
export const THE_RESULT_ECLASS = ECore.EClass.create({
248264
name: "Result"

src/parsing/tylasu-parser.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class ANTLRTokenFactory extends TokenFactory<TylasuANTLRToken> {
6767
}
6868
}
6969

70+
export const SYNTAX_ERROR = "parser.syntaxError";
71+
export const INPUT_NOT_FULLY_CONSUMED = "parser.inputNotFullyConsumed";
72+
export const ERROR_NODE_FOUND = "parser.errorNodeFound";
73+
7074
export abstract class TylasuANTLRLexer<T extends TylasuToken> implements TylasuLexer<T> {
7175

7276
constructor(public readonly tokenFactory: TokenFactory<T>) {}
@@ -99,7 +103,8 @@ export abstract class TylasuANTLRLexer<T extends TylasuToken> implements TylasuL
99103

100104
if (t && (t.type != Token.EOF)) {
101105
const message = "The lexer didn't consume the entire input";
102-
issues.push(Issue.syntactic(message, IssueSeverity.WARNING, Position.ofTokenEnd(t)))
106+
issues.push(Issue.lexical(message, IssueSeverity.WARNING, Position.ofTokenEnd(t), undefined,
107+
INPUT_NOT_FULLY_CONSUMED))
103108
}
104109

105110
const code = inputStream.getTextFromRange(0, inputStream.size - 1);
@@ -117,7 +122,9 @@ export abstract class TylasuANTLRLexer<T extends TylasuToken> implements TylasuL
117122
Issue.lexical(
118123
msg || "unspecified",
119124
IssueSeverity.ERROR,
120-
Position.ofPoint(new Point(line, charPositionInLine))));
125+
Position.ofPoint(new Point(line, charPositionInLine)),
126+
undefined,
127+
SYNTAX_ERROR));
121128
}
122129
});
123130
}
@@ -177,15 +184,20 @@ export abstract class TylasuParser<
177184
Issue.syntactic(
178185
"The whole input was not consumed",
179186
IssueSeverity.ERROR,
180-
Position.ofTokenEnd(lastToken)));
187+
Position.ofTokenEnd(lastToken),
188+
undefined,
189+
INPUT_NOT_FULLY_CONSUMED));
181190
}
182191

183192
processDescendantsAndErrors(
184193
root,
185194
() => {},
186195
it => {
187-
const message = `Error node found (token: ${it.symbol?.text})`;
188-
issues.push(Issue.syntactic(message, IssueSeverity.ERROR, Position.ofParseTree(it)));
196+
const message = `Error node found (token: ${it.symbol?.type}${it.symbol?.text})`;
197+
issues.push(Issue.syntactic(message, IssueSeverity.ERROR, Position.ofParseTree(it),
198+
undefined,
199+
ERROR_NODE_FOUND,
200+
[it.symbol?.type?.toString() || "", it.symbol?.text || ""]));
189201
});
190202
}
191203

@@ -270,7 +282,9 @@ export abstract class TylasuParser<
270282
Issue.syntactic(
271283
msg || "unspecified",
272284
IssueSeverity.ERROR,
273-
Position.ofPoint(new Point(line, charPositionInLine))));
285+
Position.ofPoint(new Point(line, charPositionInLine)),
286+
undefined,
287+
SYNTAX_ERROR));
274288
}
275289
});
276290
}

src/transformation/transformation.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ export class ChildNodeFactory<Source, Target, Child> {
188188
*/
189189
const NO_CHILD_NODE = new ChildNodeFactory<any, any, any>("", (node) => node);
190190

191+
export const SOURCE_NODE_NOT_MAPPED = "ast.transform.sourceNotMapped";
192+
191193
/**
192194
* Implementation of a tree-to-tree transformation. For each source node type, we can register a factory that knows how
193195
* to create a transformed node. Then, this transformer can read metadata in the transformed node to recursively
@@ -264,15 +266,18 @@ export class ASTTransformer {
264266
if (this.allowGenericNode) {
265267
const origin : Origin | undefined = this.asOrigin(source);
266268
nodes = [new GenericNode(parent).withOrigin(origin)];
269+
const nodeName = getNodeDefinition(source)?.name || source?.constructor.name || "–";
267270
this.issues.push(
268271
Issue.semantic(
269-
`Source node not mapped: ${getNodeDefinition(source)?.name}`,
272+
`Source node not mapped: ${nodeName}`,
270273
IssueSeverity.INFO,
271-
origin?.position
274+
origin?.position,
275+
origin instanceof Node ? origin : undefined,
276+
SOURCE_NODE_NOT_MAPPED,
277+
[nodeName]
272278
)
273279
);
274-
}
275-
else {
280+
} else {
276281
throw new Error(`Unable to translate node ${source} (class ${getNodeDefinition(source)?.name})`)
277282
}
278283
}

src/validation.ts

+23-16
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
1+
import {Node} from "./model/model";
12
import {Position} from "./model/position";
23

34
export enum IssueType { LEXICAL, SYNTACTIC, SEMANTIC}
45

56
export enum IssueSeverity { ERROR, WARNING, INFO}
67

78
export class Issue {
8-
type: IssueType;
9-
message: string;
10-
severity: IssueSeverity = IssueSeverity.ERROR;
11-
position?: Position;
129

13-
constructor(type: IssueType, message: string, severity: IssueSeverity, position?: Position) {
14-
this.type = type;
15-
this.message = message;
16-
this.severity = severity;
17-
this.position = position;
10+
constructor(
11+
public readonly type: IssueType,
12+
public readonly message: string,
13+
public readonly severity: IssueSeverity,
14+
public readonly position?: Position,
15+
public readonly node?: Node,
16+
public readonly code?: string,
17+
public readonly args: string[] = []
18+
) {
19+
if (!position) {
20+
this.position = node?.position;
21+
}
1822
}
1923

20-
static lexical(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position): Issue {
21-
return new Issue(IssueType.LEXICAL, message, severity, position);
24+
static lexical(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position,
25+
node?: Node, code?: string, args: string[] = []): Issue {
26+
return new Issue(IssueType.LEXICAL, message, severity, position, node, code, args);
2227
}
2328

24-
static syntactic(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position): Issue {
25-
return new Issue(IssueType.SYNTACTIC, message, severity, position);
29+
static syntactic(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position,
30+
node?: Node, code?: string, args: string[] = []): Issue {
31+
return new Issue(IssueType.SYNTACTIC, message, severity, position, node, code, args);
2632
}
2733

28-
static semantic(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position): Issue {
29-
return new Issue(IssueType.SEMANTIC, message, severity, position);
34+
static semantic(message: string, severity: IssueSeverity = IssueSeverity.ERROR, position?: Position,
35+
node?: Node, code?: string, args: string[] = []): Issue {
36+
return new Issue(IssueType.SEMANTIC, message, severity, position, node, code, args);
3037
}
31-
}
38+
}

tests/data/playground/example1.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"eClass": "https://strumenta.com/starlasu/v2#//Issue",
2424
"type": "SEMANTIC",
2525
"message": "Something's wrong",
26-
"severity": "ERROR"
26+
"severity": "ERROR",
27+
"args": []
2728
}
2829
]
2930
},

tests/issues.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {Issue, IssueSeverity, SOURCE_NODE_NOT_MAPPED} from "../src";
2+
import i18next from 'i18next';
3+
import {SYNTAX_ERROR} from "../src/parsing";
4+
import {expect} from "chai";
5+
6+
describe('Issues', function() {
7+
it("can be translated",
8+
function () {
9+
i18next.init({
10+
lng: 'en', // if you're using a language detector, do not define the lng option
11+
debug: true,
12+
resources: {
13+
en: {
14+
translation: {
15+
ast: {
16+
transform: {
17+
sourceNotMapped: "Source node not mapped: {{type}}"
18+
}
19+
},
20+
parser: {
21+
syntaxError: "A syntax error occurred!"
22+
}
23+
}
24+
}
25+
}
26+
});
27+
let issue = Issue.syntactic("Unexpected token: foo", IssueSeverity.ERROR, undefined, undefined, SYNTAX_ERROR);
28+
expect(i18next.t(issue.code!)).to.equal("A syntax error occurred!");
29+
issue = Issue.syntactic("Unexpected token: foo", IssueSeverity.ERROR, undefined, undefined, SOURCE_NODE_NOT_MAPPED, ["SomeNode"]);
30+
expect(i18next.t(issue.code!, { type: issue.args[0] })).to.equal("Source node not mapped: SomeNode");
31+
});
32+
});

yarn.lock

+19
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,13 @@
263263
dependencies:
264264
"@babel/helper-plugin-utils" "^7.22.5"
265265

266+
"@babel/runtime@^7.23.2":
267+
version "7.24.7"
268+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
269+
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
270+
dependencies:
271+
regenerator-runtime "^0.14.0"
272+
266273
"@babel/template@^7.22.15", "@babel/template@^7.3.3":
267274
version "7.22.15"
268275
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
@@ -1780,6 +1787,13 @@ human-signals@^2.1.0:
17801787
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
17811788
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
17821789

1790+
i18next@^23.11.5:
1791+
version "23.11.5"
1792+
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.11.5.tgz#d71eb717a7e65498d87d0594f2664237f9e361ef"
1793+
integrity sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==
1794+
dependencies:
1795+
"@babel/runtime" "^7.23.2"
1796+
17831797
ignore@^5.2.0, ignore@^5.2.4:
17841798
version "5.2.4"
17851799
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
@@ -2709,6 +2723,11 @@ reflect-metadata@^0.1.13:
27092723
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
27102724
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
27112725

2726+
regenerator-runtime@^0.14.0:
2727+
version "0.14.1"
2728+
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
2729+
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
2730+
27122731
require-directory@^2.1.1:
27132732
version "2.1.1"
27142733
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"

0 commit comments

Comments
 (0)