Repository: Khan/KaTeX
Pull request #1551 (2018-08-06 01:42:51)

Flatten a bunch of non-pervasive ParseNode types (part 1)
Towards #1492.
Also, this renames some of the `value` sub-fields to something more descriptive.

codecov[bot]: # [Codecov](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=h1) Report
> Merging [#1551](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=desc) into [master](https://codecov.io/gh/Khan/KaTeX/commit/b2d38104cd964ead9fc5eb4364146463bfd48ef6?src=pr&el=desc) will **decrease** coverage by `<.01%`.
> The diff coverage is `76.92%`.


[![Impacted file tree graph](https://codecov.io/gh/Khan/KaTeX/pull/1551/graphs/tree.svg?height=150&src=pr&token=vfDnj2P77o&width=650)](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master   #1551      +/-   ##
=========================================
- Coverage    84.1%   84.1%   -0.01%     
=========================================
  Files          79      79              
  Lines        4562    4555       -7     
  Branches      806     803       -3     
=========================================
- Hits         3837    3831       -6     
+ Misses        630     629       -1     
  Partials       95      95
```


| [Impacted Files](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/Parser.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL1BhcnNlci5qcw==) | `93.94% <ø> (ø)` | :arrow_up: |
| [src/functions/tag.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2Z1bmN0aW9ucy90YWcuanM=) | `100% <ø> (ø)` | :arrow_up: |
| [src/parseTree.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL3BhcnNlVHJlZS5qcw==) | `100% <ø> (ø)` | :arrow_up: |
| [src/parseNode.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL3BhcnNlTm9kZS5qcw==) | `84.21% <ø> (ø)` | :arrow_up: |
| [src/utils.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL3V0aWxzLmpz) | `66.66% <0%> (ø)` | :arrow_up: |
| [src/buildCommon.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2J1aWxkQ29tbW9uLmpz) | `91.09% <0%> (ø)` | :arrow_up: |
| [src/functions/cr.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2Z1bmN0aW9ucy9jci5qcw==) | `95.65% <100%> (ø)` | :arrow_up: |
| [src/functions/mclass.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2Z1bmN0aW9ucy9tY2xhc3MuanM=) | `100% <100%> (ø)` | :arrow_up: |
| [src/functions/font.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2Z1bmN0aW9ucy9mb250Lmpz) | `95.83% <100%> (ø)` | :arrow_up: |
| [src/buildHTML.js](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree#diff-c3JjL2J1aWxkSFRNTC5qcw==) | `97.69% <100%> (ø)` | :arrow_up: |
| ... and [5 more](https://codecov.io/gh/Khan/KaTeX/pull/1551/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=footer). Last update [b2d3810...ae68f65](https://codecov.io/gh/Khan/KaTeX/pull/1551?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/Parser.js b/src/Parser.js
index 5456ddf5..6d09b29a 100644
--- a/src/Parser.js
+++ b/src/Parser.js
@@ -942,10 +942,7 @@ export default class Parser {
             const urlArg = {
                 type: "url",
                 mode: this.mode,
-                value: {
-                    type: "url",
-                    value: url,
-                },
+                url,
             };
             this.consume();
             if (funcName === "\\href") {  // two arguments
@@ -983,11 +980,8 @@ export default class Parser {
             return newArgument({
                 type: "verb",
                 mode: "text",
-                value: {
-                    type: "verb",
-                    body: arg,
-                    star: star,
-                },
+                body: arg,
+                star,
             }, nucleus);
         }
         // At this point, we should have a symbol, possibly with accents.
diff --git a/src/buildCommon.js b/src/buildCommon.js
index f2b3e98a..410544ce 100644
--- a/src/buildCommon.js
+++ b/src/buildCommon.js
@@ -613,8 +613,8 @@ const makeVList = function(params: VListParam, options: Options): DomSpan {
 
 // Converts verb group into body string, dealing with \verb* form
 const makeVerb = function(group: ParseNode<"verb">, options: Options): string {
-    let text = group.value.body;
-    if (group.value.star) {
+    let text = group.body;
+    if (group.star) {
         text = text.replace(/ /g, '\u2423');  // Open Box
     } else {
         text = text.replace(/ /g, '\xA0');    // No-Break Space
diff --git a/src/buildHTML.js b/src/buildHTML.js
index 7757b110..c9ac1504 100644
--- a/src/buildHTML.js
+++ b/src/buildHTML.js
@@ -309,8 +309,8 @@ export default function buildHTML(tree: AnyParseNode[], options: Options): DomSp
     // Strip off outer tag wrapper for processing below.
     let tag = null;
     if (tree.length === 1 && tree[0].type === "tag") {
-        tag = tree[0].value.tag;
-        tree = tree[0].value.body;
+        tag = tree[0].tag;
+        tree = tree[0].body;
     }
 
     // Build the expression contained in the tree
diff --git a/src/environments/array.js b/src/environments/array.js
index 3bfa98e9..f1988851 100644
--- a/src/environments/array.js
+++ b/src/environments/array.js
@@ -139,7 +139,7 @@ function parseArray(
             if (!cr) {
                 throw new ParseError(`Failed to parse function after ${next}`);
             }
-            rowGaps.push(assertNodeType(cr, "cr").value.size);
+            rowGaps.push(assertNodeType(cr, "cr").size);
 
             // check for \hline(s) following the row separator
             hLinesBeforeRow.push(getHLines(parser));
@@ -563,12 +563,9 @@ defineEnvironment({
         return delimiters ? {
             type: "leftright",
             mode: context.mode,
-            value: {
-                type: "leftright",
-                body: [res],
-                left: delimiters[0],
-                right: delimiters[1],
-            },
+            body: [res],
+            left: delimiters[0],
+            right: delimiters[1],
         } : res;
     },
     htmlBuilder,
@@ -614,12 +611,9 @@ defineEnvironment({
         return {
             type: "leftright",
             mode: context.mode,
-            value: {
-                type: "leftright",
-                body: [res],
-                left: "\\{",
-                right: ".",
-            },
+            body: [res],
+            left: "\\{",
+            right: ".",
         };
     },
     htmlBuilder,
diff --git a/src/functions/cr.js b/src/functions/cr.js
index 7fa4d2d9..871be0a2 100644
--- a/src/functions/cr.js
+++ b/src/functions/cr.js
@@ -40,12 +40,9 @@ defineFunction({
         return {
             type: "cr",
             mode: parser.mode,
-            value: {
-                type: "cr",
-                newLine,
-                newRow,
-                size: size && assertNodeType(size, "size"),
-            },
+            newLine,
+            newRow,
+            size: size && assertNodeType(size, "size"),
         };
     },
 
@@ -53,16 +50,16 @@ defineFunction({
     // not within tabular/array environments.
 
     htmlBuilder: (group, options) => {
-        if (group.value.newRow) {
+        if (group.newRow) {
             throw new ParseError(
                 "\\cr valid only within a tabular/array environment");
         }
         const span = buildCommon.makeSpan(["mspace"], [], options);
-        if (group.value.newLine) {
+        if (group.newLine) {
             span.classes.push("newline");
-            if (group.value.size) {
+            if (group.size) {
                 span.style.marginTop =
-                    calculateSize(group.value.size.value.value, options) + "em";
+                    calculateSize(group.size.value.value, options) + "em";
             }
         }
         return span;
@@ -70,11 +67,11 @@ defineFunction({
 
     mathmlBuilder: (group, options) => {
         const node = new mathMLTree.MathNode("mspace");
-        if (group.value.newLine) {
+        if (group.newLine) {
             node.setAttribute("linebreak", "newline");
-            if (group.value.size) {
+            if (group.size) {
                 node.setAttribute("height",
-                    calculateSize(group.value.size.value.value, options) + "em");
+                    calculateSize(group.size.value.value, options) + "em");
             }
         }
         return node;
diff --git a/src/functions/delimsizing.js b/src/functions/delimsizing.js
index 4f5fdc94..f89f4023 100644
--- a/src/functions/delimsizing.js
+++ b/src/functions/delimsizing.js
@@ -12,7 +12,6 @@ import * as mml from "../buildMathML";
 
 import type Options from "../Options";
 import type {AnyParseNode, ParseNode, SymbolParseNode} from "../parseNode";
-import type {LeftRightDelimType} from "../parseNode";
 import type {FunctionContext} from "../defineFunction";
 
 // Extra data needed for the delimiter handler down below
@@ -52,7 +51,7 @@ const delimiters = [
     ".",
 ];
 
-type IsMiddle = {value: string, options: Options};
+type IsMiddle = {delim: string, options: Options};
 
 // Delimiter functions
 function checkDelimiter(
@@ -87,39 +86,33 @@ defineFunction({
         return {
             type: "delimsizing",
             mode: context.parser.mode,
-            value: {
-                type: "delimsizing",
-                size: delimiterSizes[context.funcName].size,
-                mclass: delimiterSizes[context.funcName].mclass,
-                value: delim.value,
-            },
+            size: delimiterSizes[context.funcName].size,
+            mclass: delimiterSizes[context.funcName].mclass,
+            delim: delim.value,
         };
     },
     htmlBuilder: (group, options) => {
-        const delim = group.value.value;
-
-        if (delim === ".") {
+        if (group.delim === ".") {
             // Empty delimiters still count as elements, even though they don't
             // show anything.
-            return buildCommon.makeSpan([group.value.mclass]);
+            return buildCommon.makeSpan([group.mclass]);
         }
 
         // Use delimiter.sizedDelim to generate the delimiter.
         return delimiter.sizedDelim(
-                delim, group.value.size, options, group.mode,
-                [group.value.mclass]);
+                group.delim, group.size, options, group.mode, [group.mclass]);
     },
     mathmlBuilder: (group) => {
         const children = [];
 
-        if (group.value.value !== ".") {
-            children.push(mml.makeText(group.value.value, group.mode));
+        if (group.delim !== ".") {
+            children.push(mml.makeText(group.delim, group.mode));
         }
 
         const node = new mathMLTree.MathNode("mo", children);
 
-        if (group.value.mclass === "mopen" ||
-            group.value.mclass === "mclose") {
+        if (group.mclass === "mopen" ||
+            group.mclass === "mclose") {
             // Only some of the delimsizing functions act as fences, and they
             // return "mopen" or "mclose" mclass.
             node.setAttribute("fence", "true");
@@ -134,11 +127,10 @@ defineFunction({
 });
 
 
-function leftRightGroupValue(group: ParseNode<"leftright">): LeftRightDelimType {
-    if (!group.value.body) {
+function assertParsed(group: ParseNode<"leftright">) {
+    if (!group.body) {
         throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
     }
-    return group.value;
 }
 
 
@@ -155,10 +147,7 @@ defineFunction({
         return {
             type: "leftright-right",
             mode: context.parser.mode,
-            value: {
-                type: "leftright-right",
-                value: checkDelimiter(args[0], context).value,
-            },
+            delim: checkDelimiter(args[0], context).value,
         };
     },
 });
@@ -188,18 +177,15 @@ defineFunction({
         return {
             type: "leftright",
             mode: parser.mode,
-            value: {
-                type: "leftright",
-                body: body,
-                left: delim.value,
-                right: assertNodeType(right, "leftright-right").value.value,
-            },
+            body,
+            left: delim.value,
+            right: assertNodeType(right, "leftright-right").delim,
         };
     },
     htmlBuilder: (group, options) => {
-        const groupValue = leftRightGroupValue(group);
+        assertParsed(group);
         // Build the inner expression
-        const inner = html.buildExpression(groupValue.body, options, true,
+        const inner = html.buildExpression(group.body, options, true,
             [null, "mclose"]);
 
         let innerHeight = 0;
@@ -226,14 +212,14 @@ defineFunction({
         innerDepth *= options.sizeMultiplier;
 
         let leftDelim;
-        if (groupValue.left === ".") {
+        if (group.left === ".") {
             // Empty delimiters in \left and \right make null delimiter spaces.
             leftDelim = html.makeNullDelimiter(options, ["mopen"]);
         } else {
             // Otherwise, use leftRightDelim to generate the correct sized
             // delimiter.
             leftDelim = delimiter.leftRightDelim(
-                groupValue.left, innerHeight, innerDepth, options,
+                group.left, innerHeight, innerDepth, options,
                 group.mode, ["mopen"]);
         }
         // Add it to the beginning of the expression
@@ -250,7 +236,7 @@ defineFunction({
                 if (isMiddle) {
                     // Apply the options that were active when \middle was called
                     inner[i] = delimiter.leftRightDelim(
-                        isMiddle.value, innerHeight, innerDepth,
+                        isMiddle.delim, innerHeight, innerDepth,
                         isMiddle.options, group.mode, []);
                 }
             }
@@ -258,11 +244,11 @@ defineFunction({
 
         let rightDelim;
         // Same for the right delimiter
-        if (groupValue.right === ".") {
+        if (group.right === ".") {
             rightDelim = html.makeNullDelimiter(options, ["mclose"]);
         } else {
             rightDelim = delimiter.leftRightDelim(
-                groupValue.right, innerHeight, innerDepth, options,
+                group.right, innerHeight, innerDepth, options,
                 group.mode, ["mclose"]);
         }
         // Add it to the end of the expression.
@@ -271,21 +257,21 @@ defineFunction({
         return buildCommon.makeSpan(["minner"], inner, options);
     },
     mathmlBuilder: (group, options) => {
-        const groupValue = leftRightGroupValue(group);
-        const inner = mml.buildExpression(groupValue.body, options);
+        assertParsed(group);
+        const inner = mml.buildExpression(group.body, options);
 
-        if (groupValue.left !== ".") {
+        if (group.left !== ".") {
             const leftNode = new mathMLTree.MathNode(
-                "mo", [mml.makeText(groupValue.left, group.mode)]);
+                "mo", [mml.makeText(group.left, group.mode)]);
 
             leftNode.setAttribute("fence", "true");
 
             inner.unshift(leftNode);
         }
 
-        if (groupValue.right !== ".") {
+        if (group.right !== ".") {
             const rightNode = new mathMLTree.MathNode(
-                "mo", [mml.makeText(groupValue.right, group.mode)]);
+                "mo", [mml.makeText(group.right, group.mode)]);
 
             rightNode.setAttribute("fence", "true");
 
@@ -311,22 +297,19 @@ defineFunction({
         return {
             type: "middle",
             mode: context.parser.mode,
-            value: {
-                type: "middle",
-                value: delim.value,
-            },
+            delim: delim.value,
         };
     },
     htmlBuilder: (group, options) => {
         let middleDelim;
-        if (group.value.value === ".") {
+        if (group.delim === ".") {
             middleDelim = html.makeNullDelimiter(options, []);
         } else {
             middleDelim = delimiter.sizedDelim(
-                group.value.value, 1, options,
+                group.delim, 1, options,
                 group.mode, []);
 
-            const isMiddle: IsMiddle = {value: group.value.value, options};
+            const isMiddle: IsMiddle = {delim: group.delim, options};
             // Property `isMiddle` not defined on `span`. It is only used in
             // this file above.
             // TODO: Fix this violation of the `span` type and possibly rename
@@ -338,7 +321,7 @@ defineFunction({
     },
     mathmlBuilder: (group, options) => {
         const middleNode = new mathMLTree.MathNode(
-            "mo", [mml.makeText(group.value.value, group.mode)]);
+            "mo", [mml.makeText(group.delim, group.mode)]);
         middleNode.setAttribute("fence", "true");
         return middleNode;
     },
diff --git a/src/functions/font.js b/src/functions/font.js
index 68d83245..6de010e4 100644
--- a/src/functions/font.js
+++ b/src/functions/font.js
@@ -7,17 +7,18 @@ import defineFunction from "../defineFunction";
 import * as html from "../buildHTML";
 import * as mml from "../buildMathML";
 
+import type {ParseNode} from "../parseNode";
 
-const htmlBuilder = (group, options) => {
-    const font = group.value.font;
+const htmlBuilder = (group: ParseNode<"font">, options) => {
+    const font = group.font;
     const newOptions = options.withFont(font);
-    return html.buildGroup(group.value.body, newOptions);
+    return html.buildGroup(group.body, newOptions);
 };
 
-const mathmlBuilder = (group, options) => {
-    const font = group.value.font;
+const mathmlBuilder = (group: ParseNode<"font">, options) => {
+    const font = group.font;
     const newOptions = options.withFont(font);
-    return mml.buildGroup(group.value.body, newOptions);
+    return mml.buildGroup(group.body, newOptions);
 };
 
 const fontAliases = {
@@ -53,11 +54,8 @@ defineFunction({
         return {
             type: "font",
             mode: parser.mode,
-            value: {
-                type: "font",
-                font: func.slice(1),
-                body,
-            },
+            font: func.slice(1),
+            body,
         };
     },
     htmlBuilder,
@@ -78,21 +76,15 @@ defineFunction({
         return {
             type: "mclass",
             mode: parser.mode,
-            value: {
-                type: "mclass",
-                mclass: binrelClass(body),
-                value: [
-                    {
-                        type: "font",
-                        mode: parser.mode,
-                        value: {
-                            type: "font",
-                            font: "boldsymbol",
-                            body,
-                        },
-                    },
-                ],
-            },
+            mclass: binrelClass(body),
+            body: [
+                {
+                    type: "font",
+                    mode: parser.mode,
+                    font: "boldsymbol",
+                    body,
+                },
+            ],
         };
     },
 });
@@ -114,14 +106,11 @@ defineFunction({
         return {
             type: "font",
             mode: mode,
-            value: {
-                type: "font",
-                font: style,
-                body: {
-                    type: "ordgroup",
-                    mode: parser.mode,
-                    value: body,
-                },
+            font: style,
+            body: {
+                type: "ordgroup",
+                mode: parser.mode,
+                value: body,
             },
         };
     },
diff --git a/src/functions/href.js b/src/functions/href.js
index 4fc74108..cf758e16 100644
--- a/src/functions/href.js
+++ b/src/functions/href.js
@@ -18,31 +18,21 @@ defineFunction({
     },
     handler: ({parser}, args) => {
         const body = args[1];
-        const href = assertNodeType(args[0], "url").value.value;
+        const href = assertNodeType(args[0], "url").url;
         return {
             type: "href",
             mode: parser.mode,
-            value: {
-                type: "href",
-                href: href,
-                body: ordargument(body),
-            },
+            href,
+            body: ordargument(body),
         };
     },
     htmlBuilder: (group, options) => {
-        const elements = html.buildExpression(
-            group.value.body,
-            options,
-            false
-        );
-
-        const href = group.value.href;
-
-        return new buildCommon.makeAnchor(href, [], elements, options);
+        const elements = html.buildExpression(group.body, options, false);
+        return new buildCommon.makeAnchor(group.href, [], elements, options);
     },
     mathmlBuilder: (group, options) => {
-        const math = mml.buildExpressionRow(group.value.body, options);
-        assertType(math, MathNode).setAttribute("href", group.value.href);
+        const math = mml.buildExpressionRow(group.body, options);
+        assertType(math, MathNode).setAttribute("href", group.href);
         return math;
     },
 });
@@ -56,7 +46,7 @@ defineFunction({
         allowedInText: true,
     },
     handler: ({parser}, args) => {
-        const href = assertNodeType(args[0], "url").value.value;
+        const href = assertNodeType(args[0], "url").url;
         const chars = [];
         for (let i = 0; i < href.length; i++) {
             let c = href[i];
@@ -81,11 +71,8 @@ defineFunction({
         return {
             type: "href",
             mode: parser.mode,
-            value: {
-                type: "href",
-                href: href,
-                body: ordargument(body),
-            },
+            href,
+            body: ordargument(body),
         };
     },
 });
diff --git a/src/functions/mathchoice.js b/src/functions/mathchoice.js
index 663325e8..4664d575 100644
--- a/src/functions/mathchoice.js
+++ b/src/functions/mathchoice.js
@@ -6,19 +6,16 @@ import Style from "../Style";
 import * as html from "../buildHTML";
 import * as mml from "../buildMathML";
 
-const chooseMathStyle = (group, options) => {
-    const style = options.style;
-    if (style.size === Style.DISPLAY.size) {
-        return group.value.display;
-    } else if  (style.size === Style.TEXT.size) {
-        return group.value.text;
-    } else if  (style.size === Style.SCRIPT.size) {
-        return group.value.script;
-    } else if  (style.size === Style.SCRIPTSCRIPT.size) {
-        return group.value.scriptscript;
-    }
-    return group.value.text;
+import type {ParseNode} from "../parseNode";
 
+const chooseMathStyle = (group: ParseNode<"mathchoice">, options) => {
+    switch (options.style.size) {
+        case Style.DISPLAY.size: return group.display;
+        case Style.TEXT.size: return group.text;
+        case Style.SCRIPT.size: return group.script;
+        case Style.SCRIPTSCRIPT.size: return group.scriptscript;
+        default: return group.text;
+    }
 };
 
 defineFunction({
@@ -31,13 +28,10 @@ defineFunction({
         return {
             type: "mathchoice",
             mode: parser.mode,
-            value: {
-                type: "mathchoice",
-                display:      ordargument(args[0]),
-                text:         ordargument(args[1]),
-                script:       ordargument(args[2]),
-                scriptscript: ordargument(args[3]),
-            },
+            display:      ordargument(args[0]),
+            text:         ordargument(args[1]),
+            script:       ordargument(args[2]),
+            scriptscript: ordargument(args[3]),
         };
     },
     htmlBuilder: (group, options) => {
diff --git a/src/functions/mclass.js b/src/functions/mclass.js
index 468a538b..04fe4436 100644
--- a/src/functions/mclass.js
+++ b/src/functions/mclass.js
@@ -7,15 +7,17 @@ import type {AnyParseNode} from "../parseNode";
 import * as html from "../buildHTML";
 import * as mml from "../buildMathML";
 
+import type {ParseNode} from "../parseNode";
+
 const makeSpan = buildCommon.makeSpan;
 
-function htmlBuilder(group, options) {
-    const elements = html.buildExpression(group.value.value, options, true);
-    return makeSpan([group.value.mclass], elements, options);
+function htmlBuilder(group: ParseNode<"mclass">, options) {
+    const elements = html.buildExpression(group.body, options, true);
+    return makeSpan([group.mclass], elements, options);
 }
 
-function mathmlBuilder(group, options) {
-    const inner = mml.buildExpression(group.value.value, options);
+function mathmlBuilder(group: ParseNode<"mclass">, options) {
+    const inner = mml.buildExpression(group.body, options);
     return mathMLTree.newDocumentFragment(inner);
 }
 
@@ -34,11 +36,8 @@ defineFunction({
         return {
             type: "mclass",
             mode: parser.mode,
-            value: {
-                type: "mclass",
-                mclass: "m" + funcName.substr(5),
-                value: ordargument(body),
-            },
+            mclass: "m" + funcName.substr(5),
+            body: ordargument(body),
         };
     },
     htmlBuilder,
@@ -70,11 +69,8 @@ defineFunction({
         return {
             type: "mclass",
             mode: parser.mode,
-            value: {
-                type: "mclass",
-                mclass: binrelClass(args[0]),
-                value: [args[1]],
-            },
+            mclass: binrelClass(args[0]),
+            body: [args[1]],
         };
     },
 });
@@ -119,11 +115,8 @@ defineFunction({
         return {
             type: "mclass",
             mode: parser.mode,
-            value: {
-                type: "mclass",
-                mclass: mclass,
-                value: [supsub],
-            },
+            mclass,
+            body: [supsub],
         };
     },
     htmlBuilder,
diff --git a/src/functions/phantom.js b/src/functions/phantom.js
index e43e469c..ad74f97b 100644
--- a/src/functions/phantom.js
+++ b/src/functions/phantom.js
@@ -18,15 +18,12 @@ defineFunction({
         return {
             type: "phantom",
             mode: parser.mode,
-            value: {
-                type: "phantom",
-                value: ordargument(body),
-            },
+            body: ordargument(body),
         };
     },
     htmlBuilder: (group, options) => {
         const elements = html.buildExpression(
-            group.value.value,
+            group.body,
             options.withPhantom(),
             false
         );
@@ -36,7 +33,7 @@ defineFunction({
         return new buildCommon.makeFragment(elements);
     },
     mathmlBuilder: (group, options) => {
-        const inner = mml.buildExpression(group.value.value, options);
+        const inner = mml.buildExpression(group.body, options);
         return new mathMLTree.MathNode("mphantom", inner);
     },
 });
@@ -53,16 +50,12 @@ defineFunction({
         return {
             type: "hphantom",
             mode: parser.mode,
-            value: {
-                type: "hphantom",
-                value: ordargument(body),
-                body: body,
-            },
+            body,
         };
     },
     htmlBuilder: (group, options) => {
         let node = buildCommon.makeSpan(
-            [], [html.buildGroup(group.value.body, options.withPhantom())]);
+            [], [html.buildGroup(group.body, options.withPhantom())]);
         node.height = 0;
         node.depth = 0;
         if (node.children) {
@@ -81,7 +74,7 @@ defineFunction({
         return node;
     },
     mathmlBuilder: (group, options) => {
-        const inner = mml.buildExpression(group.value.value, options);
+        const inner = mml.buildExpression(ordargument(group.body), options);
         const node = new mathMLTree.MathNode("mphantom", inner);
         node.setAttribute("height", "0px");
         return node;
@@ -100,23 +93,19 @@ defineFunction({
         return {
             type: "vphantom",
             mode: parser.mode,
-            value: {
-                type: "vphantom",
-                value: ordargument(body),
-                body: body,
-            },
+            body,
         };
     },
     htmlBuilder: (group, options) => {
         const inner = buildCommon.makeSpan(
             ["inner"],
-            [html.buildGroup(group.value.body, options.withPhantom())]);
+            [html.buildGroup(group.body, options.withPhantom())]);
         const fix = buildCommon.makeSpan(["fix"], []);
         return buildCommon.makeSpan(
             ["mord", "rlap"], [inner, fix], options);
     },
     mathmlBuilder: (group, options) => {
-        const inner = mml.buildExpression(group.value.value, options);
+        const inner = mml.buildExpression(ordargument(group.body), options);
         const node = new mathMLTree.MathNode("mphantom", inner);
         node.setAttribute("width", "0px");
         return node;
diff --git a/src/functions/tag.js b/src/functions/tag.js
index 238c5a59..65ad3e91 100644
--- a/src/functions/tag.js
+++ b/src/functions/tag.js
@@ -10,10 +10,10 @@ defineFunctionBuilders({
         const table = new mathMLTree.MathNode("mtable", [
             new mathMLTree.MathNode("mlabeledtr", [
                 new mathMLTree.MathNode("mtd", [
-                    mml.buildExpressionRow(group.value.tag, options),
+                    mml.buildExpressionRow(group.tag, options),
                 ]),
                 new mathMLTree.MathNode("mtd", [
-                    mml.buildExpressionRow(group.value.body, options),
+                    mml.buildExpressionRow(group.body, options),
                 ]),
             ]),
         ]);
diff --git a/src/parseNode.js b/src/parseNode.js
index ead63a70..4de68c11 100644
--- a/src/parseNode.js
+++ b/src/parseNode.js
@@ -10,13 +10,6 @@ import type {Measurement} from "./units";
 export type NodeType = $Keys<ParseNodeTypes>;
 export type ParseNode<TYPE: NodeType> = $ElementType<ParseNodeTypes, TYPE>;
 
-export type LeftRightDelimType = {|
-    type: "leftright",
-    body: AnyParseNode[],
-    left: string,
-    right: string,
-|};
-
 // ParseNode's corresponding to Symbol `Group`s in symbols.js.
 export type SymbolParseNode =
     ParseNode<"atom"> |
@@ -116,11 +109,8 @@ type ParseNodeTypes = {
         type: "tag",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "tag",
-            body: AnyParseNode[],
-            tag: AnyParseNode[],
-        |},
+        body: AnyParseNode[],
+        tag: AnyParseNode[],
     |},
     "text": {|
         type: "text",
@@ -136,20 +126,14 @@ type ParseNodeTypes = {
         type: "url",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "url",
-            value: string,
-        |},
+        url: string,
     |},
     "verb": {|
         type: "verb",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "verb",
-            body: string,
-            star: boolean,
-        |},
+        body: string,
+        star: boolean,
     |},
     // From symbol groups, constructed in Parser.js via `symbols` lookup.
     // (Some of these have "-token" suffix to distinguish them from existing
@@ -216,23 +200,17 @@ type ParseNodeTypes = {
         type: "cr",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "cr",
-            newRow: boolean,
-            newLine: boolean,
-            size: ?ParseNode<"size">,
-        |},
+        newRow: boolean,
+        newLine: boolean,
+        size: ?ParseNode<"size">,
     |},
     "delimsizing": {|
         type: "delimsizing",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "delimsizing",
-            size: 1 | 2 | 3 | 4,
-            mclass: "mopen" | "mclose" | "mrel" | "mord",
-            value: string,
-        |},
+        size: 1 | 2 | 3 | 4,
+        mclass: "mopen" | "mclose" | "mrel" | "mord",
+        delim: string,
     |},
     "enclose": {|
         type: "enclose",
@@ -260,11 +238,8 @@ type ParseNodeTypes = {
         type: "font",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "font",
-            font: string,
-            body: AnyParseNode,
-        |},
+        font: string,
+        body: AnyParseNode,
     |},
     "genfrac": {|
         type: "genfrac",
@@ -294,11 +269,8 @@ type ParseNodeTypes = {
         type: "href",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "href",
-            href: string,
-            body: AnyParseNode[],
-        |},
+        href: string,
+        body: AnyParseNode[],
     |},
     "htmlmathml": {|
         type: "htmlmathml",
@@ -344,57 +316,37 @@ type ParseNodeTypes = {
         type: "leftright",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: LeftRightDelimType,
+        body: AnyParseNode[],
+        left: string,
+        right: string,
     |},
     "leftright-right": {|
         type: "leftright-right",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "leftright-right",
-            value: string,
-        |},
+        delim: string,
     |},
     "mathchoice": {|
         type: "mathchoice",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "mathchoice",
-            display: AnyParseNode[],
-            text: AnyParseNode[],
-            script: AnyParseNode[],
-            scriptscript: AnyParseNode[],
-        |},
+        display: AnyParseNode[],
+        text: AnyParseNode[],
+        script: AnyParseNode[],
+        scriptscript: AnyParseNode[],
     |},
     "middle": {|
         type: "middle",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "middle",
-            value: string,
-        |},
+        delim: string,
     |},
     "mclass": {|
         type: "mclass",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "mclass",
-            mclass: string,
-            value: AnyParseNode[],
-        |},
-    |},
-    "mod": {|
-        type: "mod",
-        mode: Mode,
-        loc?: ?SourceLocation,
-        value: {|
-            type: "mod",
-            modType: string,
-            value: ?AnyParseNode[],
-        |},
+        mclass: string,
+        body: AnyParseNode[],
     |},
     "operatorname": {|
         type: "operatorname",
@@ -418,30 +370,19 @@ type ParseNodeTypes = {
         type: "phantom",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "phantom",
-            value: AnyParseNode[],
-        |},
+        body: AnyParseNode[],
     |},
     "hphantom": {|
         type: "hphantom",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "hphantom",
-            body: AnyParseNode,
-            value: AnyParseNode[],
-        |},
+        body: AnyParseNode,
     |},
     "vphantom": {|
         type: "vphantom",
         mode: Mode,
         loc?: ?SourceLocation,
-        value: {|
-            type: "vphantom",
-            body: AnyParseNode,
-            value: AnyParseNode[],
-        |},
+        body: AnyParseNode,
     |},
     "raisebox": {|
         type: "raisebox",
diff --git a/src/parseTree.js b/src/parseTree.js
index 625515ea..26270fba 100644
--- a/src/parseTree.js
+++ b/src/parseTree.js
@@ -32,11 +32,8 @@ const parseTree = function(toParse: string, settings: Settings): AnyParseNode[]
         tree = [{
             type: "tag",
             mode: "text",
-            value: {
-                type: "tag",
-                body: tree,
-                tag: parser.parse(),
-            },
+            body: tree,
+            tag: parser.parse(),
         }];
     }
 
diff --git a/src/utils.js b/src/utils.js
index 154138aa..c287a141 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -110,7 +110,7 @@ const getBaseElem = function(group: AnyParseNode): AnyParseNode {
             return group;
         }
     } else if (group.type === "font") {
-        return getBaseElem(group.value.body);
+        return getBaseElem(group.body);
     } else {
         return group;
     }


Test patch:
diff --git a/test/__snapshots__/katex-spec.js.snap b/test/__snapshots__/katex-spec.js.snap
index 93eb4bd3..605a69e2 100755
--- a/test/__snapshots__/katex-spec.js.snap
+++ b/test/__snapshots__/katex-spec.js.snap
@@ -751,22 +751,19 @@ exports[`An implicit group parser within optional groups should work with old fo
         "value": [
           {
             "type": "font",
-            "mode": "math",
-            "value": {
-              "type": "font",
-              "body": {
-                "type": "ordgroup",
-                "mode": "math",
-                "value": [
-                  {
-                    "type": "textord",
-                    "mode": "math",
-                    "value": "3"
-                  }
-                ]
-              },
-              "font": "mathtt"
-            }
+            "body": {
+              "type": "ordgroup",
+              "mode": "math",
+              "value": [
+                {
+                  "type": "textord",
+                  "mode": "math",
+                  "value": "3"
+                }
+              ]
+            },
+            "font": "mathtt",
+            "mode": "math"
           }
         ]
       }
diff --git a/test/katex-spec.js b/test/katex-spec.js
index b1f4b1dd..99ac5e40 100644
--- a/test/katex-spec.js
+++ b/test/katex-spec.js
@@ -95,7 +95,7 @@ describe("A rel parser", function() {
                 group = group.value.html[0];
             }
             if (group.type === "mclass") {
-                expect(group.value.mclass).toEqual("mrel");
+                expect(group.mclass).toEqual("mrel");
             } else {
                 expect(group.type).toEqual("atom");
                 expect(group.family).toEqual("rel");
@@ -895,16 +895,16 @@ describe("A delimiter sizing parser", function() {
         const leftParse = getParsed(normalDelim)[0];
         const rightParse = getParsed(bigDelim)[0];
 
-        expect(leftParse.value.mclass).toEqual("mopen");
-        expect(rightParse.value.mclass).toEqual("mclose");
+        expect(leftParse.mclass).toEqual("mopen");
+        expect(rightParse.mclass).toEqual("mclose");
     });
 
     it("should parse the correct size delimiter", function() {
         const smallParse = getParsed(normalDelim)[0];
         const bigParse = getParsed(bigDelim)[0];
 
-        expect(smallParse.value.size).toEqual(1);
-        expect(bigParse.value.size).toEqual(4);
+        expect(smallParse.size).toEqual(1);
+        expect(bigParse.size).toEqual(4);
     });
 });
 
@@ -1122,8 +1122,8 @@ describe("A left/right parser", function() {
         const parse = getParsed(normalLeftRight)[0];
 
         expect(parse.type).toEqual("leftright");
-        expect(parse.value.left).toEqual("(");
-        expect(parse.value.right).toEqual(")");
+        expect(parse.left).toEqual("(");
+        expect(parse.right).toEqual(")");
     });
 
     it("should error when it is mismatched", function() {
@@ -1476,37 +1476,37 @@ describe("A font parser", function() {
 
     it("should produce the correct fonts", function() {
         const mathbbParse = getParsed`\mathbb x`[0];
-        expect(mathbbParse.value.font).toEqual("mathbb");
-        expect(mathbbParse.value.type).toEqual("font");
+        expect(mathbbParse.font).toEqual("mathbb");
+        expect(mathbbParse.type).toEqual("font");
 
         const mathrmParse = getParsed`\mathrm x`[0];
-        expect(mathrmParse.value.font).toEqual("mathrm");
-        expect(mathrmParse.value.type).toEqual("font");
+        expect(mathrmParse.font).toEqual("mathrm");
+        expect(mathrmParse.type).toEqual("font");
 
         const mathitParse = getParsed`\mathit x`[0];
-        expect(mathitParse.value.font).toEqual("mathit");
-        expect(mathitParse.value.type).toEqual("font");
+        expect(mathitParse.font).toEqual("mathit");
+        expect(mathitParse.type).toEqual("font");
 
         const mathcalParse = getParsed`\mathcal C`[0];
-        expect(mathcalParse.value.font).toEqual("mathcal");
-        expect(mathcalParse.value.type).toEqual("font");
+        expect(mathcalParse.font).toEqual("mathcal");
+        expect(mathcalParse.type).toEqual("font");
 
         const mathfrakParse = getParsed`\mathfrak C`[0];
-        expect(mathfrakParse.value.font).toEqual("mathfrak");
-        expect(mathfrakParse.value.type).toEqual("font");
+        expect(mathfrakParse.font).toEqual("mathfrak");
+        expect(mathfrakParse.type).toEqual("font");
     });
 
     it("should parse nested font commands", function() {
         const nestedParse = getParsed`\mathbb{R \neq \mathrm{R}}`[0];
-        expect(nestedParse.value.font).toEqual("mathbb");
-        expect(nestedParse.value.type).toEqual("font");
+        expect(nestedParse.font).toEqual("mathbb");
+        expect(nestedParse.type).toEqual("font");
 
-        const bbBody = nestedParse.value.body.value;
+        const bbBody = nestedParse.body.value;
         expect(bbBody).toHaveLength(3);
         expect(bbBody[0].type).toEqual("mathord");
         expect(bbBody[2].type).toEqual("font");
-        expect(bbBody[2].value.font).toEqual("mathrm");
-        expect(bbBody[2].value.type).toEqual("font");
+        expect(bbBody[2].font).toEqual("mathrm");
+        expect(bbBody[2].type).toEqual("font");
     });
 
     it("should work with \\textcolor", function() {
@@ -1515,8 +1515,8 @@ describe("A font parser", function() {
         expect(colorMathbbParse.value.color).toEqual("blue");
         const body = colorMathbbParse.value.value;
         expect(body).toHaveLength(1);
-        expect(body[0].value.type).toEqual("font");
-        expect(body[0].value.font).toEqual("mathbb");
+        expect(body[0].type).toEqual("font");
+        expect(body[0].font).toEqual("mathbb");
     });
 
     it("should not parse a series of font commands", function() {
@@ -1525,13 +1525,13 @@ describe("A font parser", function() {
 
     it("should nest fonts correctly", function() {
         const bf = getParsed`\mathbf{a\mathrm{b}c}`[0];
-        expect(bf.value.type).toEqual("font");
-        expect(bf.value.font).toEqual("mathbf");
-        expect(bf.value.body.value).toHaveLength(3);
-        expect(bf.value.body.value[0].value).toEqual("a");
-        expect(bf.value.body.value[1].value.type).toEqual("font");
-        expect(bf.value.body.value[1].value.font).toEqual("mathrm");
-        expect(bf.value.body.value[2].value).toEqual("c");
+        expect(bf.type).toEqual("font");
+        expect(bf.font).toEqual("mathbf");
+        expect(bf.body.value).toHaveLength(3);
+        expect(bf.body.value[0].value).toEqual("a");
+        expect(bf.body.value[1].type).toEqual("font");
+        expect(bf.body.value[1].font).toEqual("mathrm");
+        expect(bf.body.value[2].value).toEqual("c");
     });
 
     it("should have the correct greediness", function() {
@@ -2292,7 +2292,7 @@ describe("A phantom parser", function() {
         const parse = getParsed`\phantom{x}`[0];
 
         expect(parse.type).toEqual("phantom");
-        expect(parse.value.value).toBeDefined();
+        expect(parse.body).toBeDefined();
     });
 });
 
@@ -2499,17 +2499,17 @@ describe("href and url commands", function() {
     it("should allow letters [#$%&~_^] without escaping", function() {
         const url = "http://example.org/~bar/#top?foo=$foo&bar=ba^r_boo%20baz";
         const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0];
-        expect(parsed1.value.href).toBe(url);
+        expect(parsed1.href).toBe(url);
         const parsed2 = getParsed(`\\url{${url}}`)[0];
-        expect(parsed2.value.href).toBe(url);
+        expect(parsed2.href).toBe(url);
     });
 
     it("should allow balanced braces in url", function() {
         const url = "http://example.org/{too}";
         const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0];
-        expect(parsed1.value.href).toBe(url);
+        expect(parsed1.href).toBe(url);
         const parsed2 = getParsed(`\\url{${url}}`)[0];
-        expect(parsed2.value.href).toBe(url);
+        expect(parsed2.href).toBe(url);
     });
 
     it("should not allow unbalanced brace(s) in url", function() {
@@ -2523,9 +2523,9 @@ describe("href and url commands", function() {
         const url = "http://example.org/~bar/#top?foo=$}foo{&bar=bar^r_boo%20baz";
         const input = url.replace(/([#$%&~_^{}])/g, '\\$1');
         const parsed1 = getParsed(`\\href{${input}}{\\alpha}`)[0];
-        expect(parsed1.value.href).toBe(url);
+        expect(parsed1.href).toBe(url);
         const parsed2 = getParsed(`\\url{${input}}`)[0];
-        expect(parsed2.value.href).toBe(url);
+        expect(parsed2.href).toBe(url);
     });
 
     it("should be marked up correctly", function() {