playwright/utils/generate_channels.js

211 lines
6.5 KiB
JavaScript
Executable File

#!/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 channels = new Set();
function tokenize(source) {
const lines = source.split('\n').filter(line => {
const trimmed = line.trim();
return !!trimmed && trimmed[0] != '#';
});
const stack = [{ indent: -1, list: [], words: '' }];
for (const line of lines) {
const indent = line.length - line.trimLeft().length;
const o = { indent, list: [], words: line.split(' ').filter(word => !!word) };
let current = stack[stack.length - 1];
while (indent <= current.indent) {
stack.pop();
current = stack[stack.length - 1];
}
current.list.push(o);
stack.push(o);
}
return stack[0].list;
}
function raise(item) {
throw new Error(item.words.join(' '));
}
function titleCase(name) {
return name[0].toUpperCase() + name.substring(1);
}
function inlineType(item, indent) {
let type = item.words[1];
const array = type.endsWith('[]');
if (array)
type = type.substring(0, type.length - 2);
let inner = '';
if (type === 'enum') {
const literals = item.list.map(literal => {
if (literal.words.length > 1 || literal.list.length)
raise(literal);
return literal.words[0];
});
inner = literals.map(literal => `'${literal}'`).join(' | ');
if (array)
inner = `(${inner})`;
} else if (['string', 'boolean', 'number', 'undefined'].includes(type)) {
inner = type;
} else if (type === 'object') {
inner = `{\n${properties(item, indent + ' ')}\n${indent}}`;
} else if (type === 'binary') {
inner = 'Binary';
} else if (type === 'Error') {
inner = 'SerializedError';
} else if (channels.has(type)) {
inner = type + 'Channel';
} else {
inner = type;
}
return inner + (array ? '[]' : '');
}
function properties(item, indent) {
const result = [];
for (const prop of item.list) {
if (prop.words.length !== 2)
raise(prop);
let name = prop.words[0];
if (!name.endsWith(':'))
raise(item);
name = name.substring(0, name.length - 1);
const optional = name.endsWith('?');
if (optional)
name = name.substring(0, name.length - 1);
result.push(`${indent}${name}${optional ? '?' : ''}: ${inlineType(prop, indent)},`);
}
return result.join('\n');
}
function objectType(name, item, indent) {
if (!item.list.length)
return `export type ${name} = {};`;
return `export type ${name} = {\n${properties(item, indent)}\n};`
}
const result = [
`/**
* 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 pdl = fs.readFileSync(path.join(__dirname, '..', 'src', 'rpc', 'protocol.pdl'), 'utf-8');
const list = tokenize(pdl);
for (const item of list) {
if (item.words[0] === 'interface')
channels.add(item.words[1]);
}
for (const item of list) {
if (item.words[0] === 'type') {
if (item.words.length !== 2)
raise(item);
result.push(`export type ${item.words[1]} = {`);
result.push(properties(item, ' '));
result.push(`};`);
} else if (item.words[0] === 'interface') {
const channelName = item.words[1];
result.push(`// ----------- ${channelName} -----------`);
const init = item.list.find(i => i.words[0] === 'initializer');
if (init && init.words.length > 1)
raise(init);
result.push(objectType(channelName + 'Initializer', init || { list: [] }, ' '));
let extendsName = 'Channel';
if (item.words.length === 4 && item.words[2] === 'extends')
extendsName = item.words[3] + 'Channel';
else if (item.words.length !== 2)
raise(item);
result.push(`export interface ${channelName}Channel extends ${extendsName} {`);
const types = new Map();
for (const method of item.list) {
if (method === init)
continue;
if (method.words[0] === 'command') {
if (method.words.length !== 2)
raise(method);
const methodName = method.words[1];
const parameters = method.list.find(i => i.words[0] === 'parameters');
const paramsName = `${channelName}${titleCase(methodName)}Params`;
types.set(paramsName, parameters || { list: [] });
const returns = method.list.find(i => i.words[0] === 'returns');
const resultName = `${channelName}${titleCase(methodName)}Result`;
types.set(resultName, returns);
result.push(` ${methodName}(params${parameters ? '' : '?'}: ${paramsName}): Promise<${resultName}>;`);
} else if (method.words[0] === 'event') {
if (method.words.length !== 2)
raise(method);
const eventName = method.words[1];
const parameters = method.list.find(i => i.words[0] === 'parameters');
const paramsName = `${channelName}${titleCase(eventName)}Event`;
types.set(paramsName, parameters || { list: [] });
result.push(` on(event: '${eventName}', callback: (params: ${paramsName}) => void): this;`);
} else {
raise(method);
}
}
result.push(`}`);
for (const [name, item] of types) {
if (!item)
result.push(`export type ${name} = void;`);
else
result.push(objectType(name, item, ' '));
}
} else {
raise(item);
}
result.push(``);
}
fs.writeFileSync(path.join(__dirname, '..', 'src', 'rpc', 'channels.ts'), result.join('\n'), 'utf-8');