Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for imported types #227

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ The main module's default export is a class which you can construct with a few o
- `implSuffix`: a suffix used, if any, to find files within the source directory based on the IDL file name
- `suppressErrors`: set to true to suppress errors during generation
- `processCEReactions` and `processHTMLConstructor`: see below
- `processReflect`: see below
- `importedTypes`: see below

The `addSource()` method can then be called multiple times to add directories containing `.webidl` IDL files and `.js` implementation class files.

Expand Down Expand Up @@ -240,6 +242,27 @@ function processReflect(idl, implName) {
}
```

### `importedTypes`

The `importedTypes` option takes a `record<string, string>` parameter, where keys are the names of Web IDL types, and the values are the path to the wrapper class API.

In case where a single module exports multiple webidl2js interface wrappers under different namespaces, it's possible to use the `#` symbol to separate the module path from the property name, eg.:

```js
new WebIDL2JS({
importedTypes: {
// The `domexception` package exports the wrapper class API
// for `DOMException` at the top level of `domexception/webidl-wrapper`:
DOMException: "domexception/webidl-wrapper",

// The `whatwg-url` package exports the wrapper class APIs
// for `URL` and `URLSearchParams` as properties of `whatwg-url/webidl-wrapper`:
URL: "whatwg-url/webidl-wrapper#URL",
URLSearchParams: "whatwg-url/webidl-wrapper#URLSearchParams",
}
});
```

## Generated wrapper class file API

The example above showed a simplified generated wrapper file with only three exports: `create`, `is`, and `interface`. In reality the generated wrapper file will contain more functionality, documented here. This functionality is different between generated wrapper files for interfaces and for dictionaries.
Expand All @@ -264,6 +287,10 @@ Performs the Web IDL conversion algorithm for this interface, converting _value_

In practice, this means doing a type-check equivalent to `is(value)`, and if it passes, returns the corresponding impl. If the type-check fails, it throws an informative exception. _context_ can be used to describe the provided value in any resulting error message.

#### `validate(value, { context })`

Like `convert(value)`, but returns the wrapper class. This exists to support imported types, which can't use `utils.wrapperFromImpl(value)` as the wrapper and impl private symbols aren't shared between packages.

#### `install(globalObject, globalNames)`

This method creates a brand new wrapper constructor and prototype and attach it to the passed `globalObject`. It also registers the created constructor with the `globalObject`'s global constructor registry, which makes `create()`, `createImpl()`, and `setup()` work. (Thus, it is important to invoke `install()` before invoking those methods, as otherwise they will throw.)
Expand Down
6 changes: 6 additions & 0 deletions lib/constructs/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,12 @@ class Interface {
exports.isImpl = value => {
return utils.isObject(value) && value instanceof Impl.implementation;
};
exports.validate = (value, { context = "The provided value" } = {}) => {
if (!exports.is(value)) {
throw new TypeError(\`\${context} is not of type '${this.name}'.\`);
}
return value;
};
exports.convert = (value, { context = "The provided value" } = {}) => {
if (exports.is(value)) {
return utils.implForWrapper(value);
Expand Down
16 changes: 14 additions & 2 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,24 @@ class Context {
processCEReactions = defaultProcessor,
processHTMLConstructor = defaultProcessor,
processReflect = null,
options = { suppressErrors: false }
} = {}) {
importedTypes = {},
options = {
suppressErrors: false
}
}) {
this.implSuffix = implSuffix;
this.processCEReactions = processCEReactions;
this.processHTMLConstructor = processHTMLConstructor;
this.processReflect = processReflect;
this.options = options;

this.initialize();
this.importedTypes = new Map(
Object.entries(importedTypes).map(([name, data]) => {
const [path, property] = data.split("#", 2);
return [name, { path, property }];
})
);
}

initialize() {
Expand Down Expand Up @@ -75,6 +84,9 @@ class Context {
if (this.enumerations.has(name)) {
return "enumeration";
}
if (this.importedTypes.has(name)) {
return "webidl2js:imported";
}
return undefined;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Transformer {
processCEReactions: opts.processCEReactions,
processHTMLConstructor: opts.processHTMLConstructor,
processReflect: opts.processReflect,
importedTypes: {
...opts.importedTypes
},
options: {
suppressErrors: Boolean(opts.suppressErrors)
}
Expand Down
23 changes: 19 additions & 4 deletions lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ function mergeExtAttrs(a = [], b = []) {
}

// Types of types that generate an output file.
const resolvedTypes = new Set(["callback", "callback interface", "dictionary", "enumeration", "interface"]);
const resolvedTypes = new Set([
"callback",
"callback interface",
"dictionary",
"enumeration",
"interface",
"webidl2js:imported"
]);

function resolveType(ctx, idlType, stack = []) {
if (resolvedMap.has(idlType)) {
Expand Down Expand Up @@ -107,6 +114,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e
}
}

let typeKind;
if (idlType.union) {
// union type
generateUnion();
Expand All @@ -130,13 +138,20 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e
) {
// string or number type compatible with webidl-conversions
generateGeneric(`conversions["${idlType.idlType}"]`);
} else if (resolvedTypes.has(ctx.typeOf(idlType.idlType))) {
} else if (resolvedTypes.has(typeKind = ctx.typeOf(idlType.idlType))) {
// callback functions, callback interfaces, dictionaries, enumerations, and interfaces
let fn;
// Avoid requiring the interface itself
if (idlType.idlType !== parentName) {
fn = `${idlType.idlType}.convert`;
requires.addRelative(idlType.idlType);
// webidl2js:imported types can't use `convert`, as we'd have no way
// to convert them back to their wrappers.
if (typeKind === "webidl2js:imported") {
const { path, property } = ctx.importedTypes.get(idlType.idlType);
fn = `${requires.add(path, property)}.validate`;
} else {
fn = `${idlType.idlType}.convert`;
requires.addRelative(idlType.idlType);
}
} else {
fn = `exports.convert`;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function formatArgs(args) {
}

function toKey(type, func = "") {
return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_");
return String(`${func}@${type}`).replace(/[@./-]+/g, " ").trim().replace(/ /g, "_");
}

const PACKAGE_NAME_REGEX = /^(?:@([^/]+?)[/])?([^/]+?)$/u;
Expand Down
Loading