blob: 0c203a0b1e90e9fef8bdcab280d04e2e4564960a [file] [log] [blame]
import { validationError } from "../error.js";
import {
idlTypeIncludesDictionary,
idlTypeIncludesEnforceRange,
} from "../validators/helpers.js";
import { Base } from "./base.js";
import {
type_with_extended_attributes,
unescape,
autoParenter,
} from "./helpers.js";
export class Attribute extends Base {
/**
* @param {import("../tokeniser.js").Tokeniser} tokeniser
* @param {object} [options]
* @param {import("../tokeniser.js").Token} [options.special]
* @param {boolean} [options.noInherit]
* @param {boolean} [options.readonly]
*/
static parse(
tokeniser,
{ special, noInherit = false, readonly = false } = {},
) {
const start_position = tokeniser.position;
const tokens = { special };
const ret = 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")) {
tokeniser.error("Attributes must be readonly in this context");
}
tokens.base = tokeniser.consume("attribute");
if (!tokens.base) {
tokeniser.unconsume(start_position);
return;
}
ret.idlType =
type_with_extended_attributes(tokeniser, "attribute-type") ||
tokeniser.error("Attribute lacks a type");
tokens.name =
tokeniser.consumeKind("identifier") ||
tokeniser.consume("async", "required") ||
tokeniser.error("Attribute lacks a name");
tokens.termination =
tokeniser.consume(";") ||
tokeniser.error("Unterminated attribute, expected `;`");
return ret.this;
}
get type() {
return "attribute";
}
get special() {
if (!this.tokens.special) {
return "";
}
return this.tokens.special.value;
}
get readonly() {
return !!this.tokens.readonly;
}
get name() {
return unescape(this.tokens.name.value);
}
*validate(defs) {
yield* this.extAttrs.validate(defs);
yield* this.idlType.validate(defs);
if (
["async_sequence", "sequence", "record"].includes(this.idlType.generic)
) {
const message = `Attributes cannot accept ${this.idlType.generic} types.`;
yield validationError(
this.tokens.name,
this,
"attr-invalid-type",
message,
);
}
{
const { reference } = idlTypeIncludesDictionary(this.idlType, defs) || {};
if (reference) {
const targetToken = (this.idlType.union ? reference : this.idlType)
.tokens.base;
const message = "Attributes cannot accept dictionary types.";
yield validationError(targetToken, this, "attr-invalid-type", message);
}
}
if (this.readonly) {
if (idlTypeIncludesEnforceRange(this.idlType, defs)) {
const targetToken = this.idlType.tokens.base;
const message =
"Readonly attributes cannot accept [EnforceRange] extended attribute.";
yield validationError(targetToken, this, "attr-invalid-type", message);
}
}
}
/** @param {import("../writer.js").Writer} w */
write(w) {
const { parent } = this;
return w.ts.definition(
w.ts.wrap([
this.extAttrs.write(w),
w.token(this.tokens.special),
w.token(this.tokens.readonly),
w.token(this.tokens.base),
w.ts.type(this.idlType.write(w)),
w.name_token(this.tokens.name, { data: this, parent }),
w.token(this.tokens.termination),
]),
{ data: this, parent },
);
}
}