UNPKG

webidl2

Version:
277 lines (258 loc) 7.67 kB
import { Base } from "./base.js"; import { ArrayBase } from "./array-base.js"; import { WrappedToken } from "./token.js"; import { list, argument_list, autoParenter, unescape } from "./helpers.js"; import { validationError } from "../error.js"; /** * @param {import("../tokeniser.js").Tokeniser} tokeniser * @param {string} tokenName */ function tokens(tokeniser, tokenName) { return list(tokeniser, { parser: WrappedToken.parser(tokeniser, tokenName), listName: tokenName + " list", }); } const extAttrValueSyntax = ["identifier", "decimal", "integer", "string"]; const shouldBeLegacyPrefixed = [ "NoInterfaceObject", "LenientSetter", "LenientThis", "TreatNonObjectAsNull", "Unforgeable", ]; const renamedLegacies = new Map([ .../** @type {[string, string][]} */ ( 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.js").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`, ); } export class ExtendedAttributeParameters extends Base { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser */ static parse(tokeniser) { const tokens = { assign: tokeniser.consume("=") }; const ret = autoParenter( new ExtendedAttributeParameters({ source: tokeniser.source, tokens }), ); ret.list = []; if (tokens.assign) { tokens.asterisk = tokeniser.consume("*"); if (tokens.asterisk) { return ret.this; } tokens.secondaryName = tokeniser.consumeKind(...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)] argument_list(tokeniser); tokens.close = tokeniser.consume(")") || tokeniser.error("Unexpected token in extended attribute argument list"); } else if (tokens.assign && !tokens.secondaryName) { tokeniser.error("No right hand side to extended attribute assignment"); } return ret.this; } get rhsIsList() { return ( this.tokens.assign && !this.tokens.asterisk && !this.tokens.secondaryName ); } get rhsType() { if (this.rhsIsList) { return this.list[0].tokens.value.type + "-list"; } if (this.tokens.asterisk) { return "*"; } if (this.tokens.secondaryName) { return this.tokens.secondaryName.type; } return null; } /** @param {import("../writer.js").Writer} w */ write(w) { const { rhsType } = this; return w.ts.wrap([ w.token(this.tokens.assign), w.token(this.tokens.asterisk), w.reference_token(this.tokens.secondaryName, this.parent), w.token(this.tokens.open), ...this.list.map((p) => { return rhsType === "identifier-list" ? w.identifier(p, this.parent) : p.write(w); }), w.token(this.tokens.close), ]); } } export class SimpleExtendedAttribute extends Base { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser */ static parse(tokeniser) { const name = tokeniser.consumeKind("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 : this.params.tokens.secondaryName ? unescape(tokens.secondaryName.value) : null; 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/whatwg/webidl/pull/609) for more \ information.`; yield 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/whatwg/webidl/pull/870) for more \ information.`; yield validationError(this.tokens.name, this, "renamed-legacy", message, { level: "warning", autofix: renameLegacyExtendedAttribute(this), }); } for (const arg of this.arguments) { yield* arg.validate(defs); } } /** @param {import("../writer.js").Writer} w */ write(w) { return w.ts.wrap([ w.ts.trivia(this.tokens.name.trivia), w.ts.extendedAttribute( w.ts.wrap([ w.ts.extendedAttributeReference(this.name), this.params.write(w), ]), ), w.token(this.tokens.separator), ]); } } /** * @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 export class ExtendedAttributes extends ArrayBase { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser */ static parse(tokeniser) { const tokens = {}; tokens.open = tokeniser.consume("["); const ret = new ExtendedAttributes({ source: tokeniser.source, tokens }); if (!tokens.open) return ret; ret.push( ...list(tokeniser, { parser: SimpleExtendedAttribute.parse, listName: "extended attribute", }), ); tokens.close = tokeniser.consume("]") || tokeniser.error( "Expected a closing token for the extended attribute list", ); if (!ret.length) { tokeniser.unconsume(tokens.close.index); tokeniser.error("An extended attribute list must not be empty"); } 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); } } /** @param {import("../writer.js").Writer} w */ write(w) { if (!this.length) return ""; return w.ts.wrap([ w.token(this.tokens.open), ...this.map((ea) => ea.write(w)), w.token(this.tokens.close), ]); } }