UNPKG

webdriver-bidi-protocol

Version:

This repository contains TypeScript types conforming to the [WebDriver BiDi](https://w3c.github.io/webdriver-bidi/) specification. It also supports WebDriver BiDi extension modules defined in other specifications:

148 lines (126 loc) 3.8 kB
/** * @license * Copyright 2025 Google Inc. * SPDX-License-Identifier: Apache-2.0 */ import {Project, Type, TypeFormatFlags} from 'ts-morph'; import * as path from 'path'; import { getTypeInNamespaceOrThrow, type MappingInterface, type SpecType, } from './utils.ts'; const rootDir = path.resolve(import.meta.dirname, '..'); const specs: SpecType[] = [ { inputFile: './main.ts', commandType: 'CommandData', modulePrefix: 'Bidi', }, { 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 expectedResultTypeName = paramsTypeString.replace('Params', 'Result'); 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 { // Default to EmptyResult expectedResultTypeName = `EmptyResult`; } } commandMappingEntries.push({ method: methodString, params: `${spec.modulePrefix}.${paramsTypeString}`, resultType: `${spec.modulePrefix}.${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, isTypeOnly: true, namespaceImport: spec.modulePrefix, }); } const mapInterface = generatedFile.addInterface({ name: 'CommandMapping', 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();