react-native
Version:
A framework for building native apps using React
252 lines (208 loc) • 6.47 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import fs from 'fs';
import yargs from 'yargs';
import {Command} from './Command';
import {Event} from './Event';
import {Graph} from './Graph';
import {Property} from './Property';
import {PropsType, Type} from './Type';
import {HeaderWriter} from './HeaderWriter';
import {ImplementationWriter} from './ImplementationWriter';
// $FlowFixMe[cannot-resolve-module] : this isn't a module, just a JSON file.
const standard = require('devtools-protocol/json/js_protocol.json');
const custom = require('../src/custom.json');
type Descriptor = {|
types: Array<Type>,
commands: Array<Command>,
events: Array<Event>,
|};
function mergeDomains(original, extra) {
return {...original, domains: original.domains.concat(extra.domains)};
}
const proto = mergeDomains(standard, custom);
function parseDomains(
domainObjs: Array<any>,
ignoreExperimental: boolean,
includeExperimental: Set<string>,
): Descriptor {
const desc = {
types: [],
commands: [],
events: [],
};
for (const obj of domainObjs) {
const domain = obj.domain;
for (const typeObj of obj.types || []) {
const type = Type.create(domain, typeObj, ignoreExperimental);
if (type) {
desc.types.push(type);
}
}
for (const commandObj of obj.commands || []) {
const command = Command.create(
domain,
commandObj,
!includeExperimental.has(`${domain}.${commandObj.name}`) &&
ignoreExperimental,
);
if (command) {
desc.commands.push(command);
}
}
for (const eventObj of obj.events || []) {
const event = Event.create(domain, eventObj, ignoreExperimental);
if (event) {
desc.events.push(event);
}
}
}
return desc;
}
function buildGraph(desc: Descriptor): Graph {
const graph = new Graph();
const types = desc.types;
const commands = desc.commands;
const events = desc.events;
const maybeAddPropEdges = function (nodeId: string, props: ?Array<Property>) {
if (props) {
for (const prop of props) {
const refId = prop.getRefDebuggerName();
(prop: Object).recursive = refId && refId === nodeId;
if (refId && refId !== nodeId) {
// Don't add edges for recursive properties.
graph.addEdge(nodeId, refId);
}
}
}
};
for (const type of types) {
graph.addNode(type.getDebuggerName());
if (type instanceof PropsType) {
maybeAddPropEdges(type.getDebuggerName(), type.properties);
}
}
for (const command of commands) {
graph.addNode(command.getDebuggerName());
maybeAddPropEdges(command.getDebuggerName(), command.parameters);
maybeAddPropEdges(command.getDebuggerName(), command.returns);
}
for (const event of events) {
graph.addNode(event.getDebuggerName());
maybeAddPropEdges(event.getDebuggerName(), event.parameters);
}
return graph;
}
function parseRoots(desc: Descriptor, rootsPath: ?string): Array<string> {
const roots = [];
if (rootsPath) {
const buf = fs.readFileSync(rootsPath);
for (let line of buf.toString().split('\n')) {
line = line.trim();
// ignore comments and blank lines
if (!line.match(/\s*#/) && line.length > 0) {
roots.push(line);
}
}
} else {
for (const type of desc.types) {
roots.push(type.getDebuggerName());
}
for (const command of desc.commands) {
roots.push(command.getDebuggerName());
}
for (const event of desc.events) {
roots.push(event.getDebuggerName());
}
}
return roots;
}
// only include types, commands, events that can be reached from the given
// root messages
function filterReachableFromRoots(
desc: Descriptor,
graph: Graph,
roots: Array<string>,
): Descriptor {
const topoSortedIds = graph.traverse(roots);
// Types can include other types by value, so they need to be topologically
// sorted in the header.
const typeMap: Map<string, Type> = new Map();
for (const type of desc.types) {
typeMap.set(type.getDebuggerName(), type);
}
const types = [];
for (const id of topoSortedIds) {
const type = typeMap.get(id);
if (type) {
types.push(type);
}
}
// Commands and events don't depend on each other, so just emit them in the
// order we got them from the JSON file.
const ids = new Set(topoSortedIds);
const commands = desc.commands.filter(cmd => ids.has(cmd.getDebuggerName()));
const events = desc.events.filter(event => ids.has(event.getDebuggerName()));
// Sort commands and events so the code is easier to read. Types have to be
// topologically sorted as explained above.
const comparator = (a, b) => {
const id1 = a.getDebuggerName();
const id2 = b.getDebuggerName();
return id1 < id2 ? -1 : id1 > id2 ? 1 : 0;
};
commands.sort(comparator);
events.sort(comparator);
return {types, commands, events};
}
function main() {
const args = yargs
.usage('Usage: msggen <header_path> <cpp_path>')
.alias('h', 'help')
.help('h')
.boolean('e')
.alias('e', 'ignore-experimental')
.describe('e', 'ignore experimental commands, props, and types')
.alias('i', 'include-experimental')
.describe('i', 'experimental commands to include')
.alias('r', 'roots')
.describe('r', 'path to a file listing root types, events, and commands')
.nargs('r', 1)
.demandCommand(2, 2).argv;
const ignoreExperimental = !!args.e;
const includeExperimental = new Set(
typeof args.i === 'string' ? args.i.split(',') : [],
);
const [headerPath, implPath] = args._;
const headerStream = fs.createWriteStream(headerPath);
const implStream = fs.createWriteStream(implPath);
const desc = parseDomains(
proto.domains,
ignoreExperimental,
includeExperimental,
);
const graph = buildGraph(desc);
const roots = parseRoots(desc, String(args.roots));
const reachable = filterReachableFromRoots(desc, graph, roots);
const hw = new HeaderWriter(
headerStream,
reachable.types,
reachable.commands,
reachable.events,
);
hw.write();
const iw = new ImplementationWriter(
implStream,
reachable.types,
reachable.commands,
reachable.events,
);
iw.write();
}
main();