findFilms/node_modules/webdriver-bidi-protocol/tools/generateCommandMap.ts

162 lines
4.2 KiB
TypeScript

/**
* @license
* Copyright 2025 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
import {Project, Type, TypeFormatFlags} from 'ts-morph';
import * as path from 'path';
import {
getResultNameFromMethod,
getTypeInNamespaceOrThrow,
type MappingInterface,
type SpecType,
} from './utils.ts';
const rootDir = path.resolve(import.meta.dirname, '..');
const MAIN_SPEC_PREFIX = 'Bidi';
const specs: SpecType[] = [
{
inputFile: './main.ts',
commandType: 'CommandData',
modulePrefix: MAIN_SPEC_PREFIX,
},
{
inputFile: './permissions.ts',
commandType: 'PermissionsCommand',
modulePrefix: 'BidiPermissions',
},
{
inputFile: './web-bluetooth.ts',
commandType: 'BluetoothCommand',
modulePrefix: 'BidiBluetooth',
},
];
const project = new Project({
tsConfigFilePath: path.resolve(rootDir, 'tsconfig.json'),
});
const commandMappingEntries: MappingInterface[] = [];
for (const spec of specs) {
const apiIndexFile = project.addSourceFileAtPath(
path.resolve(rootDir, 'src/gen', spec.inputFile),
);
// Allow other name
const commandType = apiIndexFile.getTypeAliasOrThrow(spec.commandType);
const unionType = commandType.getType();
let types: Type[];
if (unionType.isUnion()) {
types = unionType.getUnionTypes();
} else {
types = [commandType.getTypeNodeOrThrow().getType()];
}
for (const unionMember of types) {
const methodProp = unionMember.getProperty('method');
if (!methodProp) {
// If not method is found continue.
// For some reason the Bluetooth spec has Record<string,never>
// TODO: fix it upstream
continue;
}
const methodType = methodProp.getTypeAtLocation(commandType);
if (!methodType.isStringLiteral()) {
throw new Error(`Non string found ${methodProp.getName()}`);
}
const methodString = `${methodType.getLiteralValue()}`;
const paramsProp = unionMember.getPropertyOrThrow('params');
const paramsType = paramsProp.getTypeAtLocation(commandType);
const paramsTypeString = paramsType.getText(
commandType,
TypeFormatFlags.None,
);
let prefix = spec.modulePrefix;
let expectedResultTypeName = paramsTypeString.replace(
'Parameters',
'Result',
);
// We need to infer from methods
// TODO: See if this is needed as a fallback always
if (paramsTypeString.includes('Extensible')) {
expectedResultTypeName = getResultNameFromMethod(methodString);
}
try {
// Usually we get something like `BrowsingContext.GetTreeResult`
getTypeInNamespaceOrThrow(apiIndexFile, expectedResultTypeName);
} catch {
try {
// Maybe it was not inside an Namespace try on the module scope
apiIndexFile.getTypeAliasOrThrow(expectedResultTypeName);
} catch {
// The EmptyResult is only available on the main spec
prefix = MAIN_SPEC_PREFIX;
// Default to EmptyResult
expectedResultTypeName = `EmptyResult`;
}
}
commandMappingEntries.push({
method: methodString,
params: `${spec.modulePrefix}.${paramsTypeString}`,
resultType: `${prefix}.${expectedResultTypeName}`,
});
}
}
// Start generating the mapping types
const outputPath = path.resolve(rootDir, 'src/gen/mapping.ts');
const generatedFile = project.createSourceFile(outputPath, '', {
overwrite: true,
});
for (const spec of specs) {
generatedFile.addImportDeclaration({
moduleSpecifier: spec.inputFile.replace('.ts', '.js'),
isTypeOnly: true,
namespaceImport: spec.modulePrefix,
});
}
const mapInterface = generatedFile.addInterface({
name: 'Commands',
isExported: true,
});
const sortedCommandMappingEntries = commandMappingEntries.sort((a, b) => {
if (a.method > b.method) {
return 1;
} else if (a.method < b.method) {
return -1;
}
return 0;
});
for (const entry of sortedCommandMappingEntries) {
mapInterface.addProperty({
// Wrap in quotes as we use <module>.<command-name>
// syntax
name: `"${entry.method}"`,
type: writer => {
writer.write(`
{
params: ${entry.params};
returnType: ${entry.resultType};
}
`);
},
});
}
await generatedFile.save();