Convert generation script to TS, and update the source webidl#383
Convert generation script to TS, and update the source webidl#383
Conversation
package.json
Outdated
| "main": "index.js", | ||
| "scripts": { | ||
| "build": "tsc --p ./tsconfig.json && node ./lib/index.js", | ||
| "baseline-accept": "copy /Y generated\\* baselines\\", |
There was a problem hiding this comment.
copy won't work on linux, could you use a node script for this instead?
src/helpers.ts
Outdated
| export function filter(obj: any, fn: (o: any, n: string | undefined) => boolean) { | ||
| if (typeof obj === "object") { | ||
| if (Array.isArray(obj)) { | ||
| const newArray: any[] = []; |
There was a problem hiding this comment.
return mapDefined(obj, e => fn(e, undefined) ? filter(e, fn) : undefined);
| return src; | ||
| } | ||
|
|
||
| function mergeNamedArrays<T extends { name: string }>(srcProp: T[], targetProp: T[]) { |
There was a problem hiding this comment.
This is only ever called on an any[], so might as well just make the args { name: string }[]...
There was a problem hiding this comment.
i do not think there is harm from the extra polymorphism
src/index.ts
Outdated
| import * as path from "path"; | ||
| import { filter, merge, filterProperties, mapToArray, distinct, map, toNameMap } from "./helpers"; | ||
|
|
||
| enum Flavor { |
src/index.ts
Outdated
| function getEventHandler(i: Browser.Interface) { | ||
| const ownEventHandler = | ||
| i.properties | ||
| ? mapToArray(i.properties.property).filter(p => p["event-handler"]).map(p => ({ |
There was a problem hiding this comment.
Nit: map<EventHandler> or there is no contextual type.
There was a problem hiding this comment.
This could be:
return !i.properties ? [] : mapDefined<Browser.Property, EventHandler>(mapToArray(i.properties.property), p => {
const eventName = p["event-handler"]!;
if (eventName === undefined) return undefined;
const eType = eNameToEType[eventName] || defaultEventType;
const eventType = eType === "Event" || dependsOn(eType, "Event") ? eType : defaultEventType;
return { name: p.name, eventName, eventType };
});
src/index.ts
Outdated
| // Some params have the type of "(DOMString or DOMString [] or Number)" | ||
| // we need to transform it into [“DOMString", "DOMString []", "Number"] | ||
| function decomposeTypes(t: string) { | ||
| return t.replace(/[\(\)]/g, "").split(" or "); |
There was a problem hiding this comment.
Maybe to be a bit safer:
if (t[0] !== "(") return [t];
assert(t[t.length - 1] === ")");
return t.slice(0, t.length - 1).split(" or ");There was a problem hiding this comment.
We should remove this code path altogether anyways, just leaving it for now until we consume other webidl files.
src/index.ts
Outdated
| return t.replace(/[\(\)]/g, "").split(" or "); | ||
| } | ||
|
|
||
| function createTextWriter(newLine: string) { |
There was a problem hiding this comment.
Might be better to print to nodes eventually?
There was a problem hiding this comment.
do not think so. so far our transformations are not that complicated..
src/index.ts
Outdated
| } | ||
|
|
||
| function makeArrayType(elementType: string): string { | ||
| return elementType.indexOf("|") > -1 ? `(${elementType})[]` : `${elementType}[]`; |
src/index.ts
Outdated
| } | ||
|
|
||
| function convertDomTypeToTsTypeWorker(obj: Browser.Typed): { name: string; nullable: boolean } { | ||
| let type; |
There was a problem hiding this comment.
Looks like obj["type-original"] is never used?
This function seems to be equivalent to:
if (typeof obj.type === "string") {
const name = covertDomTypeToTsTypeSimple(obj.type);
const typeArg = obj.subtype && convertDomTypeToTsType(obj.subtype);
return {
name: typeArg ? name === "Array" ? makeArrayType(typeArg) : `${name}<${typeArg}>` : name,
nullable: !!obj.nullable,
};
}
else {
assert(!obj.subtype);
const types = obj.type.map(convertDomTypeToTsTypeWorker);
return {
name: types.map(t => t.name).join(" | "),
nullable: types.some(t => t.nullable),
};
}There was a problem hiding this comment.
yes. i wanted to leave it for now to see what other idl files have
There was a problem hiding this comment.
Could still apply the above refactoring though?
src/index.ts
Outdated
| } | ||
|
|
||
| function covertDomTypeToTsTypeSimple(objDomType: string): string { | ||
| if (typeof objDomType !== "string") { |
|
@weswigham and @Andy-MS any more comments? |
|
Btw, just wanted to point out (not sure if relevant) that WebIDL is seeing many changes lately after being abandoned for quite some years. This happens on the editors draft though and it not in stable yet: |
* Fixes in preparation of lib changes See microsoft/TypeScript-DOM-lib-generator#383 * Fix breakes * revert changes to knuddels-userapps-api * Fix compressedTexSubImage2D overload * Fix event emitter methods * Remove duplicate declaration * Fix ScreenOrientation.type
src/index.ts
Outdated
| import { Flavor, emitWebIDl } from "./emitter"; | ||
|
|
||
| function emitDomWorker(webidl: Browser.WebIdl, knownWorkerTypes: Set<string>, tsWorkerOutput: string) { | ||
| const worker: Browser.WebIdl = { |
There was a problem hiding this comment.
This is an odd name, would call it converted. Also, this initializer is identical to const result: Browser.WebIdl later, so could factor those out to a getEmptyWebIdl() function.
There was a problem hiding this comment.
it is the filtered webidl file to represent the webworker APIs, simiar to the other one, browser.
|
|
||
| const result = emitWebIDl(worker, Flavor.Worker); | ||
| fs.writeFileSync(tsWorkerOutput, result); | ||
| return; |
There was a problem hiding this comment.
it is intentinal to get an error if i have any code mixed with the helpers.
|
|
||
| const result = emitWebIDl(browser, Flavor.Web); | ||
| fs.writeFileSync(tsWebOutput, result); | ||
| return; |
src/index.ts
Outdated
|
|
||
| if (obj["callback-functions"]) result["callback-functions"]!["callback-function"] = filterProperties(obj["callback-functions"]!["callback-function"], (cb) => !(template["callback-functions"] && template["callback-functions"]!["callback-function"][cb.name])); | ||
| if (obj["callback-interfaces"]) result["callback-interfaces"]!.interface = filterInterface(obj["callback-interfaces"]!.interface, template["callback-interfaces"] && template["callback-interfaces"]!.interface); | ||
| if (obj.dictionaries) result.dictionaries!.dictionary = filterDictinary(obj.dictionaries.dictionary, template.dictionaries && template.dictionaries.dictionary); |
There was a problem hiding this comment.
typo: dictionary (in filterDictinary)
Also, this could be if (obj.dictionaries && template.dictionaries) instead of having the null check at the start of filterDictinary
There was a problem hiding this comment.
I need to assign it something, so having them in the functions seems easier to read.
src/emitter.ts
Outdated
| "AudioContext": { extendType: ["OfflineContext"], memberNames: new Set(["suspend"]) }, | ||
| "HTMLCollection": { extendType: ["HTMLFormControlsCollection"], memberNames: new Set(["namedItem"]) }, | ||
| }; | ||
| const evetTypeMap: Record<string, string> = { |
src/emitter.ts
Outdated
| function emitProperties(prefix: string, emitScope: EmitScope, i: Browser.Interface, conflictedMembers: Set<string>) { | ||
| // Note: the schema file shows the property doesn't have "static" attribute, | ||
| // therefore all properties are emited for the instance type. | ||
| if (emitScope !== EmitScope.StaticOnly) { |
src/emitter.ts
Outdated
| function emitMethods(prefix: string, emitScope: EmitScope, i: Browser.Interface, conflictedMembers: Set<string>) { | ||
| // If prefix is not empty, then this is the global declare function addEventListener, we want to override this | ||
| // Otherwise, this is EventTarget.addEventListener, we want to keep that. | ||
| function mFilter(m: Browser.Method) { |
| } | ||
|
|
||
| /// Emit the properties and methods of a given interface | ||
| function emitMembers(prefix: string, emitScope: EmitScope, i: Browser.Interface) { |
There was a problem hiding this comment.
Would move prefix to the end and make it optional.
Also, the only prefix value currently (besides "") is "declare var", so maybe the parameter should be just inNamespace = false.
There was a problem hiding this comment.
yeah. i need to do this for all prefixes.. but will do this separately. no need to pass a string in.
| } | ||
| } | ||
|
|
||
| function emitEventHandlers(prefix: string, i: Browser.Interface) { |
There was a problem hiding this comment.
function emitEventHandlers(i: Browser.Interface, inNamespace = false) {
for (const addOrRemove of ["add", "remove"]) {
const optionsType = addOrRemove === "add" ? "AddEventListenerOptions" : "EventListenerOptions";
const fPrefix = inNamespace ? "declare function " : "";
if (tryEmitTypedEventHandlerForInterface()) {
// only emit the string event handler if we just emited a typed handler
printer.printLine(`${fPrefix}${addOrRemove}EventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | ${optionsType}): void;`);
}
function emitTypedEventHandler(iParent: Browser.Interface) {
printer.printLine(`${fPrefix}${addOrRemove}EventListener<K extends keyof ${iParent.name}EventMap>(type: K, listener: (this: ${i.name}, ev: ${iParent.name}EventMap[K]) => any, options?: boolean | ${optionsType}): void;`);
}
function tryEmitTypedEventHandlerForInterface() {
if (iNameToEhList[i.name] && iNameToEhList[i.name].length) {
emitTypedEventHandler(i);
return true;
}
if (iNameToEhParents[i.name] && iNameToEhParents[i.name].length) {
iNameToEhParents[i.name]
.sort(compareName)
.forEach(emitTypedEventHandler);
return true;
}
return false;
}
}
}There was a problem hiding this comment.
not sure how i feel about functions declared in a for loop. will refactor though.
| } | ||
|
|
||
| function emitConstructorSignature(i: Browser.Interface) { | ||
| const constructor = typeof i.constructor === "object" ? i.constructor : undefined; |
There was a problem hiding this comment.
Was there some bad input this is avoiding?
There was a problem hiding this comment.
no this is cause all objects have a constructor property on them :D
|
@mhegazy seems that this PR change the type of |
|
PRs welcomed. |
This change includes two parts:
A note about methodology:
I have converted the original F# script to TS first, maintain the input file source, and testing as I went to ensure the output was the same. when that was complete, i then plugged in the new file definition file and continued from there. Unfortunately the new input file is different enough from the new one that it made splitting this change into two more expensive.
A note for reviewers:
There are two things to look out for, first and foremost the changes in the generated files, and second the generation script.
Testing: