├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── lib ├── code-generation.js ├── index.js └── traceur-parsing.js ├── package.json ├── scripts ├── generate-class.js └── traceur-tree.js └── test ├── cases ├── comments.idl ├── comments.js ├── constants.idl ├── constants.js ├── custom-element-callbacks.idl ├── custom-element-callbacks.js ├── empty-interface.idl ├── empty-interface.js ├── html-hr-element.idl ├── html-hr-element.js ├── implements.idl ├── implements.js ├── inheritance.idl ├── inheritance.js ├── methods.idl ├── methods.js ├── multiple-attributes.idl ├── multiple-attributes.js ├── no-argument-method.idl ├── no-argument-method.js ├── no-conversion.idl ├── no-conversion.js ├── no-interface-object.idl ├── no-interface-object.js ├── readonly-attribute.idl ├── readonly-attribute.js ├── reflect-attributes.idl ├── reflect-attributes.js ├── reflect-custom-name.idl ├── reflect-custom-name.js ├── reflect-readonly-attribute.idl ├── reflect-readonly-attribute.js ├── stringifier-attribute.idl ├── stringifier-attribute.js ├── stringifier-basic.idl ├── stringifier-basic.js ├── stringifier-operation.idl ├── stringifier-operation.js ├── stringifier-with-return-type.idl └── stringifier-with-return-type.js ├── check-cases.js ├── check-errors.js └── errors ├── bad-implements.idl ├── bad-implements.txt ├── empty.idl ├── empty.txt ├── multiple-interfaces.idl ├── multiple-interfaces.txt ├── non-interface.idl ├── non-interface.txt ├── partial-interface.idl └── partial-interface.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | 11 | # Traceur outputs with 2 spaces; IDL files match for sanity 12 | [test/cases/*] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The BSD 2-Clause License 2 | 3 | Copyright (c) 2014, Domenic Denicola 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generate JavaScript Classes from WebIDL Interfaces 2 | 3 | The goal of this project is to take as input 4 | 5 | ```js 6 | // foo.idl 7 | interface Foo : Bar { 8 | [Reflect] attribute unsigned long x; 9 | readonly attribute long y; 10 | attribute DOMString z; 11 | boolean method(DOMString arg); 12 | 13 | constant unsigned short A_CONSTANT = 42; 14 | } 15 | ``` 16 | 17 | plus 18 | 19 | ```js 20 | // foo-impl.js 21 | export default class FooImpl { 22 | get y() { return Math.random() * 1000; } 23 | get z() { return this._z; } 24 | set z(v) { this._z = v; } 25 | method(arg) { return arg.toLowerCase(); } 26 | } 27 | 28 | ``` 29 | 30 | and produce something like 31 | 32 | ```js 33 | // foo.js 34 | import reflector from "webidl-html-reflector"; 35 | import conversions from "webidl-conversions"; 36 | import Impl from "./foo-impl"; 37 | 38 | export default class Foo extends Bar { 39 | get x() { 40 | return reflector["unsigned long"].get(this, "x"); 41 | } 42 | set x(v) { 43 | v = conversions["unsigned long"](v); 44 | reflector["unsigned long"].set(this, "x", v); 45 | } 46 | 47 | get y() { 48 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "y").get; 49 | const implResult = implGetter.call(this); 50 | return conversions["long"](implResult); 51 | } 52 | 53 | get z() { 54 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "z").get; 55 | const implResult = implGetter.call(this); 56 | return conversions["DOMString"](implResult); 57 | } 58 | set z(v) { 59 | v = conversions["DOMString"](v); 60 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, "z").set; 61 | implSetter.call(this, v); 62 | } 63 | 64 | method(arg) { 65 | arg = conversions["DOMString"](arg); 66 | const implMethod = Impl.prototype.method; 67 | const implResult = implMethod.call(this, arg); 68 | return conversions["boolean"](implResult); 69 | } 70 | } 71 | 72 | Object.defineProperty(Foo, "A_CONSTANT", { value: 42, enumerable: true }); 73 | Object.defineProperty(Foo.prototype, "A_CONSTANT", { value: 42, enumerable: true }); 74 | 75 | window.Foo = Foo; 76 | ``` 77 | 78 | ## API 79 | 80 | This package's main module's default export is a function that takes as input a WebIDL string and the implementation module name, and returns a string of JavaScript. It will parse the WebIDL, making sure that it contains a single (non-partial) interface, and then build up the resulting JavaScript. Any unsupported WebIDL features—which is most of them, right now—will generally be ignored. An example: 81 | 82 | ```js 83 | const fs = require("fs"); 84 | import generate from "webidl-class-generator"; 85 | 86 | const idl = fs.readFileSync("html-hr-element.idl", { encoding: "utf-8" }); 87 | const js = generate(idl, "./html-hr-element-impl.js"); 88 | 89 | fs.writeFileSync("html-hr-element.js", js); 90 | ``` 91 | 92 | ## Nonstandard Extended Attributes 93 | 94 | A couple of non-standard extended attributes are allowed which are not part of the WebIDL specification. 95 | 96 | ### `[Reflect]` 97 | 98 | The `[Reflect]` extended attribute is implemented to call `this.getAttribute` or `this.setAttribute` and process the input our output using [webidl-html-reflector](https://github.com/domenic/webidl-html-reflector), on both setting and getting. If `[Reflect]` is specified, the implementation class will not be consulted for the getter or setter logic. 99 | 100 | By default the attribute passed to `this.getAttribute` and `this.setAttribute` will be the same as the name of the property being reflected. You can use the form `[Reflect=custom]` or `[Reflect=custom_with_dashes]` to change that to be `"custom"` or `"custom-with-dashes"`, respectively. 101 | 102 | ### `[NoConversion]` 103 | 104 | The `[NoConversion]` extended attribute will cause any type conversions to be omitted from a getter or setter. This is mostly useful when the implementation class already does the type conversion, e.g. if implementing [`URLUtils`](https://url.spec.whatwg.org/#urlutils) it is possible that the techniques using by the implementation class will already produce `USVString`s, and thus it would be undesirable to convert them all over again. 105 | 106 | ### `[CustomElementCallbacks]` 107 | 108 | The `[CustomElementCallbacks]` extended attribute can be applied to an interface to denote that it should copy over any of the [custom element callbacks](https://w3c.github.io/webcomponents/spec/custom/#types-of-callbacks) from the implementation class to the generated class. 109 | 110 | ## Status 111 | 112 | We only support a subset of WebIDL features; they are being added on an as-needed basis for [HTML as Custom Elements](https://github.com/dglazkov/html-as-custom-elements). Check out [the test cases](https://github.com/domenic/webidl-class-generator/tree/master/test/cases) for a sampling of what's supported, and [the issues](https://github.com/domenic/webidl-class-generator/labels/idl%20feature) for upcoming ones that need work. 113 | -------------------------------------------------------------------------------- /lib/code-generation.js: -------------------------------------------------------------------------------- 1 | import { parseModule, parsePropertyDefinition, parseStatement, parseStatements } from "./traceur-parsing.js"; 2 | const { ParseTreeWriter } = require("traceur").get("outputgeneration/ParseTreeWriter.js"); 3 | const { createIdentifierExpression: id, createFunctionBody, createStringLiteralToken: s } 4 | = require("traceur").get("codegeneration/ParseTreeFactory.js"); 5 | 6 | export function moduleTree(className, { 7 | includeReflector = false, 8 | includeConversions = false, 9 | implModuleName, 10 | baseClassName, 11 | implemented } = {}) { 12 | const moduleTree = baseClassName ? 13 | parseModule`export default class ${className} extends ${id(baseClassName)} { }` : 14 | parseModule`export default class ${className} { }`; 15 | 16 | for (const [interfaceName, moduleName] of implemented) { 17 | moduleTree.scriptItemList.unshift(importTree(interfaceName, moduleName)); 18 | } 19 | 20 | if (implModuleName !== undefined) { 21 | moduleTree.scriptItemList.unshift(importTree("Impl", implModuleName)); 22 | } 23 | 24 | if (includeReflector) { 25 | moduleTree.scriptItemList.unshift(parseStatement`import reflector from "webidl-html-reflector";`); 26 | } 27 | 28 | if (includeConversions) { 29 | moduleTree.scriptItemList.unshift(parseStatement`import conversions from "webidl-conversions";`); 30 | } 31 | 32 | return moduleTree; 33 | } 34 | 35 | export function getterTree(name, type, { noConversion } = {}) { 36 | const tree = parsePropertyDefinition`get ${name}() { 37 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, ${name}).get; 38 | const implResult = implGetter.call(this); 39 | }`; 40 | 41 | if (noConversion) { 42 | tree.body.statements.push(parseStatement`return implResult;`); 43 | } else { 44 | tree.body.statements.push(parseStatement`return conversions[${type}](implResult);`); 45 | } 46 | 47 | return tree; 48 | } 49 | 50 | export function reflectGetterTree(name, type, as = name.toLowerCase()) { 51 | return parsePropertyDefinition`get ${name}() { 52 | return reflector[${type}].get(this, ${as}); 53 | }`; 54 | } 55 | 56 | export function setterTree(name, type, { noConversion } = {}) { 57 | const tree = parsePropertyDefinition`set ${name}(v) { 58 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, ${name}).set; 59 | implSetter.call(this, v); 60 | }`; 61 | 62 | if (!noConversion) { 63 | tree.body.statements.unshift(parseStatement`v = conversions[${type}](v);`); 64 | } 65 | 66 | return tree; 67 | } 68 | 69 | export function reflectSetterTree(name, type, as = name.toLowerCase(), { noConversion } = {}) { 70 | const tree = parsePropertyDefinition`set ${name}(v) { 71 | reflector[${type}].set(this, ${as}, v); 72 | }`; 73 | 74 | if (!noConversion) { 75 | tree.body.statements.unshift(parseStatement`v = conversions[${type}](v);`); 76 | } 77 | 78 | return tree; 79 | } 80 | 81 | export function methodTree(name, args, returnType) { 82 | const argNames = args.map(a => a.name); 83 | const methodTree = parsePropertyDefinition`${name}(${argNames.join(", ")}) { }`; 84 | 85 | methodTree.body.statements = args.map(arg => { 86 | const identifier = id(arg.name); 87 | return parseStatement`${identifier} = conversions[${arg.type}](${identifier});`; 88 | }); 89 | 90 | methodTree.body.statements.push(parseStatement`const implMethod = Impl.prototype.${name};`); 91 | 92 | const callStatement = parseStatement`const implResult = implMethod.call(this);` 93 | callStatement.declarations.declarations[0].initializer.args.args.push(...argNames.map(id)); 94 | methodTree.body.statements.push(callStatement); 95 | 96 | methodTree.body.statements.push(parseStatement`return conversions[${returnType}](implResult);`); 97 | 98 | return methodTree; 99 | } 100 | 101 | export function methodStringifierTree(name) { 102 | return parsePropertyDefinition`toString() { 103 | return conversions["DOMString"](this.${name}()); 104 | }`; 105 | } 106 | 107 | export function attributeStringifierTree(name) { 108 | return parsePropertyDefinition`toString() { 109 | return this.${name}; 110 | }`; 111 | } 112 | 113 | export function mixinTrees(className, mixinName) { 114 | return parseStatements` 115 | Object.getOwnPropertyNames(${id(mixinName)}.prototype).forEach((key) => { 116 | const propDesc = Object.getOwnPropertyDescriptor(${id(mixinName)}.prototype, key); 117 | Object.defineProperty(${id(className)}.prototype, key, propDesc); 118 | }); 119 | `; 120 | } 121 | 122 | export function constantTrees(className, propertyName, value) { 123 | return parseStatements` 124 | Object.defineProperty(${id(className)}, ${propertyName}, { value: ${value}, enumerable: true }); 125 | Object.defineProperty(${id(className)}.prototype, ${propertyName}, { value: ${value}, enumerable: true }); 126 | `; 127 | } 128 | 129 | export function addToWindowTree(className) { 130 | return parseStatement`window.${className} = ${id(className)};` 131 | } 132 | 133 | export function transferCustomElementCallbacksTrees(className) { 134 | return parseStatements` 135 | ${id(className)}.prototype.createdCallback = Impl.prototype.createdCallback; 136 | ${id(className)}.prototype.attachedCallback = Impl.prototype.attachedCallback; 137 | ${id(className)}.prototype.detachedCallback = Impl.prototype.detachedCallback; 138 | ${id(className)}.prototype.attributeChangedCallback = Impl.prototype.attributeChangedCallback; 139 | `; 140 | } 141 | 142 | export function treeToString(tree) { 143 | const writer = new ParseTreeWriter(); 144 | writer.visitAny(tree); 145 | return writer.toString(); 146 | } 147 | 148 | function importTree(identifier, moduleName) { 149 | // Working around https://github.com/google/traceur-compiler/issues/1414 150 | const statement = parseStatement`import ${id(identifier)} from "__placeholder__";`; 151 | statement.moduleSpecifier.token = s(moduleName); 152 | return statement; 153 | } 154 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const parseWebIDL = require("webidl2").parse; 2 | import * as codeGen from "./code-generation.js"; 3 | 4 | export default function generate(webIdlString, implModuleName, implementsModuleNameGetter = defaultImplements) { 5 | const { theInterface, implemented } = getInterfaceAndImplemented(webIdlString); 6 | const attributes = theInterface.members.filter(m => m.type === "attribute"); 7 | const operations = theInterface.members.filter(m => m.type === "operation"); 8 | const constants = theInterface.members.filter(m => m.type === "const"); 9 | 10 | const transferCustomElementCallbacks = theInterface.extAttrs.some(xa => xa.name === "CustomElementCallbacks"); 11 | const includeReflector = attributes.some(hasReflect); 12 | const hasNonReflectedAttributes = attributes.some(a => !hasReflect(a)); 13 | const includeImpl = transferCustomElementCallbacks || operations.length > 0 || hasNonReflectedAttributes; 14 | const includeConversions = operations.length > 0 || 15 | attributes.some(a => (!hasReflect(a) || !a.readonly) && !hasNoConversion(a)); 16 | const implementedMap = new Map(implemented.map(name => [name, implementsModuleNameGetter(name)])); 17 | const moduleTree = codeGen.moduleTree(theInterface.name, { 18 | includeReflector, 19 | includeConversions, 20 | baseClassName: theInterface.inheritance, 21 | implModuleName: includeImpl ? implModuleName : undefined, 22 | implemented: implementedMap 23 | }); 24 | 25 | const classTree = moduleTree.scriptItemList[moduleTree.scriptItemList.length - 1].declaration.expression; 26 | 27 | attributes.forEach(a => { 28 | const reflect = getReflect(a); 29 | const noConversion = a.extAttrs.some(xa => xa.name === "NoConversion"); 30 | if (reflect.shouldReflect) { 31 | const getterTree = codeGen.reflectGetterTree(a.name, a.idlType.idlType, reflect.as, { noConversion }); 32 | classTree.elements.push(getterTree); 33 | 34 | if (!a.readonly) { 35 | const setterTree = codeGen.reflectSetterTree(a.name, a.idlType.idlType, reflect.as, { noConversion }); 36 | classTree.elements.push(setterTree); 37 | } 38 | } else { 39 | classTree.elements.push(codeGen.getterTree(a.name, a.idlType.idlType, { noConversion })); 40 | 41 | if (!a.readonly) { 42 | classTree.elements.push(codeGen.setterTree(a.name, a.idlType.idlType, { noConversion })); 43 | } 44 | } 45 | 46 | if (a.stringifier) { 47 | classTree.elements.push(codeGen.attributeStringifierTree(a.name)); 48 | } 49 | }); 50 | 51 | operations.forEach(o => { 52 | let name, args, returnType; 53 | if (o.stringifier) { 54 | if (o.name) { 55 | classTree.elements.push(codeGen.methodStringifierTree(o.name)); 56 | return; 57 | } 58 | 59 | name = 'toString'; 60 | args = []; 61 | returnType = 'DOMString'; 62 | } else { 63 | name = o.name; 64 | args = o.arguments.map(arg => ({ name: arg.name, type: arg.idlType.idlType })); 65 | returnType = o.idlType.idlType; 66 | } 67 | 68 | classTree.elements.push(codeGen.methodTree(name, args, returnType)); 69 | }); 70 | 71 | for (const mixinName of implemented) { 72 | moduleTree.scriptItemList.push(...codeGen.mixinTrees(theInterface.name, mixinName)); 73 | } 74 | 75 | constants.forEach(c => { 76 | moduleTree.scriptItemList.push(...codeGen.constantTrees(theInterface.name, c.name, c.value.value)); 77 | }); 78 | 79 | if (transferCustomElementCallbacks) { 80 | moduleTree.scriptItemList.push(...codeGen.transferCustomElementCallbacksTrees(theInterface.name)); 81 | } 82 | 83 | const addToWindow = !theInterface.extAttrs.some(xa => xa.name === "NoInterfaceObject"); 84 | if (addToWindow) { 85 | moduleTree.scriptItemList.push(codeGen.addToWindowTree(theInterface.name)); 86 | } 87 | 88 | return codeGen.treeToString(moduleTree); 89 | }; 90 | 91 | function getInterfaceAndImplemented(webIdlString) { 92 | const idlTree = parseWebIDL(webIdlString); 93 | if (idlTree.length === 0) { 94 | throw new Error("IDL file must contain an interface"); 95 | } 96 | 97 | const implementsNodes = idlTree.filter(n => n.type === "implements"); 98 | const rest = idlTree.filter(n => n.type !== "implements"); 99 | 100 | if (rest.length > 1) { 101 | throw new Error("IDL file must contain only a single interface, potentially with implements statements"); 102 | } 103 | const theInterface = rest[0]; 104 | if (theInterface.type !== "interface") { 105 | throw new Error("IDL file must contain an interface, not a " + theInterface.type); 106 | } 107 | 108 | if (theInterface.partial) { 109 | throw new Error("IDL file must not contain a partial interface"); 110 | } 111 | 112 | const implemented = implementsNodes.map(n => { 113 | if (n.target !== theInterface.name) { 114 | throw new Error("IDL file must only contain implements statements for the single interface present"); 115 | } 116 | return n.implements; 117 | }); 118 | 119 | return { theInterface, implemented }; 120 | } 121 | 122 | function getReflect(webIdlAttribute) { 123 | const reflectXAttr = webIdlAttribute.extAttrs.find(xa => xa.name === "Reflect"); 124 | if (!reflectXAttr) { 125 | return { shouldReflect: false }; 126 | } 127 | 128 | if (reflectXAttr.rhs) { 129 | return { shouldReflect: true, as: reflectXAttr.rhs.value.replace(/_/g, '-') }; 130 | } 131 | 132 | return { shouldReflect: true }; 133 | } 134 | 135 | function hasReflect(attribute) { 136 | return attribute.extAttrs.some(xa => xa.name === "Reflect"); 137 | } 138 | 139 | function hasNoConversion(attribute) { 140 | return attribute.extAttrs.some(xa => xa.name === "NoConversion"); 141 | } 142 | 143 | function defaultImplements(interfaceName) { 144 | return `./${interfaceName}.js`; 145 | } 146 | -------------------------------------------------------------------------------- /lib/traceur-parsing.js: -------------------------------------------------------------------------------- 1 | // This file wraps Traceur's PlaceholderParser helpers so that they don't cache. This is only necessary because we are 2 | // doing a bad thing and mutating ASTs frequently. See discussion at 3 | // https://github.com/google/traceur-compiler/issues/1407 4 | 5 | const PlaceholderParser = require("traceur").get("codegeneration/PlaceholderParser.js"); 6 | const { CloneTreeTransformer: { cloneTree } } = require("traceur").get("codegeneration/CloneTreeTransformer.js"); 7 | 8 | export const parseModule = makeCloningParser("parseModule"); 9 | export const parseStatement = makeCloningParser("parseStatement"); 10 | export const parsePropertyDefinition = makeCloningParser("parsePropertyDefinition"); 11 | 12 | export function parseStatements(...args) { 13 | return PlaceholderParser.parseStatements(...args).map(cloneTree); 14 | } 15 | 16 | function makeCloningParser(parserMethodName) { 17 | return (...args) => cloneTree(PlaceholderParser[parserMethodName](...args)); 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webidl-class-generator", 3 | "version": "1.6.2", 4 | "description": "Generates classes from WebIDL plus implementation code", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "mocha --compilers js:mocha-traceur test/*.js" 8 | }, 9 | "repository": "domenic/webidl-class-generator", 10 | "keywords": [ 11 | "webidl", 12 | "es6", 13 | "class", 14 | "interface", 15 | "ecmascript", 16 | "dom" 17 | ], 18 | "files": [ 19 | "lib/" 20 | ], 21 | "author": "Domenic Denicola (https://domenic.me/)", 22 | "license": "BSD-2-Clause", 23 | "devDependencies": { 24 | "glob": "^4.0.6", 25 | "mocha": "^1.21.4", 26 | "mocha-traceur": "^2.1.0" 27 | }, 28 | "dependencies": { 29 | "traceur": "0.0.79", 30 | "webidl2": "^2.0.6" 31 | }, 32 | "traceur-runner": true 33 | } 34 | -------------------------------------------------------------------------------- /scripts/generate-class.js: -------------------------------------------------------------------------------- 1 | // Usage: `node scripts/generate-class.js < test/cases/html-hr-element.idl` 2 | 3 | require("traceur").require.makeDefault(function (filename) { 4 | return filename.indexOf("node_modules") === -1; 5 | }); 6 | 7 | const generate = require("..").default; 8 | 9 | let input = ""; 10 | process.stdin.on("data", function (data) { 11 | input += data; 12 | }); 13 | process.stdin.on("end", function () { 14 | process.stdout.write(generate(input)); 15 | }); 16 | -------------------------------------------------------------------------------- /scripts/traceur-tree.js: -------------------------------------------------------------------------------- 1 | // Usage: `node scripts/traceur-tree test/cases/empty-interface.js` will output the Traceur tree for the given ES6 2 | // file. You can then use this to guide your tree-generation logic. 3 | 4 | "use strict"; 5 | const fs = require("fs"); 6 | const traceur = require("traceur"); 7 | const util = require("util"); 8 | const ErrorReporter = traceur.util.ErrorReporter; 9 | const SourceFile = traceur.syntax.SourceFile; 10 | const Parser = traceur.syntax.Parser; 11 | 12 | const filename = process.argv[2]; 13 | const contents = fs.readFileSync(filename, { encoding: "utf8" }); 14 | 15 | const errorReporter = new ErrorReporter(); 16 | const sourceFile = new SourceFile(filename, contents); 17 | const parser = new Parser(sourceFile, errorReporter); 18 | const tree = parser.parseModule(); 19 | 20 | console.log(util.inspect(tree.toJSON().scriptItemList, { depth: Infinity, colors: true })); 21 | -------------------------------------------------------------------------------- /test/cases/comments.idl: -------------------------------------------------------------------------------- 1 | // Comment above 2 | interface Comments { 3 | /* before */ readonly attribute DOMString bar; 4 | }; 5 | // Comment after 6 | -------------------------------------------------------------------------------- /test/cases/comments.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./comments-impl.js"; 3 | export default class Comments { 4 | get bar() { 5 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "bar").get; 6 | const implResult = implGetter.call(this); 7 | return conversions["DOMString"](implResult); 8 | } 9 | } 10 | window.Comments = Comments; 11 | -------------------------------------------------------------------------------- /test/cases/constants.idl: -------------------------------------------------------------------------------- 1 | interface Constants { 2 | const unsigned short ZERO = 0; 3 | const unsigned long ONE = 0x1; 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/constants.js: -------------------------------------------------------------------------------- 1 | export default class Constants {} 2 | Object.defineProperty(Constants, "ZERO", { 3 | value: 0, 4 | enumerable: true 5 | }); 6 | Object.defineProperty(Constants.prototype, "ZERO", { 7 | value: 0, 8 | enumerable: true 9 | }); 10 | Object.defineProperty(Constants, "ONE", { 11 | value: 1, 12 | enumerable: true 13 | }); 14 | Object.defineProperty(Constants.prototype, "ONE", { 15 | value: 1, 16 | enumerable: true 17 | }); 18 | window.Constants = Constants; 19 | -------------------------------------------------------------------------------- /test/cases/custom-element-callbacks.idl: -------------------------------------------------------------------------------- 1 | [CustomElementCallbacks] 2 | interface CustomElementCallbacks {}; 3 | -------------------------------------------------------------------------------- /test/cases/custom-element-callbacks.js: -------------------------------------------------------------------------------- 1 | import Impl from "./custom-element-callbacks-impl.js"; 2 | export default class CustomElementCallbacks {} 3 | CustomElementCallbacks.prototype.createdCallback = Impl.prototype.createdCallback; 4 | CustomElementCallbacks.prototype.attachedCallback = Impl.prototype.attachedCallback; 5 | CustomElementCallbacks.prototype.detachedCallback = Impl.prototype.detachedCallback; 6 | CustomElementCallbacks.prototype.attributeChangedCallback = Impl.prototype.attributeChangedCallback; 7 | window.CustomElementCallbacks = CustomElementCallbacks; 8 | -------------------------------------------------------------------------------- /test/cases/empty-interface.idl: -------------------------------------------------------------------------------- 1 | interface EmptyInterface { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/empty-interface.js: -------------------------------------------------------------------------------- 1 | export default class EmptyInterface {} 2 | window.EmptyInterface = EmptyInterface; 3 | -------------------------------------------------------------------------------- /test/cases/html-hr-element.idl: -------------------------------------------------------------------------------- 1 | interface HTMLHRElement : HTMLElement { 2 | [Reflect] attribute DOMString align; 3 | [Reflect] attribute DOMString color; 4 | [Reflect] attribute boolean noShade; 5 | [Reflect] attribute DOMString size; 6 | [Reflect] attribute DOMString width; 7 | }; 8 | -------------------------------------------------------------------------------- /test/cases/html-hr-element.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import reflector from "webidl-html-reflector"; 3 | export default class HTMLHRElement extends HTMLElement { 4 | get align() { 5 | return reflector["DOMString"].get(this, "align"); 6 | } 7 | set align(v) { 8 | v = conversions["DOMString"](v); 9 | reflector["DOMString"].set(this, "align", v); 10 | } 11 | get color() { 12 | return reflector["DOMString"].get(this, "color"); 13 | } 14 | set color(v) { 15 | v = conversions["DOMString"](v); 16 | reflector["DOMString"].set(this, "color", v); 17 | } 18 | get noShade() { 19 | return reflector["boolean"].get(this, "noshade"); 20 | } 21 | set noShade(v) { 22 | v = conversions["boolean"](v); 23 | reflector["boolean"].set(this, "noshade", v); 24 | } 25 | get size() { 26 | return reflector["DOMString"].get(this, "size"); 27 | } 28 | set size(v) { 29 | v = conversions["DOMString"](v); 30 | reflector["DOMString"].set(this, "size", v); 31 | } 32 | get width() { 33 | return reflector["DOMString"].get(this, "width"); 34 | } 35 | set width(v) { 36 | v = conversions["DOMString"](v); 37 | reflector["DOMString"].set(this, "width", v); 38 | } 39 | } 40 | window.HTMLHRElement = HTMLHRElement; 41 | -------------------------------------------------------------------------------- /test/cases/implements.idl: -------------------------------------------------------------------------------- 1 | interface Implements { 2 | readonly attribute DOMString foo; 3 | }; 4 | 5 | Implements implements Mixin; 6 | -------------------------------------------------------------------------------- /test/cases/implements.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./implements-impl.js"; 3 | import Mixin from "./Mixin.js"; 4 | export default class Implements { 5 | get foo() { 6 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "foo").get; 7 | const implResult = implGetter.call(this); 8 | return conversions["DOMString"](implResult); 9 | } 10 | } 11 | Object.getOwnPropertyNames(Mixin.prototype).forEach((key) => { 12 | const propDesc = Object.getOwnPropertyDescriptor(Mixin.prototype, key); 13 | Object.defineProperty(Implements.prototype, key, propDesc); 14 | }); 15 | window.Implements = Implements; 16 | -------------------------------------------------------------------------------- /test/cases/inheritance.idl: -------------------------------------------------------------------------------- 1 | interface Inheritance : Base { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/inheritance.js: -------------------------------------------------------------------------------- 1 | export default class Inheritance extends Base {} 2 | window.Inheritance = Inheritance; 3 | -------------------------------------------------------------------------------- /test/cases/methods.idl: -------------------------------------------------------------------------------- 1 | interface Methods { 2 | boolean theMethod(DOMString arg1, unsigned long arg2); 3 | 4 | void otherMethod(double otherMethodArg); 5 | }; 6 | -------------------------------------------------------------------------------- /test/cases/methods.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./methods-impl.js"; 3 | export default class Methods { 4 | theMethod(arg1, arg2) { 5 | arg1 = conversions["DOMString"](arg1); 6 | arg2 = conversions["unsigned long"](arg2); 7 | const implMethod = Impl.prototype.theMethod; 8 | const implResult = implMethod.call(this, arg1, arg2); 9 | return conversions["boolean"](implResult); 10 | } 11 | otherMethod(otherMethodArg) { 12 | otherMethodArg = conversions["double"](otherMethodArg); 13 | const implMethod = Impl.prototype.otherMethod; 14 | const implResult = implMethod.call(this, otherMethodArg); 15 | return conversions["void"](implResult); 16 | } 17 | } 18 | window.Methods = Methods; 19 | -------------------------------------------------------------------------------- /test/cases/multiple-attributes.idl: -------------------------------------------------------------------------------- 1 | interface MultipleAttributes { 2 | attribute unsigned long attribute1; 3 | readonly attribute DOMString attribute2; 4 | attribute short attribute3; 5 | }; 6 | -------------------------------------------------------------------------------- /test/cases/multiple-attributes.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./multiple-attributes-impl.js"; 3 | export default class MultipleAttributes { 4 | get attribute1() { 5 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "attribute1").get; 6 | const implResult = implGetter.call(this); 7 | return conversions["unsigned long"](implResult); 8 | } 9 | set attribute1(v) { 10 | v = conversions["unsigned long"](v); 11 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, "attribute1").set; 12 | implSetter.call(this, v); 13 | } 14 | get attribute2() { 15 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "attribute2").get; 16 | const implResult = implGetter.call(this); 17 | return conversions["DOMString"](implResult); 18 | } 19 | get attribute3() { 20 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "attribute3").get; 21 | const implResult = implGetter.call(this); 22 | return conversions["short"](implResult); 23 | } 24 | set attribute3(v) { 25 | v = conversions["short"](v); 26 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, "attribute3").set; 27 | implSetter.call(this, v); 28 | } 29 | } 30 | window.MultipleAttributes = MultipleAttributes; 31 | -------------------------------------------------------------------------------- /test/cases/no-argument-method.idl: -------------------------------------------------------------------------------- 1 | interface NoArgumentMethod { 2 | boolean theMethod(); 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/no-argument-method.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./no-argument-method-impl.js"; 3 | export default class NoArgumentMethod { 4 | theMethod() { 5 | const implMethod = Impl.prototype.theMethod; 6 | const implResult = implMethod.call(this); 7 | return conversions["boolean"](implResult); 8 | } 9 | } 10 | window.NoArgumentMethod = NoArgumentMethod; 11 | -------------------------------------------------------------------------------- /test/cases/no-conversion.idl: -------------------------------------------------------------------------------- 1 | interface NoConversion { 2 | [NoConversion] readonly attribute USVString origin; 3 | [NoConversion] attribute USVString protocol; 4 | [NoConversion, Reflect] attribute boolean foo; 5 | }; 6 | -------------------------------------------------------------------------------- /test/cases/no-conversion.js: -------------------------------------------------------------------------------- 1 | import reflector from "webidl-html-reflector"; 2 | import Impl from "./no-conversion-impl.js"; 3 | export default class NoConversion { 4 | get origin() { 5 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "origin").get; 6 | const implResult = implGetter.call(this); 7 | return implResult; 8 | } 9 | get protocol() { 10 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "protocol").get; 11 | const implResult = implGetter.call(this); 12 | return implResult; 13 | } 14 | set protocol(v) { 15 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, "protocol").set; 16 | implSetter.call(this, v); 17 | } 18 | get foo() { 19 | return reflector["boolean"].get(this, "foo"); 20 | } 21 | set foo(v) { 22 | reflector["boolean"].set(this, "foo", v); 23 | } 24 | } 25 | window.NoConversion = NoConversion; 26 | -------------------------------------------------------------------------------- /test/cases/no-interface-object.idl: -------------------------------------------------------------------------------- 1 | [NoInterfaceObject] 2 | interface NotOnWindow {}; 3 | -------------------------------------------------------------------------------- /test/cases/no-interface-object.js: -------------------------------------------------------------------------------- 1 | export default class NotOnWindow {} 2 | -------------------------------------------------------------------------------- /test/cases/readonly-attribute.idl: -------------------------------------------------------------------------------- 1 | interface ReadonlyAttribute { 2 | readonly attribute DOMString theAttribute; 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/readonly-attribute.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./readonly-attribute-impl.js"; 3 | export default class ReadonlyAttribute { 4 | get theAttribute() { 5 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "theAttribute").get; 6 | const implResult = implGetter.call(this); 7 | return conversions["DOMString"](implResult); 8 | } 9 | } 10 | window.ReadonlyAttribute = ReadonlyAttribute; 11 | -------------------------------------------------------------------------------- /test/cases/reflect-attributes.idl: -------------------------------------------------------------------------------- 1 | interface ReflectAttributes { 2 | [Reflect] readonly attribute unsigned long attribute1; 3 | [Reflect] attribute DOMString attribute2; 4 | [Reflect] readonly attribute boolean attribute3; 5 | }; 6 | -------------------------------------------------------------------------------- /test/cases/reflect-attributes.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import reflector from "webidl-html-reflector"; 3 | export default class ReflectAttributes { 4 | get attribute1() { 5 | return reflector["unsigned long"].get(this, "attribute1"); 6 | } 7 | get attribute2() { 8 | return reflector["DOMString"].get(this, "attribute2"); 9 | } 10 | set attribute2(v) { 11 | v = conversions["DOMString"](v); 12 | reflector["DOMString"].set(this, "attribute2", v); 13 | } 14 | get attribute3() { 15 | return reflector["boolean"].get(this, "attribute3"); 16 | } 17 | } 18 | window.ReflectAttributes = ReflectAttributes; 19 | -------------------------------------------------------------------------------- /test/cases/reflect-custom-name.idl: -------------------------------------------------------------------------------- 1 | interface ReflectCustomName { 2 | [Reflect=foo] attribute DOMString defaultFoo; 3 | [Reflect=with_underscores_as_dashes] readonly attribute unsigned long anotherAttribute; 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/reflect-custom-name.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import reflector from "webidl-html-reflector"; 3 | export default class ReflectCustomName { 4 | get defaultFoo() { 5 | return reflector["DOMString"].get(this, "foo"); 6 | } 7 | set defaultFoo(v) { 8 | v = conversions["DOMString"](v); 9 | reflector["DOMString"].set(this, "foo", v); 10 | } 11 | get anotherAttribute() { 12 | return reflector["unsigned long"].get(this, "with-underscores-as-dashes"); 13 | } 14 | } 15 | window.ReflectCustomName = ReflectCustomName; 16 | -------------------------------------------------------------------------------- /test/cases/reflect-readonly-attribute.idl: -------------------------------------------------------------------------------- 1 | interface ReflectReadonlyAttribute { 2 | [Reflect] readonly attribute unsigned long reflectMe; 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/reflect-readonly-attribute.js: -------------------------------------------------------------------------------- 1 | import reflector from "webidl-html-reflector"; 2 | export default class ReflectReadonlyAttribute { 3 | get reflectMe() { 4 | return reflector["unsigned long"].get(this, "reflectme"); 5 | } 6 | } 7 | window.ReflectReadonlyAttribute = ReflectReadonlyAttribute; 8 | -------------------------------------------------------------------------------- /test/cases/stringifier-attribute.idl: -------------------------------------------------------------------------------- 1 | interface StringifierAttribute { 2 | stringifier attribute USVString href; 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/stringifier-attribute.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./stringifier-attribute-impl.js"; 3 | export default class StringifierAttribute { 4 | get href() { 5 | const implGetter = Object.getOwnPropertyDescriptor(Impl.prototype, "href").get; 6 | const implResult = implGetter.call(this); 7 | return conversions["USVString"](implResult); 8 | } 9 | set href(v) { 10 | v = conversions["USVString"](v); 11 | const implSetter = Object.getOwnPropertyDescriptor(Impl.prototype, "href").set; 12 | implSetter.call(this, v); 13 | } 14 | toString() { 15 | return this.href; 16 | } 17 | } 18 | window.StringifierAttribute = StringifierAttribute; 19 | -------------------------------------------------------------------------------- /test/cases/stringifier-basic.idl: -------------------------------------------------------------------------------- 1 | interface StringifierBasic { 2 | stringifier; 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/stringifier-basic.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./stringifier-basic-impl.js"; 3 | export default class StringifierBasic { 4 | toString() { 5 | const implMethod = Impl.prototype.toString; 6 | const implResult = implMethod.call(this); 7 | return conversions["DOMString"](implResult); 8 | } 9 | } 10 | window.StringifierBasic = StringifierBasic; 11 | -------------------------------------------------------------------------------- /test/cases/stringifier-operation.idl: -------------------------------------------------------------------------------- 1 | interface StringifierOperation { 2 | boolean foo(); 3 | stringifier DOMString foo(); 4 | }; 5 | -------------------------------------------------------------------------------- /test/cases/stringifier-operation.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./stringifier-operation-impl.js"; 3 | export default class StringifierOperation { 4 | foo() { 5 | const implMethod = Impl.prototype.foo; 6 | const implResult = implMethod.call(this); 7 | return conversions["boolean"](implResult); 8 | } 9 | toString() { 10 | return conversions["DOMString"](this.foo()); 11 | } 12 | } 13 | window.StringifierOperation = StringifierOperation; 14 | -------------------------------------------------------------------------------- /test/cases/stringifier-with-return-type.idl: -------------------------------------------------------------------------------- 1 | interface StringifierWithReturnType { 2 | stringifier DOMString (); 3 | }; 4 | -------------------------------------------------------------------------------- /test/cases/stringifier-with-return-type.js: -------------------------------------------------------------------------------- 1 | import conversions from "webidl-conversions"; 2 | import Impl from "./stringifier-with-return-type-impl.js"; 3 | export default class StringifierWithReturnType { 4 | toString() { 5 | const implMethod = Impl.prototype.toString; 6 | const implResult = implMethod.call(this); 7 | return conversions["DOMString"](implResult); 8 | } 9 | } 10 | window.StringifierWithReturnType = StringifierWithReturnType; 11 | -------------------------------------------------------------------------------- /test/check-cases.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const fs = require("fs"); 3 | const glob = require("glob"); 4 | const path = require("path"); 5 | 6 | import generate from ".."; 7 | 8 | describe("Checking inputs against outputs", () => { 9 | glob.sync(path.resolve(__dirname, "cases/*.idl")).forEach(idlFilePath => { 10 | const idlFileName = path.basename(idlFilePath); 11 | const withoutExtension = path.basename(idlFilePath, ".idl"); 12 | const jsFileName = withoutExtension + ".js"; 13 | const implModuleName = `./${withoutExtension}-impl.js`; 14 | const jsFilePath = path.resolve(__dirname, "cases", jsFileName); 15 | 16 | const idlContents = fs.readFileSync(idlFilePath, { encoding: "utf-8" }); 17 | const jsContents = fs.readFileSync(jsFilePath, { encoding: "utf-8" }); 18 | 19 | specify(idlFileName, () => { 20 | assert.strictEqual(generate(idlContents, implModuleName), jsContents); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/check-errors.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const fs = require("fs"); 3 | const glob = require("glob"); 4 | const path = require("path"); 5 | 6 | import generate from ".."; 7 | 8 | describe("Checking that correct errors are thrown", () => { 9 | glob.sync(path.resolve(__dirname, "errors/*.idl")).forEach(idlFilePath => { 10 | const idlFileName = path.basename(idlFilePath); 11 | const errorFileName = path.basename(idlFilePath, ".idl") + ".txt"; 12 | const errorFilePath = path.resolve(__dirname, "errors", errorFileName); 13 | 14 | const idlContents = fs.readFileSync(idlFilePath, { encoding: "utf-8" }); 15 | const errrorContents = fs.readFileSync(errorFilePath, { encoding: "utf-8" }).trim(); 16 | 17 | specify(idlFileName, () => { 18 | assert.throws(() => generate(idlContents), er => er.message.includes(errrorContents)); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/errors/bad-implements.idl: -------------------------------------------------------------------------------- 1 | interface Foo {}; 2 | 3 | Bar implements Baz; 4 | -------------------------------------------------------------------------------- /test/errors/bad-implements.txt: -------------------------------------------------------------------------------- 1 | IDL file must only contain implements statements for the single interface present 2 | -------------------------------------------------------------------------------- /test/errors/empty.idl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domenic/webidl-class-generator/1f1d0d707c95ce2e6f68b2434886c3eb73c3a055/test/errors/empty.idl -------------------------------------------------------------------------------- /test/errors/empty.txt: -------------------------------------------------------------------------------- 1 | IDL file must contain an interface 2 | -------------------------------------------------------------------------------- /test/errors/multiple-interfaces.idl: -------------------------------------------------------------------------------- 1 | interface Foo { 2 | 3 | }; 4 | 5 | interface Bar { 6 | 7 | }; 8 | -------------------------------------------------------------------------------- /test/errors/multiple-interfaces.txt: -------------------------------------------------------------------------------- 1 | IDL file must contain only a single interface, potentially with implements statements 2 | -------------------------------------------------------------------------------- /test/errors/non-interface.idl: -------------------------------------------------------------------------------- 1 | dictionary Foo { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /test/errors/non-interface.txt: -------------------------------------------------------------------------------- 1 | IDL file must contain an interface, not a dictionary 2 | -------------------------------------------------------------------------------- /test/errors/partial-interface.idl: -------------------------------------------------------------------------------- 1 | partial interface Foo { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /test/errors/partial-interface.txt: -------------------------------------------------------------------------------- 1 | IDL file must not contain a partial interface 2 | --------------------------------------------------------------------------------