webidl2.js

NPM version Known Vulnerabilities Financial Contributors on Open Collective

Purpose

This is a parser for Web IDL, a language to specify web APIs in interoperable way. This library supports both Node.js and the browser environment.

Try the online checker here.

Installation

Just the usual. For Node:

npm install webidl2

In the browser without module support:

<script src='./webidl2/dist/webidl2.js'></script>

Documentation

WebIDL2 provides two functions: parse and write.

In Node, that happens with:

const { parse, write, validate } = require("webidl2");
const tree = parse("string of WebIDL");
const text = write(tree);
const validation = validate(tree);

In the browser:

<script>
  const tree = WebIDL2.parse("string of WebIDL");
  const text = WebIDL2.write(tree);
  const validation = WebIDL2.validate(tree);
</script>

<!-- Or when module is supported -->
<script type="module">
  import { parse, write, validate } from "./webidl2/index.js";
  const tree = parse("string of WebIDL");
  const text = write(tree);
  const validation = validate(tree);
</script>

parse() optionally takes an option bag with the following fields:

write() optionally takes a “templates” object, whose properties are functions that process input in different ways (depending on what is needed for output). Every property is optional. Each property is documented below:

var result = WebIDL2.write(tree, {
  templates: {
    /**
     * A function that receives syntax strings plus anything the templates returned.
     * The items are guaranteed to be ordered.
     * The returned value may be again passed to any template functions,
     * or it may also be the final return value of `write()`.
     * @param {any[]} items
     */
    wrap: items => items.join(""),
    /**
     * @param {string} t A trivia string, which includes whitespaces and comments.
     */
    trivia: t => t,
    /**
     * The identifier for a container type. For example, the `Foo` part of `interface Foo {};`.
     * @param {string} escaped The escaped raw name of the definition.
     * @param data The definition with the name
     * @param parent The parent of the definition, undefined if absent
     */
    name: (escaped, { data, parent }) => escaped,
    /**
     * Called for each type referece, e.g. `Window`, `DOMString`, or `unsigned long`.
     * @param escaped The referenced name. Typically string, but may also be the return
     *            value of `wrap()` if the name contains whitespace.
     * @param unescaped Unescaped reference.
     */
    reference: (escaped, unescaped) => escaped,
    /**
     * Called for each generic-form syntax, e.g. `sequence`, `Promise`, or `maplike`.
     * @param {string} name The keyword for syntax
     */
    generic: name => name,
    /**
     * Called for each nameless members, e.g. `stringifier` for `stringifier;` and `constructor` for `constructor();`
     * @param {string} name The keyword for syntax
     */
    nameless: (keyword, { data, parent }) => keyword,
    /**
     * Called only once for each types, e.g. `Document`, `Promise<DOMString>`, or `sequence<long>`.
     * @param type The `wrap()`ed result of references and syntatic bracket strings.
     */
    type: type => type,
    /**
     * Receives the return value of `reference()`. String if it's absent.
     */
    inheritance: inh => inh,
    /**
     * Called for each IDL type definition, e.g. an interface, an operation, or a typedef.
     * @param content The wrapped value of everything the definition contains.
     * @param data The original definition object
     * @param parent The parent of the definition, undefined if absent
     */
    definition: (content, { data, parent }) => content,
    /**
     * Called for each extended attribute annotation.
     * @param content The wrapped value of everything the annotation contains.
     */
    extendedAttribute: content => content,
    /**
     * The `Foo` part of `[Foo=Whatever]`.
     * @param ref The name of the referenced extended attribute name.
     */
    extendedAttributeReference: ref => ref
  }
});

“Wrapped value” here will all be raw strings when the wrap() callback is absent.

validate() receives an AST or an array of AST, and returns semantic errors as an array of objects. Their fields are same as errors have, with one addition:

const validations = validate(tree);
for (const validation of validations) {
  console.log(validation.message);
}
// Validation error on line X: ...
// Validation error on line Y: ...

The validator function may provide an autofix function that modifies AST. You can optionally call it to skip manual fixes, but make sure you review the result.

const validations = validate(tree);
for (const validation of validations) {
  if (validation.autofix) {
    validation.autofix();
  }
}
write(tree); // magic!

Errors

When there is a syntax error in the WebIDL, it throws an exception object with the following properties:

The exception also has a toString() method that hopefully should produce a decent error message.

AST (Abstract Syntax Tree)

The parse() method returns a tree object representing the parse tree of the IDL. Comment and white space are not represented in the AST.

The root of this object is always an array of definitions (where definitions are any of interfaces, dictionaries, callbacks, etc. — anything that can occur at the root of the IDL).

IDL Type

This structure is used in many other places (operation return types, argument types, etc.). It captures a WebIDL type with a number of options. Types look like this and are typically attached to a field called idlType:

{
  "type": "attribute-type",
  "generic": "",
  "idlType": "unsigned short",
  "nullable": false,
  "union": false,
  "extAttrs": [...]
}

Where the fields are as follows:

Interface

Interfaces look like this:

{
  "type": "interface",
  "name": "Animal",
  "partial": false,
  "members": [...],
  "inheritance": null,
  "extAttrs": [...]
}, {
  "type": "interface",
  "name": "Human",
  "partial": false,
  "members": [...],
  "inheritance": "Animal",
  "extAttrs": [...]
}

The fields are as follows:

Interface mixins

Interfaces mixins look like this:

{
  "type": "interface mixin",
  "name": "Animal",
  "inheritance": null,
  "partial": false,
  "members": [...],
  "extAttrs": [...]
}, {
  "type": "interface mixin",
  "name": "Human",
  "inheritance": null,
  "partial": false,
  "members": [...],
  "extAttrs": [...]
}

The fields are as follows:

Namespace

Namespaces look like this:

{
  "type": "namespace",
  "name": "console",
  "inheritance": null,
  "partial": false,
  "members": [...],
  "extAttrs": [...]
}

The fields are as follows:

Callback Interfaces

These are captured by the same structure as Interfaces except that their type field is “callback interface”.

Callback

A callback looks like this:

{
  "type": "callback",
  "name": "AsyncOperationCallback",
  "idlType": {
    "type": "return-type",
    "generic": "",
    "nullable": false,
    "union": false,
    "idlType": "undefined",
    "extAttrs": []
  },
  "arguments": [...],
  "extAttrs": []
}

The fields are as follows:

Dictionary

A dictionary looks like this:

{
  "type": "dictionary",
  "name": "PaintOptions",
  "partial": false,
  "members": [{
    "type": "field",
    "name": "fillPattern",
    "required": false,
    "idlType": {
      "type": "dictionary-type",
      "generic": "",
      "nullable": true
      "union": false,
      "idlType": "DOMString",
      "extAttrs": []
    },
    "extAttrs": [],
    "default": {
      "type": "string",
      "value": "black"
    }
  }],
  "inheritance": null,
  "extAttrs": []
}

The fields are as follows:

All the members are fields as follows:

Enum

An enum looks like this:

{
  "type": "enum",
  "name": "MealType",
  "values": [
    {
      "type": "enum-value",
      "value": "rice"
    },
    {
      "type": "enum-value",
      "value": "noodles"
    },
    {
      "type": "enum-value",
      "value": "other"
    }
  ]
  "extAttrs": []
}

The fields are as follows:

Typedef

A typedef looks like this:

{
  "type": "typedef",
  "idlType": {
    "type": "typedef-type",
    "generic": "sequence",
    "nullable": false,
    "union": false,
    "idlType": [
      {
        "type": "typedef-type",
        "generic": "",
        "nullable": false,
        "union": false,
        "idlType": "Point",
        "extAttrs": [...]
      }
    ],
    "extAttrs": [...]
  },
  "name": "PointSequence",
  "extAttrs": []
}

The fields are as follows:

Includes

An includes definition looks like this:

{
  "type": "includes",
  "target": "Node",
  "includes": "EventTarget",
  "extAttrs": []
}

The fields are as follows:

Operation Member

An operation looks like this:

{
  "type": "operation",
  "special": "",
  "idlType": {
    "type": "return-type",
    "generic": "",
    "nullable": false,
    "union": false,
    "idlType": "undefined",
    "extAttrs": []
  },
  "name": "intersection",
  "arguments": [{
    "default": null,
    "optional": false,
    "variadic": true,
    "extAttrs": [],
    "idlType": {
      "type": "argument-type",
      "generic": "",
      "nullable": false,
      "union": false,
      "idlType": "long",
      "extAttrs": [...]
    },
    "name": "ints"
  }],
  "extAttrs": [],
  "parent": { ... }
}

The fields are as follows:

Constructor Operation Member

A constructor operation member looks like this:

{
  "type": "constructor",
  "arguments": [{
    "default": null,
    "optional": false,
    "variadic": true,
    "extAttrs": [],
    "idlType": {
      "type": "argument-type",
      "generic": "",
      "nullable": false,
      "union": false,
      "idlType": "long",
      "extAttrs": [...]
    },
    "name": "ints"
  }],
  "extAttrs": [],
  "parent": { ... }
}

The fields are as follows:

Attribute Member

An attribute member looks like this:

{
  "type": "attribute",
  "special": "",
  "readonly": false,
  "idlType": {
    "type": "attribute-type",
    "generic": "",
    "nullable": false,
    "union": false,
    "idlType": "any",
    "extAttrs": [...]
  },
  "name": "regexp",
  "extAttrs": [],
  "parent": { ... }
}

The fields are as follows:

Constant Member

A constant member looks like this:

{
  "type": "const",
  "idlType": {
    "type": "const-type",
    "generic": "",
    "nullable": false,
    "union": false,
    "idlType": "boolean",
    "extAttrs": []
  },
  "name": "DEBUG",
  "value": {
    "type": "boolean",
    "value": false
  },
  "extAttrs": [],
  "parent": { ... }
}

The fields are as follows:

Arguments

The arguments (e.g. for an operation) look like this:

{
  "arguments": [{
    "type": "argument",
    "default": null,
    "optional": false,
    "variadic": true
    "extAttrs": []
    "idlType": {
      "type": "argument-type",
      "generic": "",
      "nullable": false,
      "union": false,
      "idlType": "float",
      "extAttrs": [...]
    },
    "name": "ints",
    "parent": { ... }
  }]
}

The fields are as follows:

Extended Attributes

Extended attribute container look like this:

{
  "extAttrs": [{
    "name": "PutForwards",
    "arguments": [...],
    "type": "extended-attribute",
    "rhs": {
      "type": "identifier",
      "value": "foo"
    },
    "parent": { ... }
  }]
}

The fields are as follows:

Extended attributes look like this:

Default and Const Values

Dictionary fields and operation arguments can take default values, and constants take values, all of which have the following fields:

For "boolean", "string", "number", and "sequence":

For "Infinity":

iterable<>, async iterable<>, maplike<>, and setlike<> declarations

These appear as members of interfaces that look like this:

{
  "type": "maplike", // or "iterable" / "setlike"
  "idlType": /* One or two types */ ,
  "readonly": false, // only for maplike and setlike
  "async": false, // iterable can be async
  "arguments": [], // only for async iterable
  "extAttrs": [],
  "parent": { ... }
}

The fields are as follows:

End of file

{
  "type": "eof",
  "value": ""
}

This type only appears as the last item of parser results, only if options.concrete is true. This is needed for the writer to keep any comments or whitespaces at the end of file.

The fields are as follows:

Testing

Running

The test runs with mocha and expect.js. Normally, running npm test in the root directory should be enough once you’re set up.

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]