Skip to content

Commit 0af4d59

Browse files
authored
Merge branch 'master' into zeronine
2 parents 0e22abd + c5f0ea7 commit 0af4d59

23 files changed

+2125
-185
lines changed

fluent-gecko/src/fluent_syntax.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
/* eslint no-unused-vars: 0 */
1+
/* eslint object-shorthand: "off",
2+
no-unused-vars: "off",
3+
no-redeclare: "off",
4+
comma-dangle: "off",
5+
no-labels: "off" */
26

37
import FluentParser from "../../fluent-syntax/src/parser";
48
import FluentSerializer from "../../fluent-syntax/src/serializer";
59
import * as ast from "../../fluent-syntax/src/ast";
610

7-
let EXPORTED_SYMBOLS = [
11+
this.EXPORTED_SYMBOLS = [
812
"FluentParser",
913
"FluentSerializer",
10-
...Object.keys(ast)
14+
...Object.keys(ast),
1115
];

fluent-gecko/xpcom_config.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ export default Object.assign({}, bundleConfig, {
66
context: 'this',
77
output: {
88
format: 'es',
9-
banner: `/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
9+
banner: `\
10+
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
1011
11-
/* Copyright 2017 Mozilla Foundation and others
12+
/* Copyright 2019 Mozilla Foundation and others
1213
*
1314
* Licensed under the Apache License, Version 2.0 (the "License");
1415
* you may not use this file except in compliance with the License.
@@ -21,7 +22,9 @@ export default Object.assign({}, bundleConfig, {
2122
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2223
* See the License for the specific language governing permissions and
2324
* limitations under the License.
24-
*/\n\n`,
25+
*/
26+
27+
`,
2528
},
2629
plugins: [
2730
nodeResolve(),

fluent-syntax/CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## fluent-syntax 0.11.0 (March 25, 2019)
4+
5+
- Add `BaseNode.equals` and `BaseNode.clone`. (#172)
6+
7+
The new `BaseNode` methods can be used to compare two nodes and to create
8+
a deep copy of an AST node.
9+
10+
- Add `Visitor` and `Transformer`. (#172)
11+
12+
Add two new exports: `Visitor` for read-only iteration over AST trees,
13+
and `Transformer` for in-place mutation of AST trees.
14+
15+
- Export `serializeExpression` and `serializeVariantKey`. (#350)
16+
17+
The `FluentSerializer.serializeExpression` method has been removed in
18+
favor of a module-wide stateless function `serializeExpression`.
19+
20+
321
## fluent-syntax 0.10.0 (December 13, 2018)
422

523
This release of `fluent-syntax` brings support for version 0.8 of the

fluent-syntax/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "fluent-syntax",
33
"description": "AST and parser for Fluent",
4-
"version": "0.10.0",
4+
"version": "0.11.0",
55
"homepage": "http://projectfluent.org",
66
"author": "Mozilla <[email protected]>",
77
"license": "Apache-2.0",

fluent-syntax/src/ast.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,69 @@
55
* Annotation.
66
*
77
*/
8-
class BaseNode {
8+
export 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+
for (let i = 0; i < thisVal.length; ++i) {
37+
if (!scalarsEqual(thisVal[i], otherVal[i], ignoredFields)) {
38+
return false;
39+
}
40+
}
41+
} else if (!scalarsEqual(thisVal, otherVal, ignoredFields)) {
42+
return false;
43+
}
44+
}
45+
return true;
46+
}
47+
48+
clone() {
49+
function visit(value) {
50+
if (value instanceof BaseNode) {
51+
return value.clone();
52+
}
53+
if (Array.isArray(value)) {
54+
return value.map(visit);
55+
}
56+
return value;
57+
}
58+
const clone = Object.create(this.constructor.prototype);
59+
for (const prop of Object.keys(this)) {
60+
clone[prop] = visit(this[prop]);
61+
}
62+
return clone;
63+
}
64+
}
65+
66+
function scalarsEqual(thisVal, otherVal, ignoredFields) {
67+
if (thisVal instanceof BaseNode) {
68+
return thisVal.equals(otherVal, ignoredFields);
69+
}
70+
return thisVal === otherVal;
1071
}
1172

1273
/*

fluent-syntax/src/index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import FluentParser from "./parser";
2-
import FluentSerializer from "./serializer";
1+
import {FluentParser} from "./parser";
2+
import {FluentSerializer} from "./serializer";
33

44
export * from "./ast";
5-
export { FluentParser, FluentSerializer };
5+
export * from "./parser";
6+
export * from "./serializer";
7+
export * from "./visitor";
68

79
export function parse(source, opts) {
810
const parser = new FluentParser(opts);

fluent-syntax/src/parser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ function withSpan(fn) {
3030
}
3131

3232

33-
export default class FluentParser {
33+
export
34+
class FluentParser {
3435
constructor({
3536
withSpans = true,
3637
} = {}) {

fluent-syntax/src/serializer.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ function isSelectExpr(elem) {
1313
&& elem.expression.type === "SelectExpression";
1414
}
1515

16+
export
1617
// Bit masks representing the state of the serializer.
17-
export const HAS_ENTRIES = 1;
18+
const HAS_ENTRIES = 1;
1819

19-
export default class FluentSerializer {
20+
export
21+
class FluentSerializer {
2022
constructor({ withJunk = false } = {}) {
2123
this.withJunk = withJunk;
2224
}
@@ -68,10 +70,6 @@ export default class FluentSerializer {
6870
throw new Error(`Unknown entry type: ${entry.type}`);
6971
}
7072
}
71-
72-
serializeExpression(expr) {
73-
return serializeExpression(expr);
74-
}
7573
}
7674

7775

@@ -177,6 +175,7 @@ function serializePlaceable(placeable) {
177175
}
178176

179177

178+
export
180179
function serializeExpression(expr) {
181180
switch (expr.type) {
182181
case "StringLiteral":
@@ -247,11 +246,14 @@ function serializeNamedArgument(arg) {
247246
}
248247

249248

249+
export
250250
function serializeVariantKey(key) {
251251
switch (key.type) {
252252
case "Identifier":
253253
return key.name;
254+
case "NumberLiteral":
255+
return key.value;
254256
default:
255-
return serializeExpression(key);
257+
throw new Error(`Unknown variant key type: ${key.type}`);
256258
}
257259
}

fluent-syntax/src/visitor.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { BaseNode } from "./ast";
2+
3+
/*
4+
* Abstract Visitor pattern
5+
*/
6+
export class Visitor {
7+
visit(node) {
8+
if (Array.isArray(node)) {
9+
node.forEach(child => this.visit(child));
10+
return;
11+
}
12+
if (!(node instanceof BaseNode)) {
13+
return;
14+
}
15+
const visit = this[`visit${node.type}`] || this.genericVisit;
16+
visit.call(this, node);
17+
}
18+
19+
genericVisit(node) {
20+
for (const propname of Object.keys(node)) {
21+
this.visit(node[propname]);
22+
}
23+
}
24+
}
25+
26+
/*
27+
* Abstract Transformer pattern
28+
*/
29+
export class Transformer extends Visitor {
30+
visit(node) {
31+
if (!(node instanceof BaseNode)) {
32+
return node;
33+
}
34+
const visit = this[`visit${node.type}`] || this.genericVisit;
35+
return visit.call(this, node);
36+
}
37+
38+
genericVisit(node) {
39+
for (const propname of Object.keys(node)) {
40+
const propvalue = node[propname];
41+
if (Array.isArray(propvalue)) {
42+
const newvals = propvalue
43+
.map(child => this.visit(child))
44+
.filter(newchild => newchild !== undefined);
45+
node[propname] = newvals;
46+
}
47+
if (propvalue instanceof BaseNode) {
48+
const new_val = this.visit(propvalue);
49+
if (new_val === undefined) {
50+
delete node[propname];
51+
} else {
52+
node[propname] = new_val;
53+
}
54+
}
55+
}
56+
return node;
57+
}
58+
}

fluent-syntax/test/ast_test.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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.ok(thisNode.clone() instanceof AST.Identifier);
16+
assert.strictEqual(thisNode.equals(otherNode), true);
17+
assert.strictEqual(thisNode.equals(thisNode.clone()), true);
18+
assert.notStrictEqual(thisNode, thisNode.clone());
19+
});
20+
test("Node.type", function() {
21+
const thisNode = new AST.Identifier("name");
22+
const otherNode = new AST.StringLiteral("name");
23+
assert.strictEqual(thisNode.equals(otherNode), false);
24+
});
25+
test("Array children", function() {
26+
const thisNode = new AST.Pattern([
27+
new AST.TextElement("one"),
28+
new AST.TextElement("two"),
29+
new AST.TextElement("three"),
30+
]);
31+
let otherNode = new AST.Pattern([
32+
new AST.TextElement("one"),
33+
new AST.TextElement("two"),
34+
new AST.TextElement("three"),
35+
]);
36+
assert.strictEqual(thisNode.equals(otherNode), true);
37+
});
38+
test("Variant order matters", function() {
39+
const thisRes = this.parser.parse(ftl`
40+
msg = { $val ->
41+
[few] things
42+
[1] one
43+
*[other] default
44+
}
45+
`);
46+
const otherRes = this.parser.parse(ftl`
47+
msg = { $val ->
48+
[few] things
49+
*[other] default
50+
[1] one
51+
}
52+
`);
53+
const thisNode = thisRes.body[0];
54+
const otherNode = otherRes.body[0];
55+
assert.strictEqual(thisNode.equals(otherNode), false);
56+
assert.strictEqual(thisRes.equals(thisRes.clone(), []), true);
57+
assert.notStrictEqual(thisRes, thisRes.clone());
58+
});
59+
test("Attribute order matters", function() {
60+
const thisRes = this.parser.parse(ftl`
61+
msg =
62+
.attr1 = one
63+
.attr2 = two
64+
`);
65+
const otherRes = this.parser.parse(ftl`
66+
msg =
67+
.attr2 = two
68+
.attr1 = one
69+
`);
70+
const thisNode = thisRes.body[0];
71+
const otherNode = otherRes.body[0];
72+
assert.strictEqual(thisNode.equals(otherNode), false);
73+
});
74+
});

0 commit comments

Comments
 (0)