Bug 1635323 [wpt PR 23397] - Roll webidl2.js to master, a=testonly
authorStephen McGruer <smcgruer@chromium.org>
Wed, 13 May 2020 09:41:55 +0000
changeset 531083 ca623c83e0b3154a097c27afade95598208cde65
parent 531082 b0e0161b9de7300553a8ff1e8a33f715f3d0a12a
child 531084 4cf49df4ad06a0d2f461800ddebd84b636e3843e
push id37435
push userapavel@mozilla.com
push dateWed, 20 May 2020 15:28:23 +0000
treeherdermozilla-central@5415da14ec9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1635323, 23397, 23328
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1635323 [wpt PR 23397] - Roll webidl2.js to master, a=testonly Automatic update from web-platform-tests Roll webidl2.js to master (#23397) This updates webidl2.js to https://github.com/w3c/webidl2.js/commit/6107689a9241d2b37e6741b58fa23074623ff23b Fixes https://github.com/web-platform-tests/wpt/issues/23328 -- wpt-commits: f9709adf63adb8c316209e7826f67f3f9c2bf2f2 wpt-pr: 23397
testing/web-platform/tests/resources/webidl2/lib/webidl2.js
--- a/testing/web-platform/tests/resources/webidl2/lib/webidl2.js
+++ b/testing/web-platform/tests/resources/webidl2/lib/webidl2.js
@@ -104,39 +104,45 @@ return /******/ (function(modules) { // 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__["parse"]; });
 
 /* harmony import */ var _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30);
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "write", function() { return _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__["write"]; });
 
 /* harmony import */ var _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31);
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "validate", function() { return _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__["validate"]; });
 
+/* harmony import */ var _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2);
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__["WebIDLParseError"]; });
+
+
 
 
 
 
 
 /***/ }),
 /* 1 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return parse; });
 /* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(15);
 /* harmony import */ var _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16);
-/* harmony import */ var _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(10);
+/* harmony import */ var _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
 /* harmony import */ var _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17);
 /* harmony import */ var _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(18);
 /* harmony import */ var _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(19);
 /* harmony import */ var _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(25);
 /* harmony import */ var _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(26);
 /* harmony import */ var _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(28);
 /* harmony import */ var _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(29);
+/* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(4);
+
 
 
 
 
 
 
 
 
@@ -205,48 +211,56 @@ function parseByTokens(tokeniser, option
     const defs = [];
     while (true) {
       const ea = _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["ExtendedAttributes"].parse(tokeniser);
       const def = definition();
       if (!def) {
         if (ea.length) error("Stray extended attributes");
         break;
       }
-      def.extAttrs = ea;
+      Object(_productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__["autoParenter"])(def).extAttrs = ea;
       defs.push(def);
     }
     const eof = consume("eof");
     if (options.concrete) {
       defs.push(eof);
     }
     return defs;
   }
   const res = definitions();
   if (tokeniser.position < source.length) error("Unrecognised tokens");
   return res;
 }
 
+/**
+ * @param {string} str
+ * @param {object} [options]
+ * @param {*} [options.sourceName]
+ * @param {boolean} [options.concrete]
+ */
 function parse(str, options = {}) {
   const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__["Tokeniser"](str);
   if (typeof options.sourceName !== "undefined") {
     tokeniser.source.name = options.sourceName;
   }
   return parseByTokens(tokeniser, options);
 }
 
 
 /***/ }),
 /* 2 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "typeNameKeywords", function() { return typeNameKeywords; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringTypes", function() { return stringTypes; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argumentNameKeywords", function() { return argumentNameKeywords; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Tokeniser", function() { return Tokeniser; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return WebIDLParseError; });
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
 /* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 
 
 
 // These regular expressions use the sticky flag so they will only match at
 // the current location (ie. the offset of lastIndex).
 const tokenRe = {
@@ -256,16 +270,33 @@ const tokenRe = {
   "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y,
   "identifier": /[_-]?[A-Za-z][0-9A-Z_a-z-]*/y,
   "string": /"[^"]*"/y,
   "whitespace": /[\t\n\r ]+/y,
   "comment": /((\/(\/.*|\*([^*]|\*[^/])*\*\/)[\t\n\r ]*)+)/y,
   "other": /[^\t\n\r 0-9A-Za-z]/y
 };
 
+const typeNameKeywords = [
+  "ArrayBuffer",
+  "DataView",
+  "Int8Array",
+  "Int16Array",
+  "Int32Array",
+  "Uint8Array",
+  "Uint16Array",
+  "Uint32Array",
+  "Uint8ClampedArray",
+  "Float32Array",
+  "Float64Array",
+  "any",
+  "object",
+  "symbol"
+];
+
 const stringTypes = [
   "ByteString",
   "DOMString",
   "USVString"
 ];
 
 const argumentNameKeywords = [
   "async",
@@ -294,37 +325,35 @@ const argumentNameKeywords = [
 ];
 
 const nonRegexTerminals = [
   "-Infinity",
   "FrozenArray",
   "Infinity",
   "NaN",
   "Promise",
-  "async",
   "boolean",
   "byte",
-  "constructor",
   "double",
   "false",
   "float",
   "long",
   "mixin",
   "null",
   "octet",
   "optional",
   "or",
   "readonly",
   "record",
   "sequence",
   "short",
   "true",
   "unsigned",
   "void"
-].concat(argumentNameKeywords, stringTypes);
+].concat(argumentNameKeywords, stringTypes, typeNameKeywords);
 
 const punctuations = [
   "(",
   ")",
   ",",
   "...",
   ":",
   ";",
@@ -341,16 +370,17 @@ const punctuations = [
 const reserved = [
   // "constructor" is now a keyword
   "_constructor",
   "toString",
   "_toString",
 ];
 
 /**
+ * @typedef {ArrayItemType<ReturnType<typeof tokenise>>} Token
  * @param {string} str
  */
 function tokenise(str) {
   const tokens = [];
   let lastCharIndex = 0;
   let trivia = "";
   let line = 1;
   let index = 0;
@@ -417,18 +447,18 @@ function tokenise(str) {
     type: "eof",
     value: "",
     trivia
   });
 
   return tokens;
 
   /**
-   * @param {keyof tokenRe} type
-   * @param {object} [options]
+   * @param {keyof typeof tokenRe} type
+   * @param {object} options
    * @param {boolean} [options.noFlushTrivia]
    */
   function attemptTokenMatch(type, { noFlushTrivia } = {}) {
     const re = tokenRe[type];
     re.lastIndex = lastCharIndex;
     const result = re.exec(str);
     if (result) {
       tokens.push({ type, value: result[0], trivia, line, index });
@@ -447,16 +477,17 @@ class Tokeniser {
    */
   constructor(idl) {
     this.source = tokenise(idl);
     this.position = 0;
   }
 
   /**
    * @param {string} message
+   * @return {never}
    */
   error(message) {
     throw new WebIDLParseError(Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["syntaxError"])(this.source, this.position, this.current, message));
   }
 
   /**
    * @param {string} type
    */
@@ -480,16 +511,26 @@ class Tokeniser {
    * @param {number} position
    */
   unconsume(position) {
     this.position = position;
   }
 }
 
 class WebIDLParseError extends Error {
+  /**
+   * @param {object} options
+   * @param {string} options.message
+   * @param {string} options.bareMessage
+   * @param {string} options.context
+   * @param {number} options.line
+   * @param {*} options.sourceName
+   * @param {string} options.input
+   * @param {*[]} options.tokens
+   */
   constructor({ message, bareMessage, context, line, sourceName, input, tokens }) {
     super(message);
 
     this.name = "WebIDLParseError"; // not to be mangled
     this.bareMessage = bareMessage;
     this.context = context;
     this.line = line;
     this.sourceName = sourceName;
@@ -512,18 +553,18 @@ class WebIDLParseError extends Error {
  */
 function lastLine(text) {
   const splitted = text.split("\n");
   return splitted[splitted.length - 1];
 }
 
 /**
  * @typedef {object} WebIDL2ErrorOptions
- * @property {"error" | "warning"} level
- * @property {Function} autofix
+ * @property {"error" | "warning"} [level]
+ * @property {Function} [autofix]
  *
  * @param {string} message error message
  * @param {"Syntax" | "Validation"} kind error type
  * @param {WebIDL2ErrorOptions} [options]
  */
 function error(source, position, current, message, kind, { level = "error", autofix, ruleName } = {}) {
   /**
    * @param {number} count
@@ -604,33 +645,32 @@ function validationError(token, current,
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unescape", function() { return unescape; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "list", function() { return list; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_value", function() { return const_value; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_data", function() { return const_data; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "primitive_type", function() { return primitive_type; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "identifiers", function() { return identifiers; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argument_list", function() { return argument_list; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "type_with_extended_attributes", function() { return type_with_extended_attributes; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "return_type", function() { return return_type; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringifier", function() { return stringifier; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getLastIndentation", function() { return getLastIndentation; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getMemberIndentation", function() { return getMemberIndentation; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autofixAddExposedWindow", function() { return autofixAddExposedWindow; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getFirstToken", function() { return getFirstToken; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findLastIndex", function() { return findLastIndex; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autoParenter", function() { return autoParenter; });
 /* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5);
-/* harmony import */ var _argument_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
-/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(12);
-/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(10);
-/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(13);
-/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(14);
-/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(2);
-
+/* harmony import */ var _argument_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8);
+/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13);
+/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(14);
+/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2);
 
 
 
 
 
 
 
 /**
@@ -734,39 +774,28 @@ function primitive_type(tokeniser) {
   if (base) {
     return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { base } });
   }
 }
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  */
-function identifiers(tokeniser) {
-  const ids = list(tokeniser, { parser: _token_js__WEBPACK_IMPORTED_MODULE_2__["Token"].parser(tokeniser, "identifier"), listName: "identifier list" });
-  if (!ids.length) {
-    tokeniser.error("Expected identifiers but none found");
-  }
-  return ids;
-}
-
-/**
- * @param {import("../tokeniser").Tokeniser} tokeniser
- */
 function argument_list(tokeniser) {
   return list(tokeniser, { parser: _argument_js__WEBPACK_IMPORTED_MODULE_1__["Argument"].parse, listName: "arguments list" });
 }
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  * @param {string} typeName
  */
 function type_with_extended_attributes(tokeniser, typeName) {
-  const extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["ExtendedAttributes"].parse(tokeniser);
+  const extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser);
   const ret = _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"].parse(tokeniser, typeName);
-  if (ret) ret.extAttrs = extAttrs;
+  if (ret) autoParenter(ret).extAttrs = extAttrs;
   return ret;
 }
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  * @param {string} typeName
  */
 function return_type(tokeniser, typeName) {
@@ -783,18 +812,18 @@ function return_type(tokeniser, typeName
 }
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  */
 function stringifier(tokeniser) {
   const special = tokeniser.consume("stringifier");
   if (!special) return;
-  const member = _attribute_js__WEBPACK_IMPORTED_MODULE_5__["Attribute"].parse(tokeniser, { special }) ||
-    _operation_js__WEBPACK_IMPORTED_MODULE_4__["Operation"].parse(tokeniser, { special }) ||
+  const member = _attribute_js__WEBPACK_IMPORTED_MODULE_4__["Attribute"].parse(tokeniser, { special }) ||
+    _operation_js__WEBPACK_IMPORTED_MODULE_3__["Operation"].parse(tokeniser, { special }) ||
     tokeniser.error("Unterminated stringifier");
   return member;
 }
 
 /**
  * @param {string} str
  */
 function getLastIndentation(str) {
@@ -820,77 +849,139 @@ function getMemberIndentation(parentTriv
 
 /**
  * @param {object} def
  * @param {import("./extended-attributes.js").ExtendedAttributes} def.extAttrs
  */
 function autofixAddExposedWindow(def) {
   return () => {
     if (def.extAttrs.length){
-      const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_6__["Tokeniser"]("Exposed=Window,");
-      const exposed = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["SimpleExtendedAttribute"].parse(tokeniser);
+      const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("Exposed=Window,");
+      const exposed = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["SimpleExtendedAttribute"].parse(tokeniser);
       exposed.tokens.separator = tokeniser.consume(",");
       const existing = def.extAttrs[0];
       if (!/^\s/.test(existing.tokens.name.trivia)) {
         existing.tokens.name.trivia = ` ${existing.tokens.name.trivia}`;
       }
       def.extAttrs.unshift(exposed);
     } else {
-      def.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["ExtendedAttributes"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_6__["Tokeniser"]("[Exposed=Window]"));
+      autoParenter(def).extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("[Exposed=Window]"));
       const trivia = def.tokens.base.trivia;
       def.extAttrs.tokens.open.trivia = trivia;
       def.tokens.base.trivia = `\n${getLastIndentation(trivia)}`;
     }
   };
 }
 
 /**
  * Get the first syntax token for the given IDL object.
  * @param {*} data
  */
 function getFirstToken(data) {
   if (data.extAttrs.length) {
     return data.extAttrs.tokens.open;
   }
-  if (data.type === "operation") {
+  if (data.type === "operation" && !data.special) {
     return getFirstToken(data.idlType);
   }
   const tokens = Object.values(data.tokens).sort((x, y) => x.index - y.index);
   return tokens[0];
 }
 
+/**
+ * @template T
+ * @param {T[]} array
+ * @param {(item: T) => boolean} predicate
+ */
+function findLastIndex(array, predicate) {
+  const index = array.slice().reverse().findIndex(predicate);
+  if (index === -1) {
+    return index;
+  }
+  return array.length - index - 1;
+}
+
+/**
+ * Returns a proxy that auto-assign `parent` field.
+ * @template T
+ * @param {T} data
+ * @param {*} [parent] The object that will be assigned to `parent`.
+ *                     If absent, it will be `data` by default.
+ * @return {T}
+ */
+function autoParenter(data, parent) {
+  if (!parent) {
+    // Defaults to `data` unless specified otherwise.
+    parent = data;
+  }
+  if (!data) {
+    // This allows `autoParenter(undefined)` which again allows
+    // `autoParenter(parse())` where the function may return nothing.
+    return data;
+  }
+  return new Proxy(data, {
+    get(target, p) {
+      const value = target[p];
+      if (Array.isArray(value)) {
+        // Wraps the array so that any added items will also automatically
+        // get their `parent` values.
+        return autoParenter(value, target);
+      }
+      return value;
+    },
+    set(target, p, value) {
+      target[p] = value;
+      if (!value) {
+        return true;
+      } else if (Array.isArray(value)) {
+        // Assigning an array will add `parent` to its items.
+        for (const item of value) {
+          if (typeof item.parent !== "undefined") {
+            item.parent = parent;
+          }
+        }
+      } else if (typeof value.parent !== "undefined") {
+        value.parent = parent;
+      }
+      return true;
+    }
+  });
+}
+
 
 /***/ }),
 /* 5 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Type", function() { return Type; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2);
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3);
 /* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8);
+
 
 
 
 
 
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  * @param {string} typeName
  */
 function generic_type(tokeniser, typeName) {
   const base = tokeniser.consume("FrozenArray", "Promise", "sequence", "record");
   if (!base) {
     return;
   }
-  const ret = new Type({ source: tokeniser.source, tokens: { base } });
+  const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens: { base } }));
   ret.tokens.open = tokeniser.consume("<") || tokeniser.error(`No opening bracket after ${base.type}`);
   switch (base.type) {
     case "Promise": {
       if (tokeniser.probe("[")) tokeniser.error("Promise type cannot have extended attribute");
       const subtype = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser, typeName) || tokeniser.error("Missing Promise subtype");
       ret.subtype.push(subtype);
       break;
     }
@@ -908,17 +999,17 @@ function generic_type(tokeniser, typeNam
       keyIdlType.type = typeName;
       const valueType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, typeName) || tokeniser.error("Error parsing generic type record");
       ret.subtype.push(keyIdlType, valueType);
       break;
     }
   }
   if (!ret.idlType) tokeniser.error(`Error parsing generic type ${base.type}`);
   ret.tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing closing bracket after ${base.type}`);
-  return ret;
+  return ret.this;
 }
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  */
 function type_suffix(tokeniser, obj) {
   const nullable = tokeniser.consume("?");
   if (nullable) {
@@ -929,17 +1020,17 @@ function type_suffix(tokeniser, obj) {
 
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  * @param {string} typeName
  */
 function single_type(tokeniser, typeName) {
   let ret = generic_type(tokeniser, typeName) || Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["primitive_type"])(tokeniser);
   if (!ret) {
-    const base = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"]);
+    const base = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"], ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["typeNameKeywords"]);
     if (!base) {
       return;
     }
     ret = new Type({ source: tokeniser.source, tokens: { base } });
     if (tokeniser.probe("<")) tokeniser.error(`Unsupported generic type ${base.value}`);
   }
   if (ret.generic === "Promise" && tokeniser.probe("?")) {
     tokeniser.error("Promise type cannot be nullable");
@@ -953,49 +1044,50 @@ function single_type(tokeniser, typeName
 /**
  * @param {import("../tokeniser").Tokeniser} tokeniser
  * @param {string} type
  */
 function union_type(tokeniser, type) {
   const tokens = {};
   tokens.open = tokeniser.consume("(");
   if (!tokens.open) return;
-  const ret = new Type({ source: tokeniser.source, tokens });
+  const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens }));
   ret.type = type || null;
   while (true) {
     const typ = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error("No type after open parenthesis or 'or' in union type");
     if (typ.idlType === "any") tokeniser.error("Type `any` cannot be included in a union type");
+    if (typ.generic === "Promise") tokeniser.error("Type `Promise` cannot be included in a union type");
     ret.subtype.push(typ);
     const or = tokeniser.consume("or");
     if (or) {
       typ.tokens.separator = or;
     }
     else break;
   }
   if (ret.idlType.length < 2) {
     tokeniser.error("At least two types are expected in a union type but found less");
   }
   tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated union type");
   type_suffix(tokeniser, ret);
-  return ret;
+  return ret.this;
 }
 
 class Type extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    * @param {string} typeName
    */
   static parse(tokeniser, typeName) {
     return single_type(tokeniser, typeName) || union_type(tokeniser, typeName);
   }
 
   constructor({ source, tokens }) {
     super({ source, tokens });
-    Object.defineProperty(this, "subtype", { value: [] });
-    this.extAttrs = [];
+    Object.defineProperty(this, "subtype", { value: [], writable: true });
+    this.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__["ExtendedAttributes"]({});
   }
 
   get generic() {
     if (this.subtype.length && this.tokens.base) {
       return this.tokens.base.value;
     }
     return "";
   }
@@ -1014,28 +1106,29 @@ class Type extends _base_js__WEBPACK_IMP
       this.tokens.prefix,
       this.tokens.base,
       this.tokens.postfix
     ].filter(t => t).map(t => t.value).join(" ");
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(name);
   }
 
   *validate(defs) {
+    yield* this.extAttrs.validate(defs);
     /*
      * If a union is nullable, its subunions cannot include a dictionary
      * If not, subunions may include dictionaries if each union is not nullable
      */
     const typedef = !this.union && defs.unique.get(this.idlType);
     const target =
       this.union ? this :
       (typedef && typedef.type === "typedef") ? typedef.idlType :
       undefined;
     if (target && this.nullable) {
       // do not allow any dictionary
-      const reference = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__["idlTypeIncludesDictionary"])(target, defs);
+      const { reference } = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__["idlTypeIncludesDictionary"])(target, defs) || {};
       if (reference) {
         const targetToken = (this.union ? reference : this).tokens.base;
         const message = `Nullable union cannot include a dictionary type`;
         yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(targetToken, this, "no-nullable-union-dict", message);
       }
     } else {
       // allow some dictionary
       for (const subtype of this.subtype) {
@@ -1048,31 +1141,41 @@ class Type extends _base_js__WEBPACK_IMP
 
 /***/ }),
 /* 6 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Base", function() { return Base; });
+// @ts-check
+
 class Base {
+  /**
+   * @param {object} initializer
+   * @param {Base["source"]} initializer.source
+   * @param {Base["tokens"]} initializer.tokens
+   */
   constructor({ source, tokens }) {
     Object.defineProperties(this, {
       source: { value: source },
-      tokens: { value: tokens }
+      tokens: { value: tokens, writable: true },
+      parent: { value: null, writable: true },
+      this: { value: this } // useful when escaping from proxy
     });
   }
 
   toJSON() {
     const json = { type: undefined, name: undefined, inheritance: undefined };
     let proto = this;
     while (proto !== Object.prototype) {
       const descMap = Object.getOwnPropertyDescriptors(proto);
       for (const [key, value] of Object.entries(descMap)) {
         if (value.enumerable || value.get) {
+          // @ts-ignore - allow indexing here
           json[key] = this[key];
         }
       }
       proto = Object.getPrototypeOf(proto);
     }
     return json;
   }
 }
@@ -1080,147 +1183,490 @@ class Base {
 
 /***/ }),
 /* 7 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "idlTypeIncludesDictionary", function() { return idlTypeIncludesDictionary; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dictionaryIncludesRequiredField", function() { return dictionaryIncludesRequiredField; });
+// @ts-check
+
 /**
+ * @typedef {import("../productions/dictionary.js").Dictionary} Dictionary
+ *
  * @param {*} idlType
- * @param {*[]} defs
+ * @param {import("../validator.js").Definitions} defs
  * @param {object} [options]
  * @param {boolean} [options.useNullableInner] use when the input idlType is nullable and you want to use its inner type
- * @return the type reference that ultimately includes dictionary.
+ * @return {{ reference: *, dictionary: Dictionary }} the type reference that ultimately includes dictionary.
  */
 function idlTypeIncludesDictionary(idlType, defs, { useNullableInner } = {}) {
   if (!idlType.union) {
     const def = defs.unique.get(idlType.idlType);
     if (!def) {
       return;
     }
     if (def.type === "typedef") {
-      const { typedefIncludesDictionary} = defs.cache;
+      const { typedefIncludesDictionary } = defs.cache;
       if (typedefIncludesDictionary.has(def)) {
         // Note that this also halts when it met indeterminate state
         // to prevent infinite recursion
         return typedefIncludesDictionary.get(def);
       }
       defs.cache.typedefIncludesDictionary.set(def, undefined); // indeterminate state
       const result = idlTypeIncludesDictionary(def.idlType, defs);
       defs.cache.typedefIncludesDictionary.set(def, result);
       if (result) {
-        return idlType;
+        return {
+          reference: idlType,
+          dictionary: result.dictionary
+        };
       }
     }
     if (def.type === "dictionary" && (useNullableInner || !idlType.nullable)) {
-      return idlType;
+      return {
+        reference: idlType,
+        dictionary: def
+      };
     }
   }
   for (const subtype of idlType.subtype) {
     const result = idlTypeIncludesDictionary(subtype, defs);
     if (result) {
       if (subtype.union) {
         return result;
       }
-      return subtype;
+      return {
+        reference: subtype,
+        dictionary: result.dictionary
+      };
     }
   }
 }
 
+/**
+ * @param {*} dict dictionary type
+ * @param {import("../validator.js").Definitions} defs
+ * @return {boolean}
+ */
+function dictionaryIncludesRequiredField(dict, defs) {
+  if (defs.cache.dictionaryIncludesRequiredField.has(dict)) {
+    return defs.cache.dictionaryIncludesRequiredField.get(dict);
+  }
+  defs.cache.dictionaryIncludesRequiredField.set(dict, undefined); // indeterminate
+  if (dict.inheritance) {
+    const superdict = defs.unique.get(dict.inheritance);
+    if (!superdict) {
+      return true;
+    }
+    if (dictionaryIncludesRequiredField(superdict, defs)) {
+      return true;
+    }
+  }
+  const result = dict.members.some(field => field.required);
+  defs.cache.dictionaryIncludesRequiredField.set(dict, result);
+  return result;
+}
+
 
 /***/ }),
 /* 8 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleExtendedAttribute", function() { return SimpleExtendedAttribute; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExtendedAttributes", function() { return ExtendedAttributes; });
+/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
+/* harmony import */ var _array_base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
+/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
+/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
+/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3);
+
+
+
+
+
+
+/**
+ * @param {import("../tokeniser").Tokeniser} tokeniser
+ * @param {string} tokenName
+ */
+function tokens(tokeniser, tokenName) {
+  return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, {
+    parser: _token_js__WEBPACK_IMPORTED_MODULE_2__["Token"].parser(tokeniser, tokenName),
+    listName: tokenName + " list"
+  });
+}
+
+const extAttrValueSyntax = ["identifier", "decimal", "integer", "string"];
+
+const shouldBeLegacyPrefixed = [
+  "NoInterfaceObject",
+  "LenientSetter",
+  "LenientThis",
+  "TreatNonObjectAsNull",
+  "Unforgeable",
+];
+
+const renamedLegacies = new Map([
+  ...shouldBeLegacyPrefixed.map(name => [name, `Legacy${name}`]),
+  ["NamedConstructor", "LegacyFactoryFunction"],
+  ["OverrideBuiltins", "LegacyOverrideBuiltIns"],
+  ["TreatNullAs", "LegacyNullToEmptyString"],
+]);
+
+/**
+ * This will allow a set of extended attribute values to be parsed.
+ * @param {import("../tokeniser").Tokeniser} tokeniser
+ */
+function extAttrListItems(tokeniser) {
+  for (const syntax of extAttrValueSyntax) {
+    const toks = tokens(tokeniser, syntax);
+    if (toks.length) {
+      return toks;
+    }
+  }
+  tokeniser.error(`Expected identifiers, strings, decimals, or integers but none found`);
+}
+
+
+class ExtendedAttributeParameters extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
+  /**
+   * @param {import("../tokeniser").Tokeniser} tokeniser
+   */
+  static parse(tokeniser) {
+    const tokens = { assign: tokeniser.consume("=") };
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new ExtendedAttributeParameters({ source: tokeniser.source, tokens }));
+    if (tokens.assign) {
+      tokens.secondaryName = tokeniser.consume(...extAttrValueSyntax);
+    }
+    tokens.open = tokeniser.consume("(");
+    if (tokens.open) {
+      ret.list = ret.rhsIsList ?
+        // [Exposed=(Window,Worker)]
+        extAttrListItems(tokeniser) :
+        // [LegacyFactoryFunction=Audio(DOMString src)] or [Constructor(DOMString str)]
+        Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["argument_list"])(tokeniser);
+      tokens.close = tokeniser.consume(")") || tokeniser.error("Unexpected token in extended attribute argument list");
+    } else if (ret.hasRhs && !tokens.secondaryName) {
+      tokeniser.error("No right hand side to extended attribute assignment");
+    }
+    return ret.this;
+  }
+
+  get rhsIsList() {
+    return this.tokens.assign && !this.tokens.secondaryName;
+  }
+
+  get rhsType() {
+    if (this.rhsIsList) {
+      return this.list[0].tokens.value.type + "-list";
+    }
+    if (this.tokens.secondaryName) {
+      return this.tokens.secondaryName.type;
+    }
+    return null;
+  }
+}
+
+class SimpleExtendedAttribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
+  /**
+   * @param {import("../tokeniser").Tokeniser} tokeniser
+   */
+  static parse(tokeniser) {
+    const name = tokeniser.consume("identifier");
+    if (name) {
+      return new SimpleExtendedAttribute({
+        source: tokeniser.source,
+        tokens: { name },
+        params: ExtendedAttributeParameters.parse(tokeniser)
+      });
+    }
+  }
+
+  constructor({ source, tokens, params }) {
+    super({ source, tokens });
+    params.parent = this;
+    Object.defineProperty(this, "params", { value: params });
+  }
+
+  get type() {
+    return "extended-attribute";
+  }
+  get name() {
+    return this.tokens.name.value;
+  }
+  get rhs() {
+    const { rhsType: type, tokens, list } = this.params;
+    if (!type) {
+      return null;
+    }
+    const value = this.params.rhsIsList ? list : Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(tokens.secondaryName.value);
+    return { type, value };
+  }
+  get arguments() {
+    const { rhsIsList, list } = this.params;
+    if (!list || rhsIsList) {
+      return [];
+    }
+    return list;
+  }
+
+  *validate(defs) {
+    const { name } = this;
+    if (name === "LegacyNoInterfaceObject") {
+      const message = `\`[LegacyNoInterfaceObject]\` extended attribute is an \
+undesirable feature that may be removed from Web IDL in the future. Refer to the \
+[relevant upstream PR](https://github.com/heycam/webidl/pull/609) for more \
+information.`;
+      yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "no-nointerfaceobject", message, { level: "warning" });
+    } else if (renamedLegacies.has(name)) {
+      const message = `\`[${name}]\` extended attribute is a legacy feature \
+that is now renamed to \`[${renamedLegacies.get(name)}]\`. Refer to the \
+[relevant upstream PR](https://github.com/heycam/webidl/pull/870) for more \
+information.`;
+      yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "renamed-legacy", message, {
+        level: "warning",
+        autofix: renameLegacyExtendedAttribute(this)
+      });
+    }
+    for (const arg of this.arguments) {
+      yield* arg.validate(defs);
+    }
+  }
+}
+
+/**
+ * @param {SimpleExtendedAttribute} extAttr
+ */
+function renameLegacyExtendedAttribute(extAttr) {
+  return () => {
+    const { name } = extAttr;
+    extAttr.tokens.name.value = renamedLegacies.get(name);
+    if (name === "TreatNullAs") {
+      extAttr.params.tokens = {};
+    }
+  };
+}
+
+// Note: we parse something simpler than the official syntax. It's all that ever
+// seems to be used
+class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__["ArrayBase"] {
+  /**
+   * @param {import("../tokeniser").Tokeniser} tokeniser
+   */
+  static parse(tokeniser) {
+    const tokens = {};
+    tokens.open = tokeniser.consume("[");
+    if (!tokens.open) return new ExtendedAttributes({});
+    const ret = new ExtendedAttributes({ source: tokeniser.source, tokens });
+    ret.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, {
+      parser: SimpleExtendedAttribute.parse,
+      listName: "extended attribute"
+    }));
+    tokens.close = tokeniser.consume("]") || tokeniser.error("Unexpected closing token of extended attribute");
+    if (!ret.length) {
+      tokeniser.error("Found an empty extended attribute");
+    }
+    if (tokeniser.probe("[")) {
+      tokeniser.error("Illegal double extended attribute lists, consider merging them");
+    }
+    return ret;
+  }
+
+  *validate(defs) {
+    for (const extAttr of this) {
+      yield* extAttr.validate(defs);
+    }
+  }
+}
+
+
+/***/ }),
+/* 9 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayBase", function() { return ArrayBase; });
+// @ts-check
+
+class ArrayBase extends Array {
+  constructor({ source, tokens }) {
+    super();
+    Object.defineProperties(this, {
+      source: { value: source },
+      tokens: { value: tokens },
+      parent: { value: null, writable: true }
+    });
+  }
+}
+
+
+/***/ }),
+/* 10 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Token", function() { return Token; });
+/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
+/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+// @ts-check
+
+
+
+
+class Token extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
+  /**
+   * @param {import("../tokeniser").Tokeniser} tokeniser
+   * @param {string} type
+   */
+  static parser(tokeniser, type) {
+    return () => {
+      const value = tokeniser.consume(type);
+      if (value) {
+        return new Token({ source: tokeniser.source, tokens: { value } });
+      }
+    };
+  }
+
+  get value() {
+    return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.value.value);
+  }
+}
+
+
+/***/ }),
+/* 11 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Argument", function() { return Argument; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
-/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
-/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
+/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
 /* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2);
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3);
 /* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7);
+// @ts-check
+
 
 
 
 
 
 
 
 
 class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
     const start_position = tokeniser.position;
+    /** @type {Base["tokens"]} */
     const tokens = {};
-    const ret = new Argument({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new Argument({ source: tokeniser.source, tokens }));
     ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser);
     tokens.optional = tokeniser.consume("optional");
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["type_with_extended_attributes"])(tokeniser, "argument-type");
     if (!ret.idlType) {
       return tokeniser.unconsume(start_position);
     }
     if (!tokens.optional) {
       tokens.variadic = tokeniser.consume("...");
     }
     tokens.name = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["argumentNameKeywords"]);
     if (!tokens.name) {
       return tokeniser.unconsume(start_position);
     }
     ret.default = tokens.optional ? _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(tokeniser) : null;
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "argument";
   }
   get optional() {
     return !!this.tokens.optional;
   }
   get variadic() {
     return !!this.tokens.variadic;
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(this.tokens.name.value);
   }
 
+  /**
+   * @param {import("../validator.js").Definitions} defs
+   */
   *validate(defs) {
     yield* this.idlType.validate(defs);
-    if (Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["idlTypeIncludesDictionary"])(this.idlType, defs, { useNullableInner: true })) {
+    const result = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["idlTypeIncludesDictionary"])(this.idlType, defs, { useNullableInner: true });
+    if (result) {
       if (this.idlType.nullable) {
         const message = `Dictionary arguments cannot be nullable.`;
         yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "no-nullable-dict-arg", message);
-      } else if (this.optional && !this.default) {
+      } else if (!this.optional) {
+        if (this.parent && !Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["dictionaryIncludesRequiredField"])(result.dictionary, defs) && isLastRequiredArgument(this)) {
+          const message = `Dictionary argument must be optional if it has no required fields`;
+          yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-optional", message, {
+            autofix: autofixDictionaryArgumentOptionality(this)
+          });
+        }
+      } else if (!this.default) {
         const message = `Optional dictionary arguments must have a default value of \`{}\`.`;
         yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-default", message, {
           autofix: autofixOptionalDictionaryDefaultValue(this)
         });
       }
     }
   }
 }
 
 /**
  * @param {Argument} arg
  */
+function isLastRequiredArgument(arg) {
+  const list = arg.parent.arguments || arg.parent.list;
+  const index = list.indexOf(arg);
+  const requiredExists = list.slice(index + 1).some(a => !a.optional);
+  return !requiredExists;
+}
+
+/**
+ * @param {Argument} arg
+ */
+function autofixDictionaryArgumentOptionality(arg) {
+  return () => {
+    const firstToken = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["getFirstToken"])(arg.idlType);
+    arg.tokens.optional = { type: "optional", value: "optional", trivia: firstToken.trivia };
+    firstToken.trivia = " ";
+    autofixOptionalDictionaryDefaultValue(arg)();
+  };
+}
+
+/**
+ * @param {Argument} arg
+ */
 function autofixOptionalDictionaryDefaultValue(arg) {
   return () => {
     arg.default = _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["Tokeniser"](" = {}"));
   };
 }
 
 
 /***/ }),
-/* 9 */
+/* 12 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Default", function() { return Default; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 
@@ -1244,254 +1690,75 @@ class Default extends _base_js__WEBPACK_
       const close = tokeniser.consume("}") || tokeniser.error("Default dictionary value must be empty");
       expression.push(close);
     }
     return new Default({ source: tokeniser.source, tokens: { assign }, expression });
   }
 
   constructor({ source, tokens, expression }) {
     super({ source, tokens });
+    expression.parent = this;
     Object.defineProperty(this, "expression", { value: expression });
   }
 
   get type() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).type;
   }
   get value() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).value;
   }
   get negative() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).negative;
   }
 }
 
 
 /***/ }),
-/* 10 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleExtendedAttribute", function() { return SimpleExtendedAttribute; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExtendedAttributes", function() { return ExtendedAttributes; });
-/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
-/* harmony import */ var _array_base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
-/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
-/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3);
-
-
-
-
-
-class ExtendedAttributeParameters extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
-  /**
-   * @param {import("../tokeniser").Tokeniser} tokeniser
-   */
-  static parse(tokeniser) {
-    const tokens = { assign: tokeniser.consume("=") };
-    const ret = new ExtendedAttributeParameters({ source: tokeniser.source, tokens });
-    if (tokens.assign) {
-      tokens.secondaryName = tokeniser.consume("identifier", "decimal", "integer", "string");
-    }
-    tokens.open = tokeniser.consume("(");
-    if (tokens.open) {
-      ret.list = ret.rhsType === "identifier-list" ?
-        // [Exposed=(Window,Worker)]
-        Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["identifiers"])(tokeniser) :
-        // [NamedConstructor=Audio(DOMString src)] or [Constructor(DOMString str)]
-        Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["argument_list"])(tokeniser);
-      tokens.close = tokeniser.consume(")") || tokeniser.error("Unexpected token in extended attribute argument list");
-    } else if (ret.hasRhs && !tokens.secondaryName) {
-      tokeniser.error("No right hand side to extended attribute assignment");
-    }
-    return ret;
-  }
-
-  get rhsType() {
-    return !this.tokens.assign ? null :
-      !this.tokens.secondaryName ? "identifier-list" :
-        this.tokens.secondaryName.type;
-  }
-}
-
-class SimpleExtendedAttribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
-  /**
-   * @param {import("../tokeniser").Tokeniser} tokeniser
-   */
-  static parse(tokeniser) {
-    const name = tokeniser.consume("identifier");
-    if (name) {
-      return new SimpleExtendedAttribute({
-        source: tokeniser.source,
-        tokens: { name },
-        params: ExtendedAttributeParameters.parse(tokeniser)
-      });
-    }
-  }
-
-  constructor({ source, tokens, params }) {
-    super({ source, tokens });
-    Object.defineProperty(this, "params", { value: params });
-  }
-
-  get type() {
-    return "extended-attribute";
-  }
-  get name() {
-    return this.tokens.name.value;
-  }
-  get rhs() {
-    const { rhsType: type, tokens, list } = this.params;
-    if (!type) {
-      return null;
-    }
-    const value = type === "identifier-list" ? list : tokens.secondaryName.value;
-    return { type, value };
-  }
-  get arguments() {
-    const { rhsType, list } = this.params;
-    if (!list || rhsType === "identifier-list") {
-      return [];
-    }
-    return list;
-  }
-
-  *validate(defs) {
-    if (this.name === "NoInterfaceObject") {
-      const message = `\`[NoInterfaceObject]\` extended attribute is an \
-undesirable feature that may be removed from Web IDL in the future. Refer to the \
-[relevant upstream PR](https://github.com/heycam/webidl/pull/609) for more \
-information.`;
-      yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(this.tokens.name, this, "no-nointerfaceobject", message, { level: "warning" });
-    }
-    for (const arg of this.arguments) {
-      yield* arg.validate(defs);
-    }
-  }
-}
-
-// Note: we parse something simpler than the official syntax. It's all that ever
-// seems to be used
-class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__["ArrayBase"] {
-  /**
-   * @param {import("../tokeniser").Tokeniser} tokeniser
-   */
-  static parse(tokeniser) {
-    const tokens = {};
-    tokens.open = tokeniser.consume("[");
-    if (!tokens.open) return new ExtendedAttributes({});
-    const ret = new ExtendedAttributes({ source: tokeniser.source, tokens });
-    ret.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["list"])(tokeniser, {
-      parser: SimpleExtendedAttribute.parse,
-      listName: "extended attribute"
-    }));
-    tokens.close = tokeniser.consume("]") || tokeniser.error("Unexpected closing token of extended attribute");
-    if (!ret.length) {
-      tokeniser.error("Found an empty extended attribute");
-    }
-    if (tokeniser.probe("[")) {
-      tokeniser.error("Illegal double extended attribute lists, consider merging them");
-    }
-    return ret;
-  }
-
-  *validate(defs) {
-    for (const extAttr of this) {
-      yield* extAttr.validate(defs);
-    }
-  }
-}
-
-
-/***/ }),
-/* 11 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayBase", function() { return ArrayBase; });
-class ArrayBase extends Array {
-  constructor({ source, tokens }) {
-    super();
-    Object.defineProperties(this, {
-      source: { value: source },
-      tokens: { value: tokens }
-    });
-  }
-}
-
-
-/***/ }),
-/* 12 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Token", function() { return Token; });
-/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
-
-
-class Token extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
-  /**
-   * @param {import("../tokeniser").Tokeniser} tokeniser
-   * @param {string} type
-   */
-  static parser(tokeniser, type) {
-    return () => {
-      const value = tokeniser.consume(type);
-      if (value) {
-        return new Token({ source: tokeniser.source, tokens: { value } });
-      }
-    };
-  }
-
-  get value() {
-    return this.tokens.value.value;
-  }
-}
-
-
-/***/ }),
 /* 13 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Operation", function() { return Operation; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
 
 
 
 
 class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
+   * @typedef {import("../tokeniser.js").Token} Token
+   *
    * @param {import("../tokeniser.js").Tokeniser} tokeniser
+   * @param {object} [options]
+   * @param {Token} [options.special]
+   * @param {Token} [options.regular]
    */
   static parse(tokeniser, { special, regular } = {}) {
     const tokens = { special };
-    const ret = new Operation({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Operation({ source: tokeniser.source, tokens }));
     if (special && special.value === "stringifier") {
       tokens.termination = tokeniser.consume(";");
       if (tokens.termination) {
         ret.arguments = [];
         return ret;
       }
     }
     if (!special && !regular) {
       tokens.special = tokeniser.consume("getter", "setter", "deleter");
     }
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Missing return type");
     tokens.name = tokeniser.consume("identifier", "includes");
     tokens.open = tokeniser.consume("(") || tokeniser.error("Invalid operation");
     ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser);
     tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated operation");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated operation, expected `;`");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "operation";
   }
   get name() {
     const { name } = this.tokens;
     if (!name) {
@@ -1535,17 +1802,17 @@ class Operation extends _base_js__WEBPAC
 
 class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser.js").Tokeniser} tokeniser
    */
   static parse(tokeniser, { special, noInherit = false, readonly = false } = {}) {
     const start_position = tokeniser.position;
     const tokens = { special };
-    const ret = new Attribute({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Attribute({ source: tokeniser.source, tokens }));
     if (!special && !noInherit) {
       tokens.special = tokeniser.consume("inherit");
     }
     if (ret.special === "inherit" && tokeniser.probe("readonly")) {
       tokeniser.error("Inherited attributes cannot be read-only");
     }
     tokens.readonly = tokeniser.consume("readonly");
     if (readonly && !tokens.readonly && tokeniser.probe("attribute")) {
@@ -1558,17 +1825,17 @@ class Attribute extends _base_js__WEBPAC
     }
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "attribute-type") || tokeniser.error("Attribute lacks a type");
     switch (ret.idlType.generic) {
       case "sequence":
       case "record": tokeniser.error(`Attributes cannot accept ${ret.idlType.generic} types`);
     }
     tokens.name = tokeniser.consume("identifier", "async", "required") || tokeniser.error("Attribute lacks a name");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated attribute, expected `;`");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "attribute";
   }
   get special() {
     if (!this.tokens.special) {
       return "";
@@ -1578,30 +1845,31 @@ class Attribute extends _base_js__WEBPAC
   get readonly() {
     return !!this.tokens.readonly;
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value);
   }
 
   *validate(defs) {
+    yield* this.extAttrs.validate(defs);
     yield* this.idlType.validate(defs);
   }
 }
 
 
 /***/ }),
 /* 15 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Enum", function() { return Enum; });
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
-/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12);
+/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10);
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
 
 
 
 
 class EnumValue extends _token_js__WEBPACK_IMPORTED_MODULE_1__["Token"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
@@ -1621,38 +1889,40 @@ class EnumValue extends _token_js__WEBPA
   }
 }
 
 class Enum extends _base_js__WEBPACK_IMPORTED_MODULE_2__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
+    /** @type {Base["tokens"]} */
     const tokens = {};
     tokens.base = tokeniser.consume("enum");
     if (!tokens.base) {
       return;
     }
     tokens.name = tokeniser.consume("identifier") || tokeniser.error("No name for enum");
-    const ret = tokeniser.current = new Enum({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["autoParenter"])(new Enum({ source: tokeniser.source, tokens }));
+    tokeniser.current = ret.this;
     tokens.open = tokeniser.consume("{") || tokeniser.error("Bodyless enum");
     ret.values = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["list"])(tokeniser, {
       parser: EnumValue.parse,
       allowDangler: true,
       listName: "enumeration"
     });
     if (tokeniser.probe("string")) {
       tokeniser.error("No comma between enum values");
     }
     tokens.close = tokeniser.consume("}") || tokeniser.error("Unexpected value in enum");
     if (!ret.values.length) {
       tokeniser.error("No value in enum");
     }
     tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after enum");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "enum";
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["unescape"])(this.tokens.name.value);
   }
@@ -1663,16 +1933,18 @@ class Enum extends _base_js__WEBPACK_IMP
 /* 16 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Includes", function() { return Includes; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+// @ts-check
+
 
 
 
 class Includes extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
@@ -1715,27 +1987,28 @@ class Includes extends _base_js__WEBPACK
 
 
 
 class Typedef extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
+    /** @type {Base["tokens"]} */
     const tokens = {};
-    const ret = new Typedef({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Typedef({ source: tokeniser.source, tokens }));
     tokens.base = tokeniser.consume("typedef");
     if (!tokens.base) {
       return;
     }
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "typedef-type") || tokeniser.error("Typedef lacks a type");
     tokens.name = tokeniser.consume("identifier") || tokeniser.error("Typedef lacks a name");
-    tokeniser.current = ret;
+    tokeniser.current = ret.this;
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated typedef, expected `;`");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "typedef";
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value);
   }
@@ -1759,36 +2032,37 @@ class Typedef extends _base_js__WEBPACK_
 
 
 class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser.js").Tokeniser} tokeniser
    */
   static parse(tokeniser, base) {
     const tokens = { base };
-    const ret = new CallbackFunction({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new CallbackFunction({ source: tokeniser.source, tokens }));
     tokens.name = tokeniser.consume("identifier") || tokeniser.error("Callback lacks a name");
-    tokeniser.current = ret;
+    tokeniser.current = ret.this;
     tokens.assign = tokeniser.consume("=") || tokeniser.error("Callback lacks an assignment");
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Callback lacks a return type");
     tokens.open = tokeniser.consume("(") || tokeniser.error("Callback lacks parentheses for arguments");
     ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser);
     tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated callback");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated callback, expected `;`");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "callback";
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value);
   }
 
   *validate(defs) {
+    yield* this.extAttrs.validate(defs);
     yield* this.idlType.validate(defs);
   }
 }
 
 
 /***/ }),
 /* 19 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
@@ -1801,16 +2075,18 @@ class CallbackFunction extends _base_js_
 /* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13);
 /* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(21);
 /* harmony import */ var _iterable_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(22);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4);
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3);
 /* harmony import */ var _validators_interface_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(23);
 /* harmony import */ var _constructor_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(24);
 /* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(2);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(8);
+
 
 
 
 
 
 
 
 
@@ -1854,56 +2130,72 @@ class Interface extends _container_js__W
     return "interface";
   }
 
   *validate(defs) {
     yield* this.extAttrs.validate(defs);
     if (
       !this.partial &&
       this.extAttrs.every(extAttr => extAttr.name !== "Exposed") &&
-      this.extAttrs.every(extAttr => extAttr.name !== "NoInterfaceObject")
+      this.extAttrs.every(extAttr => extAttr.name !== "LegacyNoInterfaceObject")
     ) {
       const message = `Interfaces must have \`[Exposed]\` extended attribute. \
 To fix, add, for example, \`[Exposed=Window]\`. Please also consider carefully \
 if your interface should also be exposed in a Worker scope. Refer to the \
 [WebIDL spec section on Exposed](https://heycam.github.io/webidl/#Exposed) \
 for more information.`;
       yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(this.tokens.name, this, "require-exposed", message, {
         autofix: Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autofixAddExposedWindow"])(this)
       });
     }
-    const constructors = this.extAttrs.filter(extAttr => extAttr.name === "Constructor");
-    for (const constructor of constructors) {
+    const oldConstructors = this.extAttrs.filter(extAttr => extAttr.name === "Constructor");
+    for (const constructor of oldConstructors) {
       const message = `Constructors should now be represented as a \`constructor()\` operation on the interface \
 instead of \`[Constructor]\` extended attribute. Refer to the \
 [WebIDL spec section on constructor operations](https://heycam.github.io/webidl/#idl-constructors) \
 for more information.`;
       yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(constructor.tokens.name, this, "constructor-member", message, {
         autofix: autofixConstructor(this, constructor)
       });
     }
 
+    const isGlobal = this.extAttrs.some(extAttr => extAttr.name === "Global");
+    if (isGlobal) {
+      const factoryFunctions = this.extAttrs.filter(extAttr => extAttr.name === "LegacyFactoryFunction");
+      for (const named of factoryFunctions) {
+        const message = `Interfaces marked as \`[Global]\` cannot have factory functions.`;
+        yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.name, this, "no-constructible-global", message);
+      }
+
+      const constructors = this.members.filter(member => member.type === "constructor");
+      for (const named of constructors) {
+        const message = `Interfaces marked as \`[Global]\` cannot have constructors.`;
+        yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.base, this, "no-constructible-global", message);
+      }
+    }
+
     yield* super.validate(defs);
     if (!this.partial) {
       yield* Object(_validators_interface_js__WEBPACK_IMPORTED_MODULE_7__["checkInterfaceMemberDuplication"])(defs, this);
     }
   }
 }
 
 function autofixConstructor(interfaceDef, constructorExtAttr) {
+  interfaceDef = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(interfaceDef);
   return () => {
     const indentation = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(interfaceDef.extAttrs.tokens.open.trivia);
     const memberIndent = interfaceDef.members.length ?
       Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getFirstToken"])(interfaceDef.members[0]).trivia) :
       Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getMemberIndentation"])(indentation);
     const constructorOp = _constructor_js__WEBPACK_IMPORTED_MODULE_8__["Constructor"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__["Tokeniser"](`\n${memberIndent}constructor();`));
-    constructorOp.extAttrs = [];
-    constructorOp.arguments = constructorExtAttr.arguments;
-
-    const existingIndex = interfaceDef.members.findIndex(m => m.type === "constructor");
+    constructorOp.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__["ExtendedAttributes"]({});
+    Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(constructorOp).arguments = constructorExtAttr.arguments;
+
+    const existingIndex = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["findLastIndex"])(interfaceDef.members, m => m.type === "constructor");
     interfaceDef.members.splice(existingIndex + 1, 0, constructorOp);
 
     const { close }  = interfaceDef.tokens;
     if (!close.trivia.includes("\n")) {
       close.trivia += `\n${indentation}`;
     }
 
     const { extAttrs } = interfaceDef;
@@ -1923,17 +2215,17 @@ function autofixConstructor(interfaceDef
 /***/ }),
 /* 20 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
-/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
 
 
 
 
 /**
  * @param {import("../tokeniser.js").Tokeniser} tokeniser
  */
@@ -1943,48 +2235,50 @@ function inheritance(tokeniser) {
     return {};
   }
   const inheritance = tokeniser.consume("identifier") || tokeniser.error("Inheritance lacks a type");
   return { colon, inheritance };
 }
 
 class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
     /**
+     * @template T
      * @param {import("../tokeniser.js").Tokeniser} tokeniser
-     * @param {*} instance
+     * @param {T} instance
      * @param {*} args
      */
     static parse(tokeniser, instance, { type, inheritable, allowedMembers }) {
       const { tokens } = instance;
       tokens.name = tokeniser.consume("identifier") || tokeniser.error(`Missing name in ${instance.type}`);
       tokeniser.current = instance;
+      instance = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(instance);
       if (inheritable) {
         Object.assign(tokens, inheritance(tokeniser));
       }
       tokens.open = tokeniser.consume("{") || tokeniser.error(`Bodyless ${type}`);
       instance.members = [];
       while (true) {
         tokens.close = tokeniser.consume("}");
         if (tokens.close) {
           tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type}`);
-          return instance;
+          return instance.this;
         }
         const ea = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__["ExtendedAttributes"].parse(tokeniser);
         let mem;
         for (const [parser, ...args] of allowedMembers) {
-          mem = parser(tokeniser, ...args);
+          mem = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(parser(tokeniser, ...args));
           if (mem) {
             break;
           }
         }
         if (!mem) {
           tokeniser.error("Unknown member");
         }
         mem.extAttrs = ea;
-        instance.members.push(mem);
+        instance.members.push(mem.this);
       }
     }
 
     get partial() {
       return !!this.tokens.partial;
     }
     get name() {
       return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value);
@@ -2020,16 +2314,17 @@ class Container extends _base_js__WEBPAC
 
 
 
 class Constant extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser.js").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
+    /** @type {Base["tokens"]} */
     const tokens = {};
     tokens.base = tokeniser.consume("const");
     if (!tokens.base) {
       return;
     }
     let idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["primitive_type"])(tokeniser);
     if (!idlType) {
       const base = tokeniser.consume("identifier") || tokeniser.error("Const lacks a type");
@@ -2039,25 +2334,25 @@ class Constant extends _base_js__WEBPACK
       tokeniser.error("Unexpected nullable constant type");
     }
     idlType.type = "const-type";
     tokens.name = tokeniser.consume("identifier") || tokeniser.error("Const lacks a name");
     tokens.assign = tokeniser.consume("=") || tokeniser.error("Const lacks value assignment");
     tokens.value = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_value"])(tokeniser) || tokeniser.error("Const lacks a value");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated const, expected `;`");
     const ret = new Constant({ source: tokeniser.source, tokens });
-    ret.idlType = idlType;
+    Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(ret).idlType = idlType;
     return ret;
   }
 
   get type() {
     return "const";
   }
   get name() {
-    return unescape(this.tokens.name.value);
+    return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value);
   }
   get value() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_data"])(this.tokens.value);
   }
 }
 
 
 /***/ }),
@@ -2074,72 +2369,98 @@ class Constant extends _base_js__WEBPACK
 
 class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser.js").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
     const start_position = tokeniser.position;
     const tokens = {};
-    const ret = new IterableLike({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new IterableLike({ source: tokeniser.source, tokens }));
     tokens.readonly = tokeniser.consume("readonly");
     if (!tokens.readonly) {
       tokens.async = tokeniser.consume("async");
     }
     tokens.base =
       tokens.readonly ? tokeniser.consume("maplike", "setlike") :
       tokens.async ? tokeniser.consume("iterable") :
       tokeniser.consume("iterable", "maplike", "setlike");
     if (!tokens.base) {
       tokeniser.unconsume(start_position);
       return;
     }
 
     const { type } = ret;
-    const secondTypeRequired = type === "maplike" || ret.async;
+    const secondTypeRequired = type === "maplike";
     const secondTypeAllowed = secondTypeRequired || type === "iterable";
+    const argumentAllowed = ret.async && type === "iterable";
 
     tokens.open = tokeniser.consume("<") || tokeniser.error(`Missing less-than sign \`<\` in ${type} declaration`);
     const first = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error(`Missing a type argument in ${type} declaration`);
     ret.idlType = [first];
+    ret.arguments = [];
+
     if (secondTypeAllowed) {
       first.tokens.separator = tokeniser.consume(",");
       if (first.tokens.separator) {
         ret.idlType.push(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser));
       }
       else if (secondTypeRequired) {
         tokeniser.error(`Missing second type argument in ${type} declaration`);
       }
     }
+
     tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing greater-than sign \`>\` in ${type} declaration`);
+
+    if (tokeniser.probe("(")) {
+      if (argumentAllowed) {
+        tokens.argsOpen = tokeniser.consume("(");
+        ret.arguments.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser));
+        tokens.argsClose = tokeniser.consume(")") || tokeniser.error("Unterminated async iterable argument list");
+      } else {
+        tokeniser.error(`Arguments are only allowed for \`async iterable\``);
+      }
+    }
+
     tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type} declaration`);
 
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return this.tokens.base.value;
   }
   get readonly() {
     return !!this.tokens.readonly;
   }
   get async() {
     return !!this.tokens.async;
   }
+
+  *validate(defs) {
+    for (const type of this.idlType) {
+      yield* type.validate(defs);
+    }
+    for (const argument of this.arguments) {
+      yield* argument.validate(defs);
+    }
+  }
 }
 
 
 /***/ }),
 /* 23 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "checkInterfaceMemberDuplication", function() { return checkInterfaceMemberDuplication; });
 /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
+// @ts-check
+
 
 
 function* checkInterfaceMemberDuplication(defs, i) {
   const opNames = new Set(getOperations(i).map(op => op.name));
   const partials = defs.partials.get(i.name) || [];
   const mixins = defs.mixinMap.get(i.name) || [];
   for (const ext of [...partials, ...mixins]) {
     const additions = getOperations(ext);
@@ -2182,23 +2503,24 @@ class Constructor extends _base_js__WEBP
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
     const base = tokeniser.consume("constructor");
     if (!base) {
       return;
     }
+    /** @type {Base["tokens"]} */
     const tokens = { base };
     tokens.open = tokeniser.consume("(") || tokeniser.error("No argument list in constructor");
     const args = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser);
     tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated constructor");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after constructor");
-    const ret = new Constructor({ tokens });
-    ret.arguments = args;
+    const ret = new Constructor({ source: tokeniser.source, tokens });
+    Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(ret).arguments = args;
     return ret;
   }
 
   get type() {
     return "constructor";
   }
 
   *validate(defs) {
@@ -2227,17 +2549,22 @@ class Constructor extends _base_js__WEBP
 
 
 
 
 
 
 class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] {
   /**
-   * @param {import("../tokeniser").Tokeniser} tokeniser
+   * @typedef {import("../tokeniser.js").Token} Token
+   *
+   * @param {import("../tokeniser.js").Tokeniser} tokeniser
+   * @param {Token} base
+   * @param {object} [options]
+   * @param {Token} [options.partial]
    */
   static parse(tokeniser, base, { partial } = {}) {
     const tokens = { partial, base };
     tokens.mixin = tokeniser.consume("mixin");
     if (!tokens.mixin) {
       return;
     }
     return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Mixin({ source: tokeniser.source, tokens }), {
@@ -2261,22 +2588,26 @@ class Mixin extends _container_js__WEBPA
 /* 26 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Dictionary", function() { return Dictionary; });
 /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20);
 /* harmony import */ var _field_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27);
+// @ts-check
+
 
 
 
 class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
+   * @param {object} [options]
+   * @param {import("../tokeniser.js").Token} [options.partial]
    */
   static parse(tokeniser, { partial } = {}) {
     const tokens = { partial };
     tokens.base = tokeniser.consume("dictionary");
     if (!tokens.base) {
       return;
     }
     return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Dictionary({ source: tokeniser.source, tokens }), {
@@ -2298,38 +2629,39 @@ class Dictionary extends _container_js__
 /* 27 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Field", function() { return Field; });
 /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
 /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
-/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
-/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
+/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8);
+/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12);
 
 
 
 
 
 class Field extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
    */
   static parse(tokeniser) {
+    /** @type {Base["tokens"]} */
     const tokens = {};
-    const ret = new Field({ source: tokeniser.source, tokens });
+    const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Field({ source: tokeniser.source, tokens }));
     ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser);
     tokens.required = tokeniser.consume("required");
     ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "dictionary-type") || tokeniser.error("Dictionary member lacks a type");
     tokens.name = tokeniser.consume("identifier") || tokeniser.error("Dictionary member lacks a name");
     ret.default = _default_js__WEBPACK_IMPORTED_MODULE_3__["Default"].parse(tokeniser);
     if (tokens.required && ret.default) tokeniser.error("Required member must not have a default");
     tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated dictionary member, expected `;`");
-    return ret;
+    return ret.this;
   }
 
   get type() {
     return "field";
   }
   get name() {
     return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value);
   }
@@ -2359,16 +2691,18 @@ class Field extends _base_js__WEBPACK_IM
 
 
 
 
 
 class Namespace extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
+   * @param {object} [options]
+   * @param {import("../tokeniser.js").Token} [options.partial]
    */
   static parse(tokeniser, { partial } = {}) {
     const tokens = { partial };
     tokens.base = tokeniser.consume("namespace");
     if (!tokens.base) {
       return;
     }
     return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Namespace({ source: tokeniser.source, tokens }), {
@@ -2405,16 +2739,17 @@ for more information.`;
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CallbackInterface", function() { return CallbackInterface; });
 /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20);
 /* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13);
 /* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(21);
+// @ts-check
 
 
 
 
 
 class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] {
   /**
    * @param {import("../tokeniser").Tokeniser} tokeniser
@@ -2538,16 +2873,22 @@ function write(ast, { templates: ts = te
       token(arg.tokens.optional),
       ts.type(type(arg.idlType)),
       token(arg.tokens.variadic),
       name_token(arg.tokens.name, { data: arg }),
       default_(arg.default),
       token(arg.tokens.separator)
     ]);
   }
+  function extended_attribute_listitem(str) {
+    return ts.wrap([
+      token(str.tokens.value),
+      token(str.tokens.separator)
+    ]);
+  }
   function identifier(id, context) {
     return ts.wrap([
       reference_token(id.tokens.value, context),
       token(id.tokens.separator)
     ]);
   }
   function make_ext_at(it) {
     const { rhsType } = it.params;
@@ -2555,17 +2896,19 @@ function write(ast, { templates: ts = te
       ts.trivia(it.tokens.name.trivia),
       ts.extendedAttribute(ts.wrap([
         ts.extendedAttributeReference(it.name),
         token(it.params.tokens.assign),
         reference_token(it.params.tokens.secondaryName, it),
         token(it.params.tokens.open),
         ...!it.params.list ? [] :
           it.params.list.map(
-            rhsType === "identifier-list" ? id => identifier(id, it) : argument
+            rhsType === "identifier-list" ? id => identifier(id, it) :
+            rhsType && rhsType.endsWith("-list") ? extended_attribute_listitem :
+            argument
           ),
         token(it.params.tokens.close)
       ])),
       token(it.tokens.separator)
     ]);
   }
   function extended_attributes(eats) {
     if (!eats.length) return "";
@@ -2719,16 +3062,19 @@ function write(ast, { templates: ts = te
     return ts.definition(ts.wrap([
       extended_attributes(it.extAttrs),
       token(it.tokens.readonly),
       token(it.tokens.async),
       token(it.tokens.base, ts.generic),
       token(it.tokens.open),
       ts.wrap(it.idlType.map(type)),
       token(it.tokens.close),
+      token(it.tokens.argsOpen),
+      ts.wrap(it.arguments.map(argument)),
+      token(it.tokens.argsClose),
       token(it.tokens.termination)
     ]), { data: it, parent });
   }
   function eof(it) {
     return ts.trivia(it.trivia);
   }
 
   const table = {
@@ -2793,16 +3139,19 @@ function getMixinMap(all, unique) {
       array.push(mixin);
     } else {
       map.set(include.target, [mixin]);
     }
   }
   return map;
 }
 
+/**
+ * @typedef {ReturnType<typeof groupDefinitions>} Definitions
+ */
 function groupDefinitions(all) {
   const unique = new Map();
   const duplicates = new Set();
   const partials = new Map();
   for (const def of all) {
     if (def.partial) {
       const array = partials.get(def.name);
       if (array) {
@@ -2823,17 +3172,18 @@ function groupDefinitions(all) {
   }
   return {
     all,
     unique,
     partials,
     duplicates,
     mixinMap: getMixinMap(all, unique),
     cache: {
-      typedefIncludesDictionary: new WeakMap()
+      typedefIncludesDictionary: new WeakMap(),
+      dictionaryIncludesRequiredField: new WeakMap()
     },
   };
 }
 
 function* checkDuplicatedNames({ unique, duplicates }) {
   for (const dup of duplicates) {
     const { name } = dup;
     const message = `The name "${name}" of type "${unique.get(name).type}" was already seen`;