Skip to content

Commit fb23d5c

Browse files
committed

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

fluent-syntax/src/ast.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,67 @@
77
*/
88
class BaseNode {
99
constructor() {}
10+
11+
equals(other, ignoredFields = ["span"]) {
12+
const thisKeys = new Set(Object.keys(this));
13+
const otherKeys = new Set(Object.keys(other));
14+
if (ignoredFields) {
15+
for (const fieldName of ignoredFields) {
16+
thisKeys.delete(fieldName);
17+
otherKeys.delete(fieldName);
18+
}
19+
}
20+
if (thisKeys.size !== otherKeys.size) {
21+
return false;
22+
}
23+
for (const fieldName of thisKeys) {
24+
if (!otherKeys.has(fieldName)) {
25+
return false;
26+
}
27+
const thisVal = this[fieldName];
28+
const otherVal = other[fieldName];
29+
if (typeof thisVal !== typeof otherVal) {
30+
return false;
31+
}
32+
if (thisVal instanceof Array) {
33+
if (thisVal.length !== otherVal.length) {
34+
return false;
35+
}
36+
// Sort elements of order-agnostic fields to ensure the
37+
// comparison is order-agnostic as well. Annotations should be
38+
// here too but they don't have sorting keys.
39+
if (["attributes", "variants"].indexOf(fieldName) >= 0) {
40+
thisVal.sort(sorting_key_compare);
41+
otherVal.sort(sorting_key_compare);
42+
}
43+
for (let i = 0, ii = thisVal.length; i < ii; ++i) {
44+
if (!scalars_equal(thisVal[i], otherVal[i], ignoredFields)) {
45+
return false;
46+
}
47+
}
48+
} else if (!scalars_equal(thisVal, otherVal, ignoredFields)) {
49+
return false;
50+
}
51+
}
52+
return true;
53+
}
54+
}
55+
56+
function scalars_equal(thisVal, otherVal, ignoredFields) {
57+
if (thisVal instanceof BaseNode) {
58+
return thisVal.equals(otherVal, ignoredFields);
59+
}
60+
return thisVal === otherVal;
61+
}
62+
63+
function sorting_key_compare(left, right) {
64+
if (left.sorting_key < right.sorting_key) {
65+
return -1;
66+
}
67+
if (left.sorting_key === right.sorting_key) {
68+
return 0;
69+
}
70+
return 1;
1071
}
1172

1273
/*
@@ -188,6 +249,10 @@ export class Attribute extends SyntaxNode {
188249
this.id = id;
189250
this.value = value;
190251
}
252+
253+
get sorting_key() {
254+
return this.id.name;
255+
}
191256
}
192257

193258
export class Variant extends SyntaxNode {
@@ -198,6 +263,13 @@ export class Variant extends SyntaxNode {
198263
this.value = value;
199264
this.default = def;
200265
}
266+
267+
get sorting_key() {
268+
if (this.key instanceof NumberLiteral) {
269+
return this.key.value;
270+
}
271+
return this.key.name;
272+
}
201273
}
202274

203275
export class NamedArgument extends SyntaxNode {

fluent-syntax/test/ast_test.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"use strict";
2+
3+
import assert from "assert";
4+
import { ftl } from "./util";
5+
import { FluentParser } from "../src";
6+
import * as AST from "../src/ast";
7+
8+
suite("BaseNode.equals", function() {
9+
setup(function() {
10+
this.parser = new FluentParser();
11+
});
12+
test("Identifier.equals", function() {
13+
const thisNode = new AST.Identifier("name");
14+
const otherNode = new AST.Identifier("name");
15+
assert.strictEqual(thisNode.equals(otherNode), true);
16+
});
17+
test("Node.type", function() {
18+
const thisNode = new AST.Identifier("name");
19+
const otherNode = new AST.StringLiteral("name");
20+
assert.strictEqual(thisNode.equals(otherNode), false);
21+
});
22+
test("Array children", function() {
23+
const thisNode = new AST.Pattern([
24+
new AST.TextElement("one"),
25+
new AST.TextElement("two"),
26+
new AST.TextElement("three"),
27+
]);
28+
let otherNode = new AST.Pattern([
29+
new AST.TextElement("one"),
30+
new AST.TextElement("two"),
31+
new AST.TextElement("three"),
32+
]);
33+
assert.strictEqual(thisNode.equals(otherNode), true);
34+
});
35+
test("Variants", function() {
36+
const thisRes = this.parser.parse(ftl`
37+
msg = { $val ->
38+
[few] things
39+
[1] one
40+
*[other] default
41+
}
42+
`);
43+
const otherRes = this.parser.parse(ftl`
44+
# a comment
45+
msg = { $val ->
46+
[few] things
47+
*[other] default
48+
[1] one
49+
}
50+
`);
51+
const thisNode = thisRes.body[0];
52+
const otherNode = otherRes.body[0];
53+
assert.strictEqual(thisNode.equals(otherNode), false);
54+
assert.strictEqual(thisNode.equals(otherNode, ['span', 'comment']), true);
55+
assert.strictEqual(thisNode.value.equals(otherNode.value), true);
56+
assert.strictEqual(thisNode.value.equals(otherNode.value, []), false);
57+
});
58+
test("Attributes without order", function() {
59+
const thisRes = this.parser.parse(ftl`
60+
msg =
61+
.attr1 = one
62+
.attr2 = two
63+
`);
64+
const otherRes = this.parser.parse(ftl`
65+
msg =
66+
.attr2 = two
67+
.attr1 = one
68+
`);
69+
const thisNode = thisRes.body[0];
70+
const otherNode = otherRes.body[0];
71+
assert.strictEqual(thisNode.equals(otherNode), true);
72+
});
73+
});

0 commit comments

Comments
 (0)