playwright/utils/generate_channels.js

221 lines
8.1 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const yaml = require('yaml');
const channels = new Set();
function raise(item) {
throw new Error('Invalid item: ' + JSON.stringify(item, null, 2));
}
function titleCase(name) {
return name[0].toUpperCase() + name.substring(1);
}
function inlineType(type, indent, wrapEnums = false) {
if (typeof type === 'string') {
const optional = type.endsWith('?');
if (optional)
type = type.substring(0, type.length - 1);
if (type === 'binary')
return { ts: 'Binary', scheme: 'tBinary', optional };
if (['string', 'boolean', 'number', 'undefined'].includes(type))
return { ts: type, scheme: `t${titleCase(type)}`, optional };
if (channels.has(type))
return { ts: `${type}Channel`, scheme: `tChannel('${type}')` , optional };
if (type === 'Channel')
return { ts: `Channel`, scheme: `tChannel('*')`, optional };
return { ts: type, scheme: `tType('${type}')`, optional };
}
if (type.type.startsWith('array')) {
const optional = type.type.endsWith('?');
const inner = inlineType(type.items, indent, true);
return { ts: `${inner.ts}[]`, scheme: `tArray(${inner.scheme})`, optional };
}
if (type.type.startsWith('enum')) {
const optional = type.type.endsWith('?');
const ts = type.literals.map(literal => `'${literal}'`).join(' | ');
return {
ts: wrapEnums ? `(${ts})` : ts,
scheme: `tEnum([${type.literals.map(literal => `'${literal}'`).join(', ')}])`,
optional
};
}
if (type.type.startsWith('object')) {
const optional = type.type.endsWith('?');
const inner = properties(type.properties, indent + ' ');
return {
ts: `{\n${inner.ts}\n${indent}}`,
scheme: `tObject({\n${inner.scheme}\n${indent}})`,
optional
};
}
raise(type);
}
function properties(props, indent) {
const ts = [];
const scheme = [];
for (const [name, value] of Object.entries(props)) {
const inner = inlineType(value, indent);
ts.push(`${indent}${name}${inner.optional ? '?' : ''}: ${inner.ts},`);
const wrapped = inner.optional ? `tOptional(${inner.scheme})` : inner.scheme;
scheme.push(`${indent}${name}: ${wrapped},`);
}
return { ts: ts.join('\n'), scheme: scheme.join('\n') };
}
function objectType(props, indent) {
if (!Object.entries(props).length)
return { ts: `{}`, scheme: `tObject({})` };
const inner = properties(props, indent + ' ');
return { ts: `{\n${inner.ts}\n${indent}}`, scheme: `tObject({\n${inner.scheme}\n${indent}})` };
}
const channels_ts = [
`/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file is generated by ${path.basename(__filename)}, do not edit manually.
import { EventEmitter } from 'events';
export type Binary = string;
export interface Channel extends EventEmitter {
}
`];
const client_validator_ts = [
`/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file is generated by ${path.basename(__filename)}, do not edit manually.
import { scheme, tOptional, tObject, tBoolean, tNumber, tString, tType, tEnum, tArray, tChannel, tUndefined, tBinary } from './validatorPrimitives';
export { validateParams } from './validatorPrimitives';
`];
const yml = fs.readFileSync(path.join(__dirname, '..', 'src', 'rpc', 'protocol.yml'), 'utf-8');
const protocol = yaml.parse(yml);
function addScheme(name, s) {
client_validator_ts.push(`scheme.${name} = ${s};`);
}
const inherits = new Map();
for (const [name, value] of Object.entries(protocol)) {
if (value.type === 'interface') {
channels.add(name);
if (value.extends)
inherits.set(name, value.extends);
}
}
for (const [name, item] of Object.entries(protocol)) {
if (item.type === 'interface') {
const channelName = name;
channels_ts.push(`// ----------- ${channelName} -----------`);
const init = objectType(item.initializer || {}, '');
const initializerName = channelName + 'Initializer';
channels_ts.push(`export type ${initializerName} = ${init.ts};`);
addScheme(initializerName, init.scheme);
channels_ts.push(`export interface ${channelName}Channel extends ${(item.extends || '') + 'Channel'} {`);
const ts_types = new Map();
for (let [eventName, event] of Object.entries(item.events || {})) {
if (event === null)
event = {};
const parameters = objectType(event.parameters || {}, '');
const paramsName = `${channelName}${titleCase(eventName)}Event`;
ts_types.set(paramsName, parameters.ts);
addScheme(paramsName, parameters.scheme);
channels_ts.push(` on(event: '${eventName}', callback: (params: ${paramsName}) => void): this;`);
for (const key of inherits.keys()) {
if (inherits.get(key) === channelName)
addScheme(`${key}${titleCase(eventName)}Event`, `tType('${paramsName}')`);
}
}
for (let [methodName, method] of Object.entries(item.commands || {})) {
if (method === null)
method = {};
const parameters = objectType(method.parameters || {}, '');
const paramsName = `${channelName}${titleCase(methodName)}Params`;
ts_types.set(paramsName, parameters.ts);
addScheme(paramsName, method.parameters ? parameters.scheme : `tOptional(tObject({}))`);
const resultName = `${channelName}${titleCase(methodName)}Result`;
const returns = objectType(method.returns || {}, '');
ts_types.set(resultName, method.returns ? returns.ts : 'void');
addScheme(resultName, method.returns ? returns.scheme : `tUndefined`);
channels_ts.push(` ${methodName}(params${method.parameters ? '' : '?'}: ${paramsName}): Promise<${resultName}>;`);
for (const key of inherits.keys()) {
if (inherits.get(key) === channelName) {
addScheme(`${key}${titleCase(methodName)}Params`, `tType('${paramsName}')`);
addScheme(`${key}${titleCase(methodName)}Result`, `tType('${resultName}')`);
}
}
}
channels_ts.push(`}`);
for (const [typeName, typeValue] of ts_types)
channels_ts.push(`export type ${typeName} = ${typeValue};`);
} else if (item.type === 'object') {
const inner = objectType(item.properties, '');
channels_ts.push(`export type ${name} = ${inner.ts};`);
addScheme(name, inner.scheme);
}
channels_ts.push(``);
}
client_validator_ts.push(``);
fs.writeFileSync(path.join(__dirname, '..', 'src', 'rpc', 'channels.ts'), channels_ts.join('\n'), 'utf-8');
fs.writeFileSync(path.join(__dirname, '..', 'src', 'rpc', 'client', 'validator.ts'), client_validator_ts.join('\n'), 'utf-8');