-
Notifications
You must be signed in to change notification settings - Fork 425
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy
won't work on linux, could you use a node script for this instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: const enum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: map<EventHandler>
or there is no contextual type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 };
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better to print to nodes eventually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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}[]`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.includes("|")
src/index.ts
Outdated
} | ||
|
||
function convertDomTypeToTsTypeWorker(obj: Browser.Typed): { name: string; nullable: boolean } { | ||
let type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. i wanted to leave it for now to see what other idl files have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could still apply the above refactoring though?
src/index.ts
Outdated
} | ||
|
||
function covertDomTypeToTsTypeSimple(objDomType: string): string { | ||
if (typeof objDomType !== "string") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this debugging code?
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: event
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: double if
could be replaced with &&
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would inline this fn
} | ||
|
||
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure how i feel about functions declared in a for loop. will refactor though.
src/emitter.ts
Outdated
} | ||
|
||
function emitConstructorSignature(i: Browser.Interface) { | ||
const constructor = typeof i.constructor === "object" ? i.constructor : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there some bad input this is avoiding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no this is cause all objects have a constructor
property on them :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah 🤦♂️
@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: