Repository: w3c/webidl2.js
Pull request #270 (2019-03-05 13:20:40)

refactor: dictionary as a class
Removes a redundant argument from `name_token()`.


Patch:
diff --git a/lib/webidl2.js b/lib/webidl2.js
index e6f6e33..9e78b17 100644
--- a/lib/webidl2.js
+++ b/lib/webidl2.js
@@ -733,18 +733,6 @@
       return ret;
     }
 
-    function inheritance() {
-      const colon = consume(":");
-      if (colon) {
-        const inh = consume(ID) || error("No type in inheritance");
-        return {
-          name: unescape(inh.value),
-          escapedName: inh.value,
-          trivia: { colon: colon.trivia, name: inh.trivia }
-        };
-      }
-    }
-
     function operation_rest(ret) {
       const { body } = ret;
       body.trivia = {};
@@ -955,6 +943,67 @@
       return ret;
     }
 
+    class Inheritance extends Definition {
+      static parse() {
+        const colon = consume(":");
+        if (!colon) {
+          return;
+        }
+        const name = consume(ID) || error("No type in inheritance");
+        return new Inheritance({ tokens: { colon, name } });
+      }
+
+      get name() {
+        return unescape(this.escapedName);
+      }
+      get escapedName() {
+        return this.tokens.name.value;
+      }
+    }
+
+    class Container extends Definition {
+      static parse(instance, { type, inheritable, allowedMembers }) {
+        const { tokens } = instance;
+        tokens.name = consume(ID) || error("No name for interface");
+        current = instance;
+        if (inheritable) {
+          instance.inheritance = Inheritance.parse() || null;
+        }
+        tokens.open = consume("{") || error(`Bodyless ${type}`);
+        instance.members = [];
+        while (true) {
+          tokens.close = consume("}");
+          if (tokens.close) {
+            tokens.termination = consume(";") || error(`Missing semicolon after ${type}`);
+            return instance;
+          }
+          const ea = extended_attrs();
+          let mem;
+          for (const [parser, ...args] of allowedMembers) {
+            mem = parser(...args);
+            if (mem) {
+              break;
+            }
+          }
+          if (!mem) {
+            error("Unknown member");
+          }
+          mem.extAttrs = ea;
+          instance.members.push(mem);
+        }
+      }
+
+      get partial() {
+        return unvalue_token(this.tokens.partial);
+      }
+      get name() {
+        return unescape(this.escapedName);
+      }
+      get escapedName() {
+        return this.tokens.name.value;
+      }
+    }
+
     function interface_rest({ typeName = "interface", partialModifier = null } = {}) {
       const name = consume(ID) || error("No name for interface");
       const trivia = {
@@ -970,7 +1019,7 @@
         members: mems,
         trivia
       };
-      if (!partialModifier) ret.inheritance = inheritance() || null;
+      if (!partialModifier) ret.inheritance = Inheritance.parse() || null;
       const open = consume("{") || error("Bodyless interface");
       trivia.open = open.trivia;
       while (true) {
@@ -1078,42 +1127,33 @@
     }
 
     function partial() {
-      const token = consume("partial");
-      if (!token) return;
-      const partialModifier = { trivia: token.trivia };
-      return dictionary({ partialModifier }) ||
+      const partial = optional_consume("partial");
+      if (!partial) return;
+      const partialModifier = { trivia: partial.trivia };
+      return Dictionary.parse({ partial }) ||
         interface_({ partialModifier }) ||
         namespace({ partialModifier }) ||
         error("Partial doesn't apply to anything");
     }
 
-    function dictionary({ partialModifier = null } = {}) {
-      const base = consume("dictionary");
-      if (!base) return;
-      const trivia = { base: base.trivia };
-      const name = consume(ID) || error("No name for dictionary");
-      trivia.name = name.trivia;
-      const mems = [];
-      const ret = current = {
-        type: "dictionary",
-        name: partialModifier ? name.value : sanitize_name(name.value, "dictionary"),
-        escapedName: name.value,
-        partial: partialModifier,
-        members: mems,
-        trivia
-      };
-      if (!partialModifier) ret.inheritance = inheritance() || null;
-      const open = consume("{") || error("Bodyless dictionary");
-      trivia.open = open.trivia;
-      while (true) {
-        const close = consume("}");
-        if (close) {
-          trivia.close = close.trivia;
-          const termination = consume(";") || error("Missing semicolon after dictionary");
-          trivia.termination = termination.trivia;
-          return ret;
+    class Dictionary extends Container {
+      static parse({ partial } = {}) {
+        const tokens = { partial };
+        tokens.base = consume("dictionary");
+        if (!tokens.base) {
+          return;
         }
-        ret.members.push(Field.parse());
+        return Container.parse(new Dictionary({ tokens }), {
+          type: "dictionary",
+          inheritable: !partial,
+          allowedMembers: [
+            [Field.parse],
+          ]
+        });
+      }
+
+      get type() {
+        return "dictionary";
       }
     }
 
@@ -1271,7 +1311,7 @@
       return callback() ||
         interface_() ||
         partial() ||
-        dictionary() ||
+        Dictionary.parse() ||
         Enum.parse() ||
         Typedef.parse() ||
         Includes.parse() ||
diff --git a/lib/writer.js b/lib/writer.js
index 9ac1d47..2a50090 100644
--- a/lib/writer.js
+++ b/lib/writer.js
@@ -46,11 +46,11 @@
     }
 
     function reference_token(t, unescaped) {
-      return t ? wrap`${ts.trivia(t.trivia)}${ts.reference(unescaped || t.value)}` : "";
+      return t ? wrap`${ts.trivia(t.trivia)}${reference(t.value, unescaped)}` : "";
     }
 
-    function name_token(t, unescaped, arg) {
-      return t ? wrap`${ts.trivia(t.trivia)}${ts.name(unescaped || t.value, arg)}` : "";
+    function name_token(t, arg) {
+      return t ? wrap`${ts.trivia(t.trivia)}${ts.name(t.value, arg)}` : "";
     }
 
     function type_body(it) {
@@ -173,8 +173,11 @@
       if (!inh) {
         return "";
       }
-      const trivia = extract_trivia(inh);
-      return wrap`${trivia.colon}:${trivia.name}${ts.inheritance(reference(inh.escapedName, inh.name))}`;
+      return ts.wrap([
+        token(inh.tokens.colon),
+        ts.trivia(inh.tokens.name.trivia),
+        ts.inheritance(reference(inh.escapedName, inh.name))
+      ]);
     }
 
     function container(type) {
@@ -214,7 +217,7 @@
         extended_attributes(it.extAttrs),
         token(it.tokens.required),
         ts.type(type(it.idlType)),
-        name_token(it.tokens.name, it.escapedName, { data: it, parent }),
+        name_token(it.tokens.name, { data: it, parent }),
         default_(it.default, it),
         token(it.tokens.termination)
       ]));
@@ -235,16 +238,16 @@
         extended_attributes(it.extAttrs),
         token(it.tokens.base),
         ts.type(type(it.idlType)),
-        name_token(it.tokens.name, it.escapedName, { data: it }),
+        name_token(it.tokens.name, { data: it }),
         token(it.tokens.termination)
       ]));
     }
     function includes(it) {
       return ts.definition(ts.wrap([
         extended_attributes(it.extAttrs),
-        reference_token(it.tokens.target, it.escapedTarget),
+        reference_token(it.tokens.target, it.target),
         token(it.tokens.includes),
-        reference_token(it.tokens.mixin, it.escapedIncludes),
+        reference_token(it.tokens.mixin, it.includes),
         token(it.tokens.termination)
       ]));
     }
@@ -264,7 +267,7 @@
       return ts.definition(ts.wrap([
         extended_attributes(it.extAttrs),
         token(it.tokens.base),
-        name_token(it.tokens.name, it.escapedName, { data: it }),
+        name_token(it.tokens.name, { data: it }),
         token(it.tokens.open),
         iterate(it.values, it),
         token(it.tokens.close),


Test patch:
diff --git a/test/syntax/baseline/dictionary-inherits.json b/test/syntax/baseline/dictionary-inherits.json
index 26f7ffa..bdfa324 100644
--- a/test/syntax/baseline/dictionary-inherits.json
+++ b/test/syntax/baseline/dictionary-inherits.json
@@ -3,7 +3,7 @@
         "type": "dictionary",
         "name": "PaintOptions",
         "escapedName": "PaintOptions",
-        "partial": null,
+        "inheritance": null,
         "members": [
             {
                 "type": "field",
@@ -107,21 +107,28 @@
                 }
             }
         ],
+        "extAttrs": null,
+        "partial": null,
         "trivia": {
             "base": "",
             "name": " ",
             "open": " ",
             "close": "\n",
             "termination": ""
-        },
-        "inheritance": null,
-        "extAttrs": null
+        }
     },
     {
         "type": "dictionary",
         "name": "WetPaintOptions",
         "escapedName": "WetPaintOptions",
-        "partial": null,
+        "inheritance": {
+            "name": "PaintOptions",
+            "escapedName": "PaintOptions",
+            "trivia": {
+                "colon": " ",
+                "name": " "
+            }
+        },
         "members": [
             {
                 "type": "field",
@@ -152,22 +159,15 @@
                 }
             }
         ],
+        "extAttrs": null,
+        "partial": null,
         "trivia": {
             "base": "\n\n",
             "name": " ",
             "open": " ",
             "close": "\n",
             "termination": ""
-        },
-        "inheritance": {
-            "name": "PaintOptions",
-            "escapedName": "PaintOptions",
-            "trivia": {
-                "colon": " ",
-                "name": " "
-            }
-        },
-        "extAttrs": null
+        }
     },
     {
         "type": "eof",
diff --git a/test/syntax/baseline/dictionary.json b/test/syntax/baseline/dictionary.json
index c23ea49..683fd6a 100644
--- a/test/syntax/baseline/dictionary.json
+++ b/test/syntax/baseline/dictionary.json
@@ -3,7 +3,7 @@
         "type": "dictionary",
         "name": "PaintOptions",
         "escapedName": "PaintOptions",
-        "partial": null,
+        "inheritance": null,
         "members": [
             {
                 "type": "field",
@@ -196,23 +196,20 @@
                 }
             }
         ],
+        "extAttrs": null,
+        "partial": null,
         "trivia": {
             "base": "// Extracted from Web IDL editors draft May 31 2011\n",
             "name": " ",
             "open": " ",
             "close": "\n",
             "termination": ""
-        },
-        "inheritance": null,
-        "extAttrs": null
+        }
     },
     {
         "type": "dictionary",
-        "name": "_A",
+        "name": "A",
         "escapedName": "_A",
-        "partial": {
-            "trivia": "\n\n"
-        },
         "members": [
             {
                 "type": "field",
@@ -271,14 +268,17 @@
                 }
             }
         ],
+        "extAttrs": null,
+        "partial": {
+            "trivia": "\n\n"
+        },
         "trivia": {
             "base": " ",
             "name": " ",
             "open": " ",
             "close": "\n",
             "termination": ""
-        },
-        "extAttrs": null
+        }
     },
     {
         "type": "eof",
diff --git a/test/writer.js b/test/writer.js
index eff93a6..a9baf5f 100644
--- a/test/writer.js
+++ b/test/writer.js
@@ -60,22 +60,27 @@ describe("Writer template functions", () => {
   });
 
   it("catches references", () => {
-    const result = rewrite("[Exposed=Window] interface Momo : Kudamono { attribute Promise<unsigned long> iro; };", {
-      reference: bracket
-    });
+    function rewriteReference(text) {
+      return rewrite(text, { reference: bracket });
+    }
+
+    const result = rewriteReference("[Exposed=Window] interface Momo : Kudamono { attribute Promise<unsigned long> iro; };");
     expect(result).toBe("[Exposed=<Window>] interface Momo : <Kudamono> { attribute <Promise><<unsigned long>> iro; };");
 
-    const includes = rewrite("A includes B;", {
-      reference: bracket
-    });
-    expect(includes).toBe("<A> includes <B>;");
+    const includes = rewriteReference("_A includes _B;");
+    expect(includes).toBe("<_A> includes <_B>;");
   });
 
   it("catches references as unescaped", () => {
-    const result = rewrite("[Exposed=Window] interface Momo : _Kudamono { attribute Promise<_Type> iro; attribute _Type sugar; };", {
-      reference: (_, unescaped) => bracket(unescaped)
-    });
+    function rewriteReference(text) {
+      return rewrite(text, { reference: (_, unescaped) => bracket(unescaped) });
+    }
+
+    const result = rewriteReference("[Exposed=Window] interface Momo : _Kudamono { attribute Promise<_Type> iro; attribute _Type sugar; };");
     expect(result).toBe("[Exposed=<Window>] interface Momo : <Kudamono> { attribute <Promise><<Type>> iro; attribute <Type> sugar; };");
+
+    const includes = rewriteReference("_A includes _B;");
+    expect(includes).toBe("<A> includes <B>;");
   });
 
   it("catches types", () => {