This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,10 @@
#!/bin/sh
DIR="$(dirname "${BASH_SOURCE[0]}")"
cd "$DIR"
FBSOURCE="$(hg root)"
CLANG_FORMAT="$FBSOURCE/tools/third-party/clang-format/clang-format"
SRC="$FBSOURCE/xplat/js/react-native-github/ReactCommon/hermes/inspector"
find "$SRC" '(' -name '*.h' -or -name '*.cpp' ')' -exec "$CLANG_FORMAT" -i -style=file '{}' ';'

View File

@ -0,0 +1,25 @@
Debugger.breakpointResolved
Debugger.disable
Debugger.enable
Debugger.evaluateOnCallFrame
Debugger.pause
Debugger.paused
Debugger.removeBreakpoint
Debugger.resume
Debugger.resumed
Debugger.scriptParsed
Debugger.setBreakpoint
Debugger.setBreakpointByUrl
Debugger.setPauseOnExceptions
Debugger.stepInto
Debugger.stepOut
Debugger.stepOver
HeapProfiler.addHeapSnapshotChunk
HeapProfiler.reportHeapSnapshotProgress
HeapProfiler.takeHeapSnapshot
HeapProfiler.startTrackingHeapObjects
HeapProfiler.stopTrackingHeapObjects
Runtime.consoleAPICalled
Runtime.evaluate
Runtime.executionContextCreated
Runtime.getProperties

View File

@ -0,0 +1,9 @@
{
"presets": [ "@babel/preset-flow", ["@babel/preset-env", {
"targets": {
"node": "current"
}
}]
],
"plugins": ["idx"]
}

View File

@ -0,0 +1,3 @@
.DS_Store
bin/
node_modules/

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { Command } from '../src/Command.js';
test('parses simple command', () => {
let obj = {
'name': 'setBreakpointsActive',
'parameters': [
{ 'name': 'active', 'type': 'boolean', 'description': 'New value for breakpoints active state.' },
],
'description': 'Activates / deactivates all breakpoints on the page.',
};
let command = Command.create('Debugger', obj, false);
expect(command.domain).toBe('Debugger');
expect(command.name).toBe('setBreakpointsActive');
expect(command.description).toBe('Activates / deactivates all breakpoints on the page.');
expect(command.parameters.map(p => p.name)).toEqual(['active']);
expect(command.returns.length).toBe(0);
expect(command.getDebuggerName()).toBe('Debugger.setBreakpointsActive');
expect(command.getCppNamespace()).toBe('debugger');
expect(command.getRequestCppType()).toBe('SetBreakpointsActiveRequest');
expect(command.getResponseCppType()).toBeUndefined();
expect(command.getForwardDecls()).toEqual(['struct SetBreakpointsActiveRequest;']);
});
test('parses command with return', () => {
let obj = {
'name': 'setBreakpoint',
'parameters': [
{ 'name': 'location', '$ref': 'Location', 'description': 'Location to set breakpoint in.' },
{ 'name': 'condition', 'type': 'string', 'optional': true, 'description': 'Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true.' },
],
'returns': [
{ 'name': 'breakpointId', '$ref': 'BreakpointId', 'description': 'Id of the created breakpoint for further reference.' },
{ 'name': 'actualLocation', '$ref': 'Location', 'description': 'Location this breakpoint resolved into.' },
],
'description': 'Sets JavaScript breakpoint at a given location.',
};
let command = Command.create('Debugger', obj, false);
expect(command.domain).toBe('Debugger');
expect(command.name).toBe('setBreakpoint');
expect(command.description).toBe('Sets JavaScript breakpoint at a given location.');
expect(command.parameters.map(p => p.name)).toEqual(['location', 'condition']);
expect(command.returns.map(p => p.name)).toEqual(['breakpointId', 'actualLocation']);
expect(command.getDebuggerName()).toBe('Debugger.setBreakpoint');
expect(command.getCppNamespace()).toBe('debugger');
expect(command.getRequestCppType()).toBe('SetBreakpointRequest');
expect(command.getResponseCppType()).toBe('SetBreakpointResponse');
expect(command.getForwardDecls()).toEqual([
'struct SetBreakpointRequest;',
'struct SetBreakpointResponse;',
]);
});

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { Event } from '../src/Event.js';
test('parses simple event', () => {
let obj = {
'name': 'resumed',
'description': 'Fired when the virtual machine resumed execution.',
};
let event = Event.create('Debugger', obj, false);
expect(event.domain).toBe('Debugger');
expect(event.name).toBe('resumed');
expect(event.description).toBe('Fired when the virtual machine resumed execution.');
expect(event.getDebuggerName()).toBe('Debugger.resumed');
expect(event.getCppNamespace()).toBe('debugger');
expect(event.getCppType()).toBe('ResumedNotification');
expect(event.getForwardDecls()).toEqual(['struct ResumedNotification;']);
});
test('parses event with params', () => {
let obj = {
'name': 'breakpointResolved',
'parameters': [
{ 'name': 'breakpointId', '$ref': 'BreakpointId', 'description': 'Breakpoint unique identifier.' },
{ 'name': 'location', '$ref': 'Location', 'description': 'Actual breakpoint location.' },
],
'description': 'Fired when breakpoint is resolved to an actual script and location.',
};
let event = Event.create('Debugger', obj, false);
expect(event.domain).toBe('Debugger');
expect(event.name).toBe('breakpointResolved');
expect(event.description).toBe('Fired when breakpoint is resolved to an actual script and location.');
expect(event.parameters.map(p => p.name)).toEqual(['breakpointId', 'location']);
expect(event.getDebuggerName()).toBe('Debugger.breakpointResolved');
expect(event.getCppNamespace()).toBe('debugger');
expect(event.getCppType()).toBe('BreakpointResolvedNotification');
expect(event.getForwardDecls()).toEqual(['struct BreakpointResolvedNotification;']);
});

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { Graph } from '../src/Graph.js';
// graph looks like this before test: https://pxl.cl/9k8t
let graph = null;
beforeEach(() => {
graph = new Graph();
graph.addEdge('A1', 'B1');
graph.addEdge('A2', 'B1');
graph.addEdge('A2', 'B2');
graph.addEdge('A3', 'B2');
graph.addEdge('A3', 'C3');
graph.addEdge('B1', 'C1');
graph.addEdge('B1', 'C2');
graph.addEdge('B1', 'C3');
graph.addEdge('B2', 'C2');
});
// checks id1 occurs after id2 in arr
function expectOccursAfter(arr, id1, id2) {
let idx1 = arr.indexOf(id1);
let idx2 = arr.indexOf(id2);
expect(idx1).not.toBe(-1);
expect(idx2).not.toBe(-1);
expect(idx1).toBeGreaterThan(idx2);
}
test('detects cycle', () => {
graph.addEdge('C2', 'A1');
expect(() => graph.traverse(['A2'])).toThrow(/^Not a DAG/);
});
test('checks for presence of root', () => {
expect(() => graph.traverse(['A1', 'NX'])).toThrow(/^No node/);
});
test('traverses partial graph', () => {
let ids = graph.traverse(['B1', 'A3']);
// Check that expected nodes are there
let sortedIds = ids.slice().sort();
expect(sortedIds).toEqual(['A3', 'B1', 'B2', 'C1', 'C2', 'C3']);
// Check that the result is topologically sorted
expectOccursAfter(ids, 'A3', 'B2');
expectOccursAfter(ids, 'A3', 'C3');
expectOccursAfter(ids, 'B1', 'C1');
expectOccursAfter(ids, 'B1', 'C2');
expectOccursAfter(ids, 'B1', 'C3');
expectOccursAfter(ids, 'B2', 'C2');
});
test('traverses complete graph', () => {
let ids = graph.traverse(['A1', 'A2', 'A3']);
// Check that expected nodes are there
let sortedIds = ids.slice().sort();
expect(sortedIds).toEqual(['A1', 'A2', 'A3', 'B1', 'B2', 'C1', 'C2', 'C3']);
// Check that the result is topologically sorted
expectOccursAfter(ids, 'A1', 'B1');
expectOccursAfter(ids, 'A2', 'B1');
expectOccursAfter(ids, 'A2', 'B2');
expectOccursAfter(ids, 'A3', 'B2');
expectOccursAfter(ids, 'A3', 'C3');
expectOccursAfter(ids, 'B1', 'C1');
expectOccursAfter(ids, 'B1', 'C2');
expectOccursAfter(ids, 'B1', 'C3');
expectOccursAfter(ids, 'B2', 'C2');
});

View File

@ -0,0 +1,130 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { expectCodeIsEqual, FakeWritable } from '../src/TestHelpers';
import {
emitNotificationDecl,
emitRequestDecl,
emitResponseDecl,
emitTypeDecl,
} from '../src/HeaderWriter';
import { Event } from '../src/Event';
import { Command } from '../src/Command';
import { Type } from '../src/Type';
let stream = null;
beforeEach(() => {
stream = new FakeWritable();
});
test('emits type decl', () => {
let obj = {
'id': 'Location',
'type': 'object',
'properties': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Script identifier as reported in the <code>Debugger.scriptParsed</code>.' },
{ 'name': 'lineNumber', 'type': 'integer', 'description': 'Line number in the script (0-based).' },
{ 'name': 'columnNumber', 'type': 'integer', 'optional': true, 'description': 'Column number in the script (0-based).' },
],
'description': 'Location in the source code.',
};
let type = Type.create('Debugger', obj);
emitTypeDecl(stream, type);
expectCodeIsEqual(stream.get(), `
struct debugger::Location : public Serializable {
Location() = default;
explicit Location(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
runtime::ScriptId scriptId{};
int lineNumber{};
folly::Optional<int> columnNumber;
};
`);
});
test('emits request decl', () => {
let obj = {
'name': 'getScriptSource',
'parameters': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Id of the script to get source for.' },
],
'returns': [
{ 'name': 'scriptSource', 'type': 'string', 'description': 'Script source.' },
],
'description': 'Returns source for the script with given id.',
};
let command = Command.create('Debugger', obj);
emitRequestDecl(stream, command);
expectCodeIsEqual(stream.get(), `
struct debugger::GetScriptSourceRequest : public Request {
GetScriptSourceRequest();
explicit GetScriptSourceRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
runtime::ScriptId scriptId{};
};
`);
});
test('emits response decl', () => {
let obj = {
'name': 'getScriptSource',
'parameters': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Id of the script to get source for.' },
],
'returns': [
{ 'name': 'scriptSource', 'type': 'string', 'description': 'Script source.' },
],
'description': 'Returns source for the script with given id.',
};
let command = Command.create('Debugger', obj);
emitResponseDecl(stream, command);
expectCodeIsEqual(stream.get(), `
struct debugger::GetScriptSourceResponse : public Response {
GetScriptSourceResponse() = default;
explicit GetScriptSourceResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
std::string scriptSource;
};
`);
});
test('emits notification decl', () => {
let obj = {
'name': 'messageAdded',
'parameters': [
{ 'name': 'message', '$ref': 'ConsoleMessage', 'description': 'Console message that has been added.' },
],
'description': 'Issued when new console message is added.',
};
let event = Event.create('Console', obj);
emitNotificationDecl(stream, event);
expectCodeIsEqual(stream.get(), `
struct console::MessageAddedNotification : public Notification {
MessageAddedNotification();
explicit MessageAddedNotification(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
console::ConsoleMessage message{};
};
`);
});

View File

@ -0,0 +1,173 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { expectCodeIsEqual, FakeWritable } from '../src/TestHelpers';
import {
emitNotificationDef,
emitRequestDef,
emitResponseDef,
emitTypeDef,
} from '../src/ImplementationWriter';
import { Event } from '../src/Event';
import { Command } from '../src/Command';
import { Type } from '../src/Type';
let stream = null;
beforeEach(() => {
stream = new FakeWritable();
});
test('emits type def', () => {
let obj = {
'id': 'Location',
'type': 'object',
'properties': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Script identifier as reported in the <code>Debugger.scriptParsed</code>.' },
{ 'name': 'lineNumber', 'type': 'integer', 'description': 'Line number in the script (0-based).' },
{ 'name': 'columnNumber', 'type': 'integer', 'optional': true, 'description': 'Column number in the script (0-based).' },
],
'description': 'Location in the source code.',
};
let type = Type.create('Debugger', obj);
emitTypeDef(stream, type);
expectCodeIsEqual(stream.get(), `
debugger::Location::Location(const dynamic &obj) {
assign(scriptId, obj, "scriptId");
assign(lineNumber, obj, "lineNumber");
assign(columnNumber, obj, "columnNumber");
}
dynamic debugger::Location::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "scriptId", scriptId);
put(obj, "lineNumber", lineNumber);
put(obj, "columnNumber", columnNumber);
return obj;
}
`);
});
test('emits request def', () => {
let obj = {
'name': 'getScriptSource',
'parameters': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Id of the script to get source for.' },
],
'returns': [
{ 'name': 'scriptSource', 'type': 'string', 'description': 'Script source.' },
],
'description': 'Returns source for the script with given id.',
};
let command = Command.create('Debugger', obj);
emitRequestDef(stream, command);
expectCodeIsEqual(stream.get(), `
debugger::GetScriptSourceRequest::GetScriptSourceRequest()
: Request("Debugger.getScriptSource") {}
debugger::GetScriptSourceRequest::GetScriptSourceRequest(const dynamic &obj)
: Request("Debugger.getScriptSource") {
assign(id, obj, "id");
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(scriptId, params, "scriptId");
}
dynamic debugger::GetScriptSourceRequest::toDynamic() const {
dynamic params = dynamic::object;
put(params, "scriptId", scriptId);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
void debugger::GetScriptSourceRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}
`);
});
test('emits response def', () => {
let obj = {
'name': 'getScriptSource',
'parameters': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Id of the script to get source for.' },
],
'returns': [
{ 'name': 'scriptSource', 'type': 'string', 'description': 'Script source.' },
],
'description': 'Returns source for the script with given id.',
};
let command = Command.create('Debugger', obj);
emitResponseDef(stream, command);
expectCodeIsEqual(stream.get(), `
debugger::GetScriptSourceResponse::GetScriptSourceResponse(const dynamic &obj) {
assign(id, obj, "id");
dynamic res = obj.at("result");
assign(scriptSource, res, "scriptSource");
}
dynamic debugger::GetScriptSourceResponse::toDynamic() const {
dynamic res = dynamic::object;
put(res, "scriptSource", scriptSource);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}
`);
});
test('emits notification def', () => {
let obj = {
'name': 'messageAdded',
'parameters': [
{ 'name': 'message', '$ref': 'ConsoleMessage', 'description': 'Console message that has been added.' },
],
'description': 'Issued when new console message is added.',
};
let event = Event.create('Console', obj);
emitNotificationDef(stream, event);
expectCodeIsEqual(stream.get(), `
console::MessageAddedNotification::MessageAddedNotification()
: Notification("Console.messageAdded") {}
console::MessageAddedNotification::MessageAddedNotification(const dynamic &obj)
: Notification("Console.messageAdded") {
assign(method, obj, "method");
dynamic params = obj.at("params");
assign(message, params, "message");
}
dynamic console::MessageAddedNotification::toDynamic() const {
dynamic params = dynamic::object;
put(params, "message", message);
dynamic obj = dynamic::object;
put(obj, "method", method);
put(obj, "params", std::move(params));
return obj;
}
`);
});

View File

@ -0,0 +1,133 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { Property } from '../src/Property.js';
test('parses required primitive prop', () => {
let obj = {
'name': 'lineNumber',
'type': 'integer',
'description': 'Line number in the script (0-based).',
};
let prop = Property.create('Debugger', obj);
expect(prop.domain).toBe('Debugger');
expect(prop.name).toBe('lineNumber');
expect(prop.type).toBe('integer');
expect(prop.optional).toBeUndefined();
expect(prop.description).toBe('Line number in the script (0-based).');
expect(prop.getFullCppType()).toBe('int');
expect(prop.getCppIdentifier()).toBe('lineNumber');
expect(prop.getInitializer()).toBe('{}');
});
test('parses optional primitive prop', () => {
let obj = {
'name': 'samplingInterval',
'type': 'number',
'optional': true,
'description': 'Average sample interval in bytes.',
};
let prop = Property.create('HeapProfiler', obj);
expect(prop.domain).toBe('HeapProfiler');
expect(prop.name).toBe('samplingInterval');
expect(prop.type).toBe('number');
expect(prop.optional).toBe(true);
expect(prop.description).toBe('Average sample interval in bytes.');
expect(prop.getFullCppType()).toBe('folly::Optional<double>');
expect(prop.getCppIdentifier()).toBe('samplingInterval');
expect(prop.getInitializer()).toBe('');
});
test('parses optional ref prop', () => {
let obj = {
'name': 'exceptionDetails',
'optional': true,
'$ref': 'Runtime.ExceptionDetails',
'description': 'Exception details if any.',
};
let prop = Property.create('Debugger', obj);
expect(prop.domain).toBe('Debugger');
expect(prop.name).toBe('exceptionDetails');
expect(prop.optional).toBe(true);
expect(prop.$ref).toBe('Runtime.ExceptionDetails');
expect(prop.description).toBe('Exception details if any.');
expect(prop.getFullCppType()).toBe('folly::Optional<runtime::ExceptionDetails>');
expect(prop.getCppIdentifier()).toBe('exceptionDetails');
expect(prop.getInitializer()).toBe('');
});
test('parses recursive ref prop', () => {
let obj = {
'name': 'parent',
'$ref': 'StackTrace',
'optional': true,
'recursive': true,
'description': 'Asynchronous JavaScript stack trace...',
};
let prop = Property.create('Runtime', obj);
expect(prop.domain).toBe('Runtime');
expect(prop.name).toBe('parent');
expect(prop.optional).toBe(true);
expect(prop.recursive).toBe(true);
expect(prop.$ref).toBe('StackTrace');
expect(prop.description).toBe('Asynchronous JavaScript stack trace...');
expect(prop.getFullCppType()).toBe('std::unique_ptr<runtime::StackTrace>');
expect(prop.getCppIdentifier()).toBe('parent');
expect(prop.getInitializer()).toBe('');
});
test('parses optional array items prop', () => {
let obj = {
'name': 'hitBreakpoints',
'type': 'array',
'optional': true,
'items': { 'type': 'string' },
'description': 'Hit breakpoints IDs',
};
let prop = Property.create('Debugger', obj);
expect(prop.domain).toBe('Debugger');
expect(prop.name).toBe('hitBreakpoints');
expect(prop.type).toBe('array');
expect(prop.optional).toBe(true);
expect(prop.items).toEqual({ 'type': 'string' });
expect(prop.description).toBe('Hit breakpoints IDs');
expect(prop.getFullCppType()).toBe('folly::Optional<std::vector<std::string>>');
expect(prop.getCppIdentifier()).toBe('hitBreakpoints');
expect(prop.getInitializer()).toBe('');
});
test('parses array ref prop', () => {
let obj = {
'name': 'domains',
'type': 'array',
'items': { '$ref': 'Domain' },
'description': 'List of supported domains.',
};
let prop = Property.create('Schema', obj);
expect(prop.domain).toBe('Schema');
expect(prop.name).toBe('domains');
expect(prop.type).toBe('array');
expect(prop.items).toEqual({ $ref: 'Domain' });
expect(prop.description).toBe('List of supported domains.');
expect(prop.getFullCppType()).toBe('std::vector<schema::Domain>');
expect(prop.getCppIdentifier()).toBe('domains');
expect(prop.getInitializer()).toBe('');
});

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import { Type } from '../src/Type.js';
test('parses primitive type', () => {
let obj = {
'id': 'Timestamp',
'type': 'number',
'description': 'Number of milliseconds since epoch.',
};
let type = Type.create('Runtime', obj, false);
expect(type.domain).toBe('Runtime');
expect(type.id).toBe('Timestamp');
expect(type.type).toBe('number');
expect(type.description).toBe('Number of milliseconds since epoch.');
expect(type.getCppNamespace()).toBe('runtime');
expect(type.getCppType()).toBe('Timestamp');
expect(type.getForwardDecls()).toEqual(['using Timestamp = double;']);
});
test('parses object type', () => {
let obj = {
'id': 'Location',
'type': 'object',
'properties': [
{ 'name': 'scriptId', '$ref': 'Runtime.ScriptId', 'description': 'Script identifier as reported in the <code>Debugger.scriptParsed</code>.' },
{ 'name': 'lineNumber', 'type': 'integer', 'description': 'Line number in the script (0-based).' },
{ 'name': 'columnNumber', 'type': 'integer', 'optional': true, 'description': 'Column number in the script (0-based).' },
],
'description': 'Location in the source code.',
};
let type = Type.create('Debugger', obj, false);
expect(type.domain).toBe('Debugger');
expect(type.id).toBe('Location');
expect(type.type).toBe('object');
expect(type.properties.map(p => p.name)).toEqual(['scriptId', 'lineNumber', 'columnNumber']);
expect(type.description).toBe('Location in the source code.');
expect(type.getCppNamespace()).toBe('debugger');
expect(type.getCppType()).toBe('Location');
expect(type.getForwardDecls()).toEqual(['struct Location;']);
});

View File

@ -0,0 +1,39 @@
{
"name": "hermes-inspector-msggen",
"version": "1.0.0",
"license": "MIT",
"bin": {
"msggen": "./bin/index.js"
},
"scripts": {
"flow": "flow",
"build": "babel src --out-dir bin --source-maps",
"watch": "babel src --out-dir bin --source-maps --watch",
"test": "jest"
},
"devDependencies": {
"@babel/cli": "^7.2.0",
"@babel/core": "^7.2.0",
"@babel/plugin-syntax-flow": "^7.2.0",
"@babel/plugin-transform-flow-strip-types": "^7.2.0",
"@babel/preset-env": "^7.2.0",
"@babel/preset-flow": "^7.2.0",
"diff": "3.5.0",
"extend": "3.0.2",
"jest": "^24.9.0",
"nwmatcher": "1.4.4",
"randomatic": "3.0.0",
"sshpk": "1.16.1",
"webpack": "^4.41.0"
},
"jest": {
"transform": {
".*": "<rootDir>/node_modules/babel-jest"
}
},
"dependencies": {
"devtools-protocol": "0.0.730699",
"idx": "^2.1.0",
"yargs": "^14.2.0"
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {Property} from './Property';
import {toCppNamespace, toCppType} from './Converters';
export class Command {
domain: string;
name: string;
description: ?string;
experimental: ?boolean;
parameters: Array<Property>;
returns: Array<Property>;
static create(
domain: string,
obj: any,
ignoreExperimental: boolean,
): ?Command {
return ignoreExperimental && obj.experimental
? null
: new Command(domain, obj, ignoreExperimental);
}
constructor(domain: string, obj: any, ignoreExperimental: boolean) {
this.domain = domain;
this.name = obj.name;
this.description = obj.description;
this.experimental = obj.experimental;
this.parameters = Property.createArray(
domain,
obj.parameters || [],
ignoreExperimental,
);
this.returns = Property.createArray(
domain,
obj.returns || [],
ignoreExperimental,
);
}
getDebuggerName(): string {
return `${this.domain}.${this.name}`;
}
getCppNamespace(): string {
return toCppNamespace(this.domain);
}
getRequestCppType(): string {
return toCppType(this.name + 'Request');
}
getResponseCppType(): ?string {
if (this.returns && this.returns.length > 0) {
return toCppType(this.name + 'Response');
}
}
getForwardDecls(): Array<string> {
const decls = [`struct ${this.getRequestCppType()};`];
const respCppType = this.getResponseCppType();
if (respCppType) {
decls.push(`struct ${respCppType};`);
}
return decls;
}
getForwardDeclSortKey(): string {
return this.getRequestCppType();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
export function toCppNamespace(domain: string): string {
return domain.substr(0, 1).toLowerCase() + domain.substr(1);
}
export function toCppType(type: string): string {
return type.substr(0, 1).toUpperCase() + type.substr(1);
}
export type JsTypeString =
| 'any'
| 'boolean'
| 'integer'
| 'number'
| 'object'
| 'string';
const jsTypeMappings = {
any: 'folly::dynamic',
array: 'folly::dynamic',
boolean: 'bool',
integer: 'int',
number: 'double',
object: 'folly::dynamic',
string: 'std::string',
};
export function jsTypeToCppType(jsTypeStr: JsTypeString): string {
return jsTypeMappings[jsTypeStr];
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {Property} from './Property';
import {toCppNamespace, toCppType} from './Converters';
export class Event {
domain: string;
name: string;
description: ?string;
experimental: ?boolean;
parameters: Array<Property>;
static create(domain: string, obj: any, ignoreExperimental: boolean): ?Event {
return ignoreExperimental && obj.experimental
? null
: new Event(domain, obj, ignoreExperimental);
}
constructor(domain: string, obj: any, ignoreExperimental: boolean) {
this.domain = domain;
this.name = obj.name;
this.description = obj.description;
this.parameters = Property.createArray(
domain,
obj.parameters || [],
ignoreExperimental,
);
}
getDebuggerName(): string {
return `${this.domain}.${this.name}`;
}
getCppNamespace(): string {
return toCppNamespace(this.domain);
}
getCppType(): string {
return toCppType(this.name + 'Notification');
}
getForwardDecls(): Array<string> {
return [`struct ${this.getCppType()};`];
}
getForwardDeclSortKey(): string {
return this.getCppType();
}
}

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
// placeholder token that will be replaced by signedsource script
export const GeneratedHeader: string =
'// Copyright 2004-present Facebook. All Rights Reserved.\n' +
'// @generated <<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>';

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import invariant from 'assert';
type NodeId = string;
class Node {
id: NodeId;
children: Set<Node>;
state: 'none' | 'visiting' | 'visited';
constructor(id: NodeId) {
this.id = id;
this.children = new Set();
this.state = 'none';
}
}
export class Graph {
nodes: Map<NodeId, Node>;
constructor() {
this.nodes = new Map();
}
addNode(nodeId: NodeId): Node {
let node = this.nodes.get(nodeId);
if (!node) {
node = new Node(nodeId);
this.nodes.set(nodeId, node);
}
return node;
}
addEdge(srcId: NodeId, dstId: NodeId) {
const src = this.addNode(srcId);
const dst = this.addNode(dstId);
src.children.add(dst);
}
// traverse returns all nodes in the graph reachable from the given rootIds.
// the returned nodes are topologically sorted, with the deepest nodes
// returned first.
traverse(rootIds: Array<NodeId>): Array<NodeId> {
// clear marks
for (const node of this.nodes.values()) {
node.state = 'none';
}
// make a fake root node that points to all the provided rootIds
const root = new Node('root');
for (const id of rootIds) {
const node = this.nodes.get(id);
invariant(node != null, `No node ${id} in graph`);
root.children.add(node);
}
const output = [];
postorder(root, output);
// remove fake root node
output.splice(-1);
return output;
}
}
function postorder(node: Node, output: Array<NodeId>) {
if (node.state === 'visited') {
return;
}
invariant(node.state !== 'visiting', `Not a DAG: cycle involving ${node.id}`);
node.state = 'visiting';
for (const child of node.children) {
postorder(child, output);
}
node.state = 'visited';
output.push(node.id);
}

View File

@ -0,0 +1,325 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {Writable} from 'stream';
import {GeneratedHeader} from './GeneratedHeader';
import {Property} from './Property';
import {PropsType, Type} from './Type';
import {Command} from './Command';
import {Event} from './Event';
import {toCppNamespace} from './Converters';
export class HeaderWriter {
stream: Writable;
types: Array<Type>;
commands: Array<Command>;
events: Array<Event>;
constructor(
stream: Writable,
types: Array<Type>,
commands: Array<Command>,
events: Array<Event>,
) {
this.stream = stream;
this.types = types;
this.commands = commands;
this.events = events;
}
write() {
this.writePrologue();
this.writeForwardDecls();
this.writeRequestHandlerDecls();
this.writeTypeDecls();
this.writeRequestDecls();
this.writeResponseDecls();
this.writeNotificationDecls();
this.writeEpilogue();
}
writePrologue() {
this.stream.write(`${GeneratedHeader}
#pragma once
#include <hermes/inspector/chrome/MessageInterfaces.h>
#include <vector>
#include <folly/Optional.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
`);
}
writeForwardDecls() {
this.stream.write('struct UnknownRequest;\n\n');
const namespaceMap: Map<string, Array<Type | Command | Event>> = new Map();
const addToMap = function(type) {
const domain = type.domain;
let types = namespaceMap.get(domain);
if (!types) {
types = [];
namespaceMap.set(domain, types);
}
types.push(type);
};
this.types.forEach(addToMap);
this.commands.forEach(addToMap);
this.events.forEach(addToMap);
for (const [domain, types] of namespaceMap) {
types.sort((a, b) => {
const nameA = a.getForwardDeclSortKey();
const nameB = b.getForwardDeclSortKey();
return nameA < nameB ? -1 : nameA > nameB ? 1 : 0;
});
const ns = toCppNamespace(domain);
this.stream.write(`namespace ${ns} {\n`);
for (const type of types) {
for (const decl of type.getForwardDecls()) {
this.stream.write(`${decl}\n`);
}
}
this.stream.write(`} // namespace ${ns}\n\n`);
}
}
writeRequestHandlerDecls() {
this.stream.write(
'\n/// RequestHandler handles requests via the visitor pattern.\n',
);
emitRequestHandlerDecl(this.stream, this.commands);
this.stream.write(
'\n/// NoopRequestHandler can be subclassed to only handle some requests.\n',
);
emitNoopRequestHandlerDecl(this.stream, this.commands);
}
writeTypeDecls() {
this.stream.write('\n/// Types\n');
for (const type of this.types) {
if (type instanceof PropsType) {
emitTypeDecl(this.stream, type);
}
}
}
writeRequestDecls() {
this.stream.write('\n/// Requests\n');
emitUnknownRequestDecl(this.stream);
for (const command of this.commands) {
emitRequestDecl(this.stream, command);
}
}
writeResponseDecls() {
this.stream.write('\n/// Responses\n');
emitErrorResponseDecl(this.stream);
emitOkResponseDecl(this.stream);
for (const command of this.commands) {
emitResponseDecl(this.stream, command);
}
}
writeNotificationDecls() {
this.stream.write('\n/// Notifications\n');
for (const event of this.events) {
emitNotificationDecl(this.stream, event);
}
}
writeEpilogue() {
this.stream.write(`
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
`);
}
}
function emitRequestHandlerDecl(stream: Writable, commands: Array<Command>) {
stream.write(`struct RequestHandler {
virtual ~RequestHandler() = default;
virtual void handle(const UnknownRequest &req) = 0;
`);
for (const command of commands) {
const cppNs = command.getCppNamespace();
const cppType = command.getRequestCppType();
stream.write(`virtual void handle(const ${cppNs}::${cppType} &req) = 0;`);
}
stream.write('};\n');
}
function emitNoopRequestHandlerDecl(
stream: Writable,
commands: Array<Command>,
) {
stream.write(`struct NoopRequestHandler : public RequestHandler {
void handle(const UnknownRequest &req) override {}
`);
for (const command of commands) {
const cppNs = command.getCppNamespace();
const cppType = command.getRequestCppType();
stream.write(`void handle(const ${cppNs}::${cppType} &req) override {}`);
}
stream.write('};\n');
}
function emitProps(stream: Writable, props: ?Array<Property>) {
if (!props || props.length === 0) {
return;
}
stream.write('\n');
for (const prop of props) {
const fullCppType = prop.getFullCppType();
const cppId = prop.getCppIdentifier();
const init = prop.getInitializer();
stream.write(` ${fullCppType} ${cppId}${init};\n`);
}
}
export function emitTypeDecl(stream: Writable, type: PropsType) {
const cppNs = type.getCppNamespace();
const cppType = type.getCppType();
stream.write(`struct ${cppNs}::${cppType} : public Serializable {
${cppType}() = default;
explicit ${cppType}(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
`);
if (type instanceof PropsType) {
emitProps(stream, type.properties);
}
stream.write('};\n\n');
}
function emitUnknownRequestDecl(stream: Writable) {
stream.write(`struct UnknownRequest : public Request {
UnknownRequest();
explicit UnknownRequest(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
folly::Optional<folly::dynamic> params;
};
`);
}
export function emitRequestDecl(stream: Writable, command: Command) {
const cppNs = command.getCppNamespace();
const cppType = command.getRequestCppType();
stream.write(`struct ${cppNs}::${cppType} : public Request {
${cppType}();
explicit ${cppType}(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
void accept(RequestHandler &handler) const override;
`);
emitProps(stream, command.parameters);
stream.write('};\n\n');
}
function emitErrorResponseDecl(stream: Writable) {
stream.write(`struct ErrorResponse : public Response {
ErrorResponse() = default;
explicit ErrorResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
int code;
std::string message;
folly::Optional<folly::dynamic> data;
};
`);
}
function emitOkResponseDecl(stream: Writable) {
stream.write(`struct OkResponse : public Response {
OkResponse() = default;
explicit OkResponse(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
};
`);
}
export function emitResponseDecl(stream: Writable, command: Command) {
const cppNs = command.getCppNamespace();
const cppType = command.getResponseCppType();
if (!cppType) {
return;
}
stream.write(`struct ${cppNs}::${cppType} : public Response {
${cppType}() = default;
explicit ${cppType}(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
`);
emitProps(stream, command.returns);
stream.write('};\n\n');
}
export function emitNotificationDecl(stream: Writable, event: Event) {
const cppNs = event.getCppNamespace();
const cppType = event.getCppType();
stream.write(`struct ${cppNs}::${cppType} : public Notification {
${cppType}();
explicit ${cppType}(const folly::dynamic &obj);
folly::dynamic toDynamic() const override;
`);
emitProps(stream, event.parameters);
stream.write('};\n\n');
}

View File

@ -0,0 +1,410 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {Writable} from 'stream';
import {GeneratedHeader} from './GeneratedHeader';
import {PropsType, Type} from './Type';
import {Command} from './Command';
import {Event} from './Event';
export class ImplementationWriter {
stream: Writable;
types: Array<Type>;
commands: Array<Command>;
events: Array<Event>;
constructor(
stream: Writable,
types: Array<Type>,
commands: Array<Command>,
events: Array<Event>,
) {
this.stream = stream;
this.types = types;
this.commands = commands;
this.events = events;
}
write() {
this.writePrologue();
this.writeRequestParser();
this.writeTypeDefs();
this.writeRequestDefs();
this.writeResponseDefs();
this.writeNotificationDefs();
this.writeEpilogue();
}
writePrologue() {
this.stream.write(`${GeneratedHeader}
#include "MessageTypes.h"
#include "MessageTypesInlines.h"
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
`);
}
writeRequestParser() {
emitRequestParser(this.stream, this.commands);
}
writeTypeDefs() {
this.stream.write('\n/// Types\n');
for (const type of this.types) {
if (type instanceof PropsType) {
emitTypeDef(this.stream, type);
}
}
}
writeRequestDefs() {
this.stream.write('\n/// Requests\n');
emitUnknownRequestDef(this.stream);
for (const command of this.commands) {
emitRequestDef(this.stream, command);
}
}
writeResponseDefs() {
this.stream.write('\n/// Responses\n');
emitErrorResponseDef(this.stream);
emitOkResponseDef(this.stream);
for (const command of this.commands) {
emitResponseDef(this.stream, command);
}
}
writeNotificationDefs() {
this.stream.write('\n/// Notifications\n');
for (const event of this.events) {
emitNotificationDef(this.stream, event);
}
}
writeEpilogue() {
this.stream.write(`
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
`);
}
}
function emitRequestParser(stream: Writable, commands: Array<Command>) {
stream.write(`
using RequestBuilder = std::unique_ptr<Request> (*)(const dynamic &);
namespace {
template <typename T>
std::unique_ptr<Request> makeUnique(const dynamic &obj) {
return std::make_unique<T>(obj);
}
} // namespace
std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
static std::unordered_map<std::string, RequestBuilder> builders = {
`);
for (const command of commands) {
const cppNs = command.getCppNamespace();
const cppType = command.getRequestCppType();
const dbgName = command.getDebuggerName();
stream.write(`{"${dbgName}", makeUnique<${cppNs}::${cppType}>},\n`);
}
stream.write(`};
dynamic obj = folly::parseJson(str);
std::string method = obj.at("method").asString();
auto it = builders.find(method);
if (it == builders.end()) {
return std::make_unique<UnknownRequest>(obj);
}
auto builder = it->second;
return builder(obj);
}
folly::Try<std::unique_ptr<Request>> Request::fromJson(const std::string &str) {
return folly::makeTryWith(
[&str] { return Request::fromJsonThrowOnError(str); });
}\n\n`);
stream.write('\n');
}
export function emitTypeDef(stream: Writable, type: PropsType) {
const cppNs = type.getCppNamespace();
const cppType = type.getCppType();
const props = type.properties || [];
// From-dynamic constructor
stream.write(`${cppNs}::${cppType}::${cppType}(const dynamic &obj) {\n`);
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`assign(${id}, obj, "${name}");\n`);
}
stream.write('}\n\n');
// toDynamic
stream.write(`dynamic ${cppNs}::${cppType}::toDynamic() const {
dynamic obj = dynamic::object;\n\n`);
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`put(obj, "${name}", ${id});\n`);
}
stream.write('return obj;\n}\n\n');
}
function emitErrorResponseDef(stream: Writable) {
stream.write(`ErrorResponse::ErrorResponse(const dynamic &obj) {
assign(id, obj, "id");
dynamic error = obj.at("error");
assign(code, error, "code");
assign(message, error, "message");
assign(data, error, "data");
}
dynamic ErrorResponse::toDynamic() const {
dynamic error = dynamic::object;
put(error, "code", code);
put(error, "message", message);
put(error, "data", data);
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "error", std::move(error));
return obj;
}\n\n`);
}
function emitOkResponseDef(stream: Writable) {
stream.write(`OkResponse::OkResponse(const dynamic &obj) {
assign(id, obj, "id");
}
dynamic OkResponse::toDynamic() const {
dynamic result = dynamic::object;
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(result));
return obj;
}\n\n`);
}
function emitUnknownRequestDef(stream: Writable) {
stream.write(`UnknownRequest::UnknownRequest() {}
UnknownRequest::UnknownRequest(const dynamic &obj) {
assign(id, obj, "id");
assign(method, obj, "method");
assign(params, obj, "params");
}
dynamic UnknownRequest::toDynamic() const {
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
put(obj, "params", params);
return obj;
}
void UnknownRequest::accept(RequestHandler &handler) const {
handler.handle(*this);
}\n\n`);
}
export function emitRequestDef(stream: Writable, command: Command) {
const cppNs = command.getCppNamespace();
const cppType = command.getRequestCppType();
const dbgName = command.getDebuggerName();
const props = command.parameters || [];
// Default constructor
stream.write(`${cppNs}::${cppType}::${cppType}()
: Request("${dbgName}") {}\n\n`);
// From-dynamic constructor
stream.write(`${cppNs}::${cppType}::${cppType}(const dynamic &obj)
: Request("${dbgName}") {
assign(id, obj, "id");
assign(method, obj, "method");\n\n`);
if (props.length > 0) {
stream.write('dynamic params = obj.at("params");\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`assign(${id}, params, "${name}");\n`);
}
}
stream.write('}\n\n');
// toDynamic
stream.write(`dynamic ${cppNs}::${cppType}::toDynamic() const {\n`);
if (props.length > 0) {
stream.write('dynamic params = dynamic::object;\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`put(params, "${name}", ${id});\n`);
}
}
stream.write(`
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "method", method);
`);
if (props.length > 0) {
stream.write('put(obj, "params", std::move(params));\n');
}
stream.write(`return obj;
}\n\n`);
// visitor
stream.write(`void ${cppNs}::${cppType}::accept(RequestHandler &handler) const {
handler.handle(*this);
}\n\n`);
}
export function emitResponseDef(stream: Writable, command: Command) {
const cppNs = command.getCppNamespace();
const cppType = command.getResponseCppType();
if (!cppType) {
return;
}
// From-dynamic constructor
stream.write(`${cppNs}::${cppType}::${cppType}(const dynamic &obj) {
assign(id, obj, "id");\n\n`);
const props = command.returns || [];
if (props.length > 0) {
stream.write('dynamic res = obj.at("result");\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`assign(${id}, res, "${name}");\n`);
}
}
stream.write('}\n\n');
// toDynamic
stream.write(`dynamic ${cppNs}::${cppType}::toDynamic() const {\n`);
if (props.length > 0) {
stream.write('dynamic res = dynamic::object;\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`put(res, "${name}", ${id});\n`);
}
}
stream.write(`
dynamic obj = dynamic::object;
put(obj, "id", id);
put(obj, "result", std::move(res));
return obj;
}\n\n`);
}
export function emitNotificationDef(stream: Writable, event: Event) {
const cppNs = event.getCppNamespace();
const cppType = event.getCppType();
const dbgName = event.getDebuggerName();
const props = event.parameters || [];
// Default constructor
stream.write(`${cppNs}::${cppType}::${cppType}()
: Notification("${dbgName}") {}\n\n`);
// From-dynamic constructor
stream.write(`${cppNs}::${cppType}::${cppType}(const dynamic &obj)
: Notification("${dbgName}") {
assign(method, obj, "method");\n\n`);
if (props.length > 0) {
stream.write('dynamic params = obj.at("params");\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`assign(${id}, params, "${name}");\n`);
}
}
stream.write('}\n\n');
// toDynamic
stream.write(`dynamic ${cppNs}::${cppType}::toDynamic() const {\n`);
if (props.length > 0) {
stream.write('dynamic params = dynamic::object;\n');
for (const prop of props) {
const id = prop.getCppIdentifier();
const name = prop.name;
stream.write(`put(params, "${name}", ${id});\n`);
}
}
stream.write(`
dynamic obj = dynamic::object;
put(obj, "method", method);
`);
if (props.length > 0) {
stream.write('put(obj, "params", std::move(params));\n');
}
stream.write(`return obj;
}\n\n`);
}

View File

@ -0,0 +1,220 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {
jsTypeToCppType,
toCppNamespace,
toCppType,
type JsTypeString,
} from './Converters';
export class Property {
domain: string;
name: string;
description: ?string;
exported: ?boolean;
experimental: ?boolean;
optional: ?boolean;
static create(domain: string, obj: any): Property {
if (obj.$ref) {
return new RefProperty(domain, obj);
} else if (obj.type === 'array') {
return new ArrayProperty(domain, obj);
}
return new PrimitiveProperty(domain, obj);
}
static createArray(
domain: string,
elements: Array<any>,
ignoreExperimental: boolean,
): Array<Property> {
let props = elements.map(elem => Property.create(domain, elem));
if (ignoreExperimental) {
props = props.filter(prop => !prop.experimental);
}
return props;
}
constructor(domain: string, obj: any) {
this.domain = domain;
this.name = obj.name;
this.description = obj.description;
this.exported = obj.exported;
this.experimental = obj.experimental;
this.optional = obj.optional;
}
getRefDebuggerName(): ?string {
throw new Error('subclass must implement');
}
getFullCppType(): string {
throw new Error('subclass must implement');
}
getCppIdentifier(): string {
// need to munge identifier if it matches a C++ keyword like "this"
if (this.name === 'this') {
return 'thisObj';
}
return this.name;
}
getInitializer(): string {
throw new Error('subclass must implement');
}
}
function maybeWrapOptional(
type: string,
optional: ?boolean,
recursive: ?boolean,
) {
if (optional) {
return recursive ? `std::unique_ptr<${type}>` : `folly::Optional<${type}>`;
}
return type;
}
function toDomainAndId(
curDomain: string,
absOrRelRef: string,
): [string, string] {
let [domain, id] = ['', ''];
// absOrRelRef can be:
// 1) absolute ref with a "." referencing a type from another namespace, like
// "Runtime.ExceptionDetails"
// 2) relative ref without a "." referencing a type in current domain, like
// "Domain"
const i = absOrRelRef.indexOf('.');
if (i === -1) {
domain = curDomain;
id = absOrRelRef;
} else {
domain = absOrRelRef.substr(0, i);
id = absOrRelRef.substr(i + 1);
}
return [domain, id];
}
function toFullCppType(curDomain: string, absOrRelRef: string) {
const [domain, id] = toDomainAndId(curDomain, absOrRelRef);
return `${toCppNamespace(domain)}::${toCppType(id)}`;
}
class PrimitiveProperty extends Property {
type: JsTypeString;
constructor(domain: string, obj: any) {
super(domain, obj);
this.type = obj.type;
}
getRefDebuggerName(): ?string {
return undefined;
}
getFullCppType(): string {
return maybeWrapOptional(jsTypeToCppType(this.type), this.optional);
}
getInitializer(): string {
// folly::Optional doesn't need to be explicitly zero-init
if (this.optional) {
return '';
}
// we want to explicitly zero-init bool, int, and double
const type = this.type;
if (type === 'boolean' || type === 'integer' || type === 'number') {
return '{}';
}
// everything else (folly::dynamic and std::string) has sensible default
// constructor, no need to explicitly zero-init
return '';
}
}
class RefProperty extends Property {
$ref: string;
recursive: ?boolean;
constructor(domain: string, obj: any) {
super(domain, obj);
this.$ref = obj.$ref;
}
getRefDebuggerName(): ?string {
const [domain, id] = toDomainAndId(this.domain, this.$ref);
return `${domain}.${id}`;
}
getFullCppType(): string {
const fullCppType = toFullCppType(this.domain, this.$ref);
return maybeWrapOptional(`${fullCppType}`, this.optional, this.recursive);
}
getInitializer(): string {
// must zero-init non-optional ref props since the ref could just be an
// alias to a C++ primitive type like int which we always want to zero-init
return this.optional ? '' : '{}';
}
}
class ArrayProperty extends Property {
type: 'array';
items:
| {|type: JsTypeString, recursive: false|}
| {|$ref: string, recursive: ?boolean|};
constructor(domain: string, obj: any) {
super(domain, obj);
this.type = obj.type;
this.items = obj.items;
}
getRefDebuggerName(): ?string {
if (this.items && this.items.$ref && !this.items.recursive) {
const [domain, id] = toDomainAndId(this.domain, this.items.$ref);
return `${domain}.${id}`;
}
}
getFullCppType(): string {
let elemType: string = 'folly::dynamic';
let recursive = false;
if (this.items) {
if (this.items.type) {
elemType = jsTypeToCppType(this.items.type);
} else if (this.items.$ref) {
elemType = toFullCppType(this.domain, this.items.$ref);
recursive = this.items.recursive;
}
}
return maybeWrapOptional(
`std::vector<${elemType}>`,
this.optional,
recursive,
);
}
getInitializer(): string {
return '';
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*global expect*/
'use strict';
// munges string so that it's nice to look at in a test diff
function strip(str) {
// Trim leading and trailing WS
str = str.replace(/^\s+/, '');
str = str.replace(/\s+$/, '');
// Collapse all repeating newlines (possibly with spaces in between) into a
// single newline
str = str.replace(/\n(\s*)/g, '\n');
// Collapse all non-newline whitespace into a single space
return str.replace(/[^\S\n]+/g, ' ');
}
export function expectCodeIsEqual(actual, expected) {
expect(strip(actual)).toBe(strip(expected));
}
export class FakeWritable {
constructor() {
this.result = '';
}
write(str) {
this.result += str;
}
get() {
return this.result;
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import {Property} from './Property';
import {jsTypeToCppType, toCppNamespace, toCppType} from './Converters';
export class Type {
domain: string;
id: string;
description: ?string;
exported: ?boolean;
experimental: ?boolean;
static create(domain: string, obj: any, ignoreExperimental: boolean): ?Type {
let type = null;
if (obj.type === 'object' && obj.properties) {
type = new PropsType(domain, obj, ignoreExperimental);
} else if (obj.type) {
type = new PrimitiveType(domain, obj, ignoreExperimental);
} else {
throw new TypeError('Type requires `type` property.');
}
if (ignoreExperimental && type.experimental) {
type = null;
}
return type;
}
constructor(domain: string, obj: any) {
this.domain = domain;
this.id = obj.id;
this.description = obj.description;
this.exported = obj.exported;
this.experimental = obj.experimental;
}
getDebuggerName(): string {
return `${this.domain}.${this.id}`;
}
getCppNamespace(): string {
return toCppNamespace(this.domain);
}
getCppType(): string {
return toCppType(this.id);
}
getForwardDecls(): Array<string> {
throw new Error('subclass must implement');
}
getForwardDeclSortKey(): string {
return this.getCppType();
}
}
export class PrimitiveType extends Type {
type: 'integer' | 'number' | 'object' | 'string';
constructor(domain: string, obj: any, ignoreExperimental: boolean) {
super(domain, obj);
this.type = obj.type;
}
getForwardDecls(): Array<string> {
return [`using ${this.getCppType()} = ${jsTypeToCppType(this.type)};`];
}
}
export class PropsType extends Type {
type: 'object';
properties: Array<Property>;
constructor(domain: string, obj: any, ignoreExperimental: boolean) {
super(domain, obj);
this.type = obj.type;
this.properties = Property.createArray(
domain,
obj.properties || [],
ignoreExperimental,
);
}
getForwardDecls(): Array<string> {
return [`struct ${this.getCppType()};`];
}
}

View File

@ -0,0 +1,230 @@
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
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: this isn't a module, just a JSON file.
const proto = require('devtools-protocol/json/js_protocol.json');
type Descriptor = {|
types: Array<Type>,
commands: Array<Command>,
events: Array<Event>,
|};
function parseDomains(
domainObjs: Array<any>,
ignoreExperimental: boolean,
): 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, 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('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 [headerPath, implPath] = args._;
const headerStream = fs.createWriteStream(headerPath);
const implStream = fs.createWriteStream(implPath);
const desc = parseDomains(proto.domains, ignoreExperimental);
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();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
#!/bin/bash
set -e
DIR=$(dirname "${BASH_SOURCE[0]}")
cd "${DIR}/msggen"
yarn install
yarn build
FBSOURCE=$(hg root)
MSGTYPES_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/tools/message_types.txt"
HEADER_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/chrome/MessageTypes.h"
CPP_PATH="${FBSOURCE}/xplat/js/react-native-github/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp"
node bin/index.js \
--ignore-experimental \
--roots "$MSGTYPES_PATH" \
"$HEADER_PATH" "$CPP_PATH"
clang-format -i --style=file "$HEADER_PATH"
clang-format -i --style=file "$CPP_PATH"
"${FBSOURCE}/tools/signedsource" sign "$HEADER_PATH"
"${FBSOURCE}/tools/signedsource" sign "$CPP_PATH"

View File

@ -0,0 +1,13 @@
#!/bin/bash
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source "$THIS_DIR/setup.sh"
buck test //xplat/js/react-native-github/ReactCommon/hermes/inspector:chrome &&
buck test //xplat/js/react-native-github/ReactCommon/hermes/inspector:detail &&
buck test //xplat/js/react-native-github/ReactCommon/hermes/inspector:inspectorlib &&
buck build //xplat/js/react-native-github/ReactCommon/hermes/inspector:hermes-chrome-debug-server

View File

@ -0,0 +1,35 @@
#!/bin/bash
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#
# Basic setup for xplat testing in sandcastle. Based on
# xplat/hermes/facebook/sandcastle/setup.sh.
set -x
set -e
set -o pipefail
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
# Buck by default uses clang-3.6 from /opt/local/bin.
# Override it to use system clang.
export PATH="/usr/bin:$PATH"
# Enter xplat
cd "$ROOT_DIR"/xplat || exit 1
# Setup env
export TITLE
TITLE=$(hg log -l 1 --template "{desc|strip|firstline}")
export REV
REV=$(hg log -l 1 --template "{node}")
export AUTHOR
AUTHOR=$(hg log -l 1 --template "{author|emailuser}")
if [ -n "$SANDCASTLE" ]; then
source automation/setup_buck.sh
fi