Skip to content

Commit

Permalink
Add TypeScript proxy to support JrS debugging w/ Chrome DevTools (#4)
Browse files Browse the repository at this point in the history
Basic debugging features of step over, step in , step out, continue, stop,
add/remove breakpoints, provide backtrace at breakpoint, and evaluate watch
expressions are all working.

A few low-hanging features still missing:
- passthrough of output to CDT console
- reporting exceptions
- verbosity control

Other features of CDT will require enhancement to JrS debugger, e.g.:
- scope chain
- inspectable eval output (e.g. JSON, not just strings)
- memory analysis
- profiling

Significant level of unit testing provided via jest.

Thanks to Martijn The <[email protected]> for initial TS code skeleton,
some unit tests, 'step out' feature, and tons of review!

JerryScript-DCO-1.0-Signed-off-by: Geoff Gustafson [email protected]
  • Loading branch information
grgustaf authored Apr 24, 2018
1 parent 8aa06c8 commit ed66d7d
Show file tree
Hide file tree
Showing 25 changed files with 6,562 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist
node_modules
src/coverage
yarn-error.log
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,73 @@
[![Build Status](https://travis-ci.org/jerryscript-project/jerryscript-debugger-ts.svg?branch=master)](https://travis-ci.org/jerryscript-project/jerryscript-debugger-ts)
[![IRC Channel](https://img.shields.io/badge/chat-on%20freenode-brightgreen.svg)](https://kiwiirc.com/client/irc.freenode.net/#jerryscript)

Work in progress...
## Dependencies

- node.js
- `yarn` or `npm` (the steps below use `yarn`)

## Installing

```
$ cd jerryscript-debugger-ts
$ yarn install
```

## Building in watch mode

This will make the TypeScript compiler monitor the source files and re-build
files whenever there is a change.

```
$ cd jerryscript-debugger-ts
$ yarn build:watch
```

## Running tests

```
$ cd jerryscript-debugger-ts
$ yarn test
```

## Running tests in watch mode

This will make the test runner monitor the source files and re-run the tests
whenever there is a change.

```
$ cd jerryscript-debugger-ts
$ yarn test:watch
```

## Running the linter

```
$ cd jerryscript-debugger-ts
$ yarn lint
```

## Using

Build and run JerryScript with the debugger enabled. For example, with the
JerryScript Linux build:
```bash
$ cd jerryscript
$ python tools/build.py --jerry-debugger=on --jerry-libc=off
$ ./build/bin/jerry --start-debug-server --log-level 2 /path/to/source.js
```

Have Chrome running and visit the URL `chrome://inspect`, and click
"Open dedicated DevTools for Node."

Finally, run this project's proxy application:
```bash
$ cd jerryscript-debugger-ts
$ ./jerry-debugger.sh
```

This should connect to the debugger and give focus to the DevTools window to
start debugging.

## Contributing
The project can only accept contributions which are licensed under the [Apache License 2.0](LICENSE) and are signed according to the JerryScript [Developer's Certificate of Origin](DCO.md). For further information please see our [Contribution Guidelines](CONTRIBUTING.md).
Expand Down
6 changes: 6 additions & 0 deletions jerry-debugger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

set -e

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
node $DIR/dist/cli/index.js $@
56 changes: 52 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,61 @@
"name": "@jerryscript/debugger",
"version": "0.0.1",
"description": "JerryScript Debugger & Chrome DevTools Proxy",
"bin": {
"jerry-debugger": "dist/cli/index.js"
},
"scripts": {
"prepare": "echo prepare",
"lint": "echo lint",
"test": "echo test"
"prepare": "tsc",
"build": "tsc",
"build:watch": "tsc --watch",
"lint": "tslint -p .",
"test": "jest",
"test:watch": "jest --watch"
},
"repository": "https://github.com/jerryscript-project/jerryscript-debugger-ts/",
"author": "JS Foundation and other contributors",
"license": "Apache-2.0",
"private": false
"private": false,
"dependencies": {
"chrome-remote-debug-protocol": "^1.2.20170721",
"commander": "^2.15.0",
"noice-json-rpc": "^1.1.1",
"uuid": "^3.2.1",
"ws": "^3.3.2"
},
"devDependencies": {
"@types/commander": "^2.12.2",
"@types/jest": "^22.1.2",
"@types/minimist": "^1.2.0",
"@types/node": "^9.4.6",
"@types/uuid": "^3.4.3",
"@types/ws": "^4.0.1",
"jest": "^22.3.0",
"ts-jest": "^22.0.4",
"tslint": "^5.9.1",
"tslint-config-standard": "^7.0.0",
"typescript": "^2.7.1"
},
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"globals": {
"ts-jest": {
"enableTsDiagnostics": true
}
},
"rootDir": "src",
"resetMocks": true,
"resetModules": true
}
}
104 changes: 104 additions & 0 deletions src/cli/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright JS Foundation and other contributors, http://js.foundation
//
// 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.

import { getOptionsFromArgs } from '../cli';
import {
DEFAULT_DEBUGGER_HOST,
DEFAULT_DEBUGGER_PORT,
} from '../../lib/debugger-client';
import {
DEFAULT_SERVER_HOST,
DEFAULT_SERVER_PORT,
} from '../../lib/cdt-proxy';

describe('getOptionsFromArgs', () => {

function getOptionsFromUserArgs(userArgs: string[]) {
return getOptionsFromArgs(['node', 'jerry-debugger.js'].concat(userArgs));
}

it('works without --inspect-brk', () => {
const opt = getOptionsFromUserArgs([]);
expect(opt.proxyAddress.host).toEqual(DEFAULT_SERVER_HOST);
expect(opt.proxyAddress.port).toEqual(DEFAULT_SERVER_PORT);
});

it('parses --inspect-brk with port only', () => {
const opt = getOptionsFromUserArgs(['--inspect-brk=1234']);
expect(opt.proxyAddress.host).toEqual(undefined);
expect(opt.proxyAddress.port).toEqual(1234);
});

it('parses --inspect-brk with no port', () => {
const opt = getOptionsFromUserArgs(['--inspect-brk=10.10.10.10:']);
expect(opt.proxyAddress.host).toEqual('10.10.10.10');
expect(opt.proxyAddress.port).toEqual(undefined);
});

it('parses --inspect-brk with no host', () => {
const opt = getOptionsFromUserArgs(['--inspect-brk=:1234']);
expect(opt.proxyAddress.host).toEqual(undefined);
expect(opt.proxyAddress.port).toEqual(1234);
});

it('parses --inspect-brk with host and port', () => {
const opt = getOptionsFromUserArgs(['--inspect-brk=10.10.10.10:1234']);
expect(opt.proxyAddress.host).toEqual('10.10.10.10');
expect(opt.proxyAddress.port).toEqual(1234);
});

it('works without --jerry-remote', () => {
const opt = getOptionsFromUserArgs([]);
expect(opt.remoteAddress.host).toEqual(DEFAULT_DEBUGGER_HOST);
expect(opt.remoteAddress.port).toEqual(DEFAULT_DEBUGGER_PORT);
});

it('parses --jerry-remote with port only', () => {
const opt = getOptionsFromUserArgs(['--jerry-remote=1234']);
expect(opt.remoteAddress.host).toEqual(undefined);
expect(opt.remoteAddress.port).toEqual(1234);
});

it('parses --jerry-remote with host and port', () => {
const opt = getOptionsFromUserArgs(['--jerry-remote=10.10.10.10:1234']);
expect(opt.remoteAddress.host).toEqual('10.10.10.10');
expect(opt.remoteAddress.port).toEqual(1234);
});

it('verbose defaults to false', () => {
const opt = getOptionsFromUserArgs([]);
expect(opt.verbose).toEqual(false);
});

it('parses verbose flag', () => {
const opt = getOptionsFromUserArgs(['--verbose']);
expect(opt.verbose).toEqual(true);
});

it('parses v alias for verbose', () => {
const opt = getOptionsFromUserArgs(['-v']);
expect(opt.verbose).toEqual(true);
});

it('jsfile defaults to untitled.js', () => {
const opt = getOptionsFromUserArgs([]);
expect(opt.jsfile).toEqual('untitled.js');
});

it('returns client source as jsfile', () => {
const opt = getOptionsFromUserArgs(['foo/bar.js']);
expect(opt.jsfile).toEqual('foo/bar.js');
});

});
97 changes: 97 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright JS Foundation and other contributors, http://js.foundation
//
// 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.

import { CDTController } from '../lib/cdt-controller';
import {
ChromeDevToolsProxyServer,
DEFAULT_SERVER_HOST,
DEFAULT_SERVER_PORT,
} from '../lib/cdt-proxy';
import {
JerryDebuggerClient,
DEFAULT_DEBUGGER_HOST,
DEFAULT_DEBUGGER_PORT,
} from '../lib/debugger-client';
import { Command } from 'commander';
import { JerryDebugProtocolHandler } from '../lib/protocol-handler';

/**
* Converts string of format [host:][port] to an object with host and port,
* each possibly undefined
*/
function getHostAndPort(input: string) {
const hostAndPort = input.split(':');
const portIndex = hostAndPort.length - 1;
const host = hostAndPort[portIndex - 1];
const port = hostAndPort[portIndex];
return {
host: host ? host : undefined,
port: port ? Number(port) : undefined,
};
}

export function getOptionsFromArgs(argv: Array<string>) {
const program = new Command('jerry-debugger');
program
.usage('[options] <script.js ...>')
.option(
'-v, --verbose',
'Enable verbose logging', false)
.option(
'--inspect-brk [[host:]port]',
'Activate Chrome DevTools proxy on host:port',
`${DEFAULT_SERVER_HOST}:${DEFAULT_SERVER_PORT}`)
.option(
'--jerry-remote [[host:]port]',
'Connect to JerryScript on host:port',
`${DEFAULT_DEBUGGER_HOST}:${DEFAULT_DEBUGGER_PORT}`)
.parse(argv);

return {
proxyAddress: getHostAndPort(program.inspectBrk),
remoteAddress: getHostAndPort(program.jerryRemote),
jsfile: program.args[0] || 'untitled.js',
verbose: program.verbose || false,
};
}

export function main(proc: NodeJS.Process) {
const options = getOptionsFromArgs(proc.argv);

const controller = new CDTController();
const jhandler = new JerryDebugProtocolHandler(controller);
const jclient = new JerryDebuggerClient({
delegate: jhandler,
...options.remoteAddress,
});
jhandler.debuggerClient = jclient;
// set this before connecting to the client
controller.protocolHandler = jhandler;

const debuggerUrl = `ws://${jclient.host}:${jclient.port}`;
jclient.connect().then(() => {
console.log(`Connected to debugger at ${debuggerUrl}`);
const proxy = new ChromeDevToolsProxyServer({
delegate: controller,
...options.proxyAddress,
jsfile: options.jsfile,
});
// set this before making further debugger calls
controller.proxyServer = proxy;
console.log(`Proxy listening at ws://${proxy.host}:${proxy.port}`);
}).catch((err) => {
console.log(`Error connecting to debugger at ${debuggerUrl}`);
console.log(err);
});
}
17 changes: 17 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright JS Foundation and other contributors, http://js.foundation
//
// 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.

import { main } from './cli';

main(process);
17 changes: 17 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright JS Foundation and other contributors, http://js.foundation
//
// 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.

// TODO: re-export more things here from lib/ that we want to expose as 'API'

export { ChromeDevToolsProxyServer } from './lib/cdt-proxy';
Loading

0 comments on commit ed66d7d

Please sign in to comment.