diff options
author | Jan Krems <jan.krems@groupon.com> | 2016-12-11 14:36:58 -0800 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2017-02-13 14:46:12 +0100 |
commit | 8c9762e150362c9b7f4db8e5c1680f1758f0ef9f (patch) | |
tree | 05b50a0014663acd3f1c0483eaf5d8013440c024 /deps/node-inspect | |
parent | b1fc7745f2c7f279d388d8c88781df776b740259 (diff) | |
download | node-new-8c9762e150362c9b7f4db8e5c1680f1758f0ef9f.tar.gz |
deps: add node-inspect 1.10.2
Squashed from:
- deps: Add node-inspect 1.10.1
This adds a reimplementation of the old CLI debugger (`node debug`)
against the new debugger protocol (`node --inspect`). This is necessary
because the old protocol won't be supported in future versions of V8.
- deps: Update node-inspect to 1.10.2
Starting with 1.10.2 the test suite should pass consistently on
windows.
- deps: Update to node-inspect 1.10.4
PR-URL: https://github.com/nodejs/node/pull/10187
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'deps/node-inspect')
48 files changed, 3792 insertions, 0 deletions
diff --git a/deps/node-inspect/.editorconfig b/deps/node-inspect/.editorconfig new file mode 100644 index 0000000000..beffa3084e --- /dev/null +++ b/deps/node-inspect/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/deps/node-inspect/.eslintrc b/deps/node-inspect/.eslintrc new file mode 100644 index 0000000000..c731203df6 --- /dev/null +++ b/deps/node-inspect/.eslintrc @@ -0,0 +1,147 @@ +root: true + +env: + node: true + es6: true + +parserOptions: + ecmaVersion: 2016 + +rules: + # Possible Errors + # http://eslint.org/docs/rules/#possible-errors + comma-dangle: [2, only-multiline] + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: [2, functions] + no-extra-semi: 2 + no-func-assign: 2 + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-obj-calls: 2 + no-proto: 2 + no-template-curly-in-string: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + no-unsafe-negation: 2 + use-isnan: 2 + valid-typeof: 2 + + # Best Practices + # http://eslint.org/docs/rules/#best-practices + dot-location: [2, property] + no-fallthrough: 2 + no-global-assign: 2 + no-multi-spaces: 2 + no-octal: 2 + no-redeclare: 2 + no-self-assign: 2 + no-unused-labels: 2 + no-useless-call: 2 + no-useless-escape: 2 + no-void: 2 + no-with: 2 + + # Strict Mode + # http://eslint.org/docs/rules/#strict-mode + strict: [2, global] + + # Variables + # http://eslint.org/docs/rules/#variables + no-delete-var: 2 + no-undef: 2 + no-unused-vars: [2, {args: none}] + + # Node.js and CommonJS + # http://eslint.org/docs/rules/#nodejs-and-commonjs + no-mixed-requires: 2 + no-new-require: 2 + no-path-concat: 2 + no-restricted-modules: [2, sys, _linklist] + no-restricted-properties: [2, { + object: assert, + property: deepEqual, + message: Please use assert.deepStrictEqual(). + }, { + property: __defineGetter__, + message: __defineGetter__ is deprecated. + }, { + property: __defineSetter__, + message: __defineSetter__ is deprecated. + }] + + # Stylistic Issues + # http://eslint.org/docs/rules/#stylistic-issues + brace-style: [2, 1tbs, {allowSingleLine: true}] + comma-spacing: 2 + comma-style: 2 + computed-property-spacing: 2 + eol-last: 2 + func-call-spacing: 2 + func-name-matching: 2 + indent: [2, 2, {SwitchCase: 1, MemberExpression: 1}] + key-spacing: [2, {mode: minimum}] + keyword-spacing: 2 + linebreak-style: [2, unix] + max-len: [2, 80, 2] + new-parens: 2 + no-mixed-spaces-and-tabs: 2 + no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] + no-tabs: 2 + no-trailing-spaces: 2 + quotes: [2, single, avoid-escape] + semi: 2 + semi-spacing: 2 + space-before-blocks: [2, always] + space-before-function-paren: [2, never] + space-in-parens: [2, never] + space-infix-ops: 2 + space-unary-ops: 2 + + # ECMAScript 6 + # http://eslint.org/docs/rules/#ecmascript-6 + arrow-parens: [2, always] + arrow-spacing: [2, {before: true, after: true}] + constructor-super: 2 + no-class-assign: 2 + no-confusing-arrow: 2 + no-const-assign: 2 + no-dupe-class-members: 2 + no-new-symbol: 2 + no-this-before-super: 2 + prefer-const: [2, {ignoreReadBeforeAssign: true}] + rest-spread-spacing: 2 + template-curly-spacing: 2 + + # Custom rules in tools/eslint-rules + align-function-arguments: 2 + align-multiline-assignment: 2 + assert-fail-single-argument: 2 + new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] + +# Global scoped method and vars +globals: + COUNTER_HTTP_CLIENT_REQUEST: false + COUNTER_HTTP_CLIENT_RESPONSE: false + COUNTER_HTTP_SERVER_REQUEST: false + COUNTER_HTTP_SERVER_RESPONSE: false + COUNTER_NET_SERVER_CONNECTION: false + COUNTER_NET_SERVER_CONNECTION_CLOSE: false + DTRACE_HTTP_CLIENT_REQUEST: false + DTRACE_HTTP_CLIENT_RESPONSE: false + DTRACE_HTTP_SERVER_REQUEST: false + DTRACE_HTTP_SERVER_RESPONSE: false + DTRACE_NET_SERVER_CONNECTION: false + DTRACE_NET_STREAM_END: false + LTTNG_HTTP_CLIENT_REQUEST: false + LTTNG_HTTP_CLIENT_RESPONSE: false + LTTNG_HTTP_SERVER_REQUEST: false + LTTNG_HTTP_SERVER_RESPONSE: false + LTTNG_NET_SERVER_CONNECTION: false + LTTNG_NET_STREAM_END: false diff --git a/deps/node-inspect/.gitignore b/deps/node-inspect/.gitignore new file mode 100644 index 0000000000..72e2c8c180 --- /dev/null +++ b/deps/node-inspect/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +npm-debug.log +/tmp +/.vs diff --git a/deps/node-inspect/.npmrc b/deps/node-inspect/.npmrc new file mode 100644 index 0000000000..38f11c645a --- /dev/null +++ b/deps/node-inspect/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org diff --git a/deps/node-inspect/.travis.yml b/deps/node-inspect/.travis.yml new file mode 100644 index 0000000000..07418a91eb --- /dev/null +++ b/deps/node-inspect/.travis.yml @@ -0,0 +1,13 @@ +language: node_js +node_js: + - '6.8' +before_deploy: + - git config --global user.email "jan.krems@gmail.com" + - git config --global user.name "Jan Krems" +deploy: + provider: script + script: ./node_modules/.bin/nlm release + skip_cleanup: true + 'on': + branch: master + node: '6.8' diff --git a/deps/node-inspect/CHANGELOG.md b/deps/node-inspect/CHANGELOG.md new file mode 100644 index 0000000000..0a7ba1be94 --- /dev/null +++ b/deps/node-inspect/CHANGELOG.md @@ -0,0 +1,212 @@ +### 1.10.4 + +* [`1c31bf7`](https://github.com/buggerjs/node-inspect/commit/1c31bf7d1b3ea1b424ae0662526596670cb506c9) **chore:** Support embedded mode + + +### 1.10.3 + +* [`7b20379`](https://github.com/buggerjs/node-inspect/commit/7b20379069af692a9038a31a4465f72db9eb532f) **chore:** Mark .eslintrc as root + + +### 1.10.2 + +* Run tests on windows - **[@jkrems](https://github.com/jkrems)** [#16](https://github.com/buggerjs/node-inspect/pull/16) + - [`5a57f98`](https://github.com/buggerjs/node-inspect/commit/5a57f9865e02eef0763c2a7f26236c34a632ccdd) **chore:** Run tests on windows + - [`0a04b50`](https://github.com/buggerjs/node-inspect/commit/0a04b50cc8b4dc6ce868927c635c479d75ce71f4) **chore:** Bump nlm to get rid of postinstall + - [`4a8b27c`](https://github.com/buggerjs/node-inspect/commit/4a8b27cea814a37895effd2a0c1b85dbfee3a7f4) **test:** Remove unix path assumptions + + +### 1.10.1 + +* [`4ba3c72`](https://github.com/buggerjs/node-inspect/commit/4ba3c72270fae9a71343ddca11aa27980678a67c) **refactor:** Undo weird bundling into one file + + +### 1.10.0 + +* [`3e1a66a`](https://github.com/buggerjs/node-inspect/commit/3e1a66a489bef19beaa5f859e99e027274ff43cb) **feat:** Support CPU & heap profiles + + +### 1.9.3 + +* Move back to single file - **[@jkrems](https://github.com/jkrems)** [#15](https://github.com/buggerjs/node-inspect/pull/15) + - [`9877660`](https://github.com/buggerjs/node-inspect/commit/9877660a73ff0ec0885ad7f939ba62020a46b4b6) **refactor:** Wrap client in IIFE + - [`7795c53`](https://github.com/buggerjs/node-inspect/commit/7795c533f0605eb128db610a5874b27e555251ef) **refactor:** Move more code in createRepl scope + - [`be34a39`](https://github.com/buggerjs/node-inspect/commit/be34a398e823612bdf5ac90bad5222af27035a00) **refactor:** Move back to single file + - [`ab45b62`](https://github.com/buggerjs/node-inspect/commit/ab45b6273dc0d3a49d3cf46a80cb48ab79d1caf8) **refactor:** Remove single-use functions + - [`37a711e`](https://github.com/buggerjs/node-inspect/commit/37a711ed5334c06ed4d85f995e567a9f176a68d5) **style:** Stop using `new Buffer` + - [`d669dc5`](https://github.com/buggerjs/node-inspect/commit/d669dc593f5ad5ca7a48f19f0905ef66ec0e540d) **chore:** Switch to node eslint rules + - [`15e7917`](https://github.com/buggerjs/node-inspect/commit/15e79177918d96dcffd2384715faf0308e97a26c) **style:** Use var in classical for loops + + +### 1.9.2 + +* [`c9dc4be`](https://github.com/buggerjs/node-inspect/commit/c9dc4beb08236e33d64f19417682cf5b3f5aeed6) **doc:** Link directly to GOVERNANCE file + + +### 1.9.1 + +* Handle big ws frames correctly - **[@jkrems](https://github.com/jkrems)** [#14](https://github.com/buggerjs/node-inspect/pull/14) + - [`f80100e`](https://github.com/buggerjs/node-inspect/commit/f80100e932710d232d074b239cbf8fefa564c789) **fix:** Handle big ws frames correctly - see: [#10](https://github.com/buggerjs/node-inspect/issues/10) + + +### 1.9.0 + +* Support for low-level agent access - **[@jkrems](https://github.com/jkrems)** [#13](https://github.com/buggerjs/node-inspect/pull/13) + - [`90ed431`](https://github.com/buggerjs/node-inspect/commit/90ed4310c62d130637c12f8ecdb752075c43ac36) **feat:** Support for low-level agent access + + +### 1.8.4 + +* Use proper path for websocket - **[@jkrems](https://github.com/jkrems)** [#12](https://github.com/buggerjs/node-inspect/pull/12) + - [`3405225`](https://github.com/buggerjs/node-inspect/commit/3405225979dfc2058bcc6d1b90f41c060dbd1f92) **fix:** Use proper path for websocket - see: [#11](https://github.com/buggerjs/node-inspect/issues/11) + + +### 1.8.3 + +* [`6f9883d`](https://github.com/buggerjs/node-inspect/commit/6f9883d4b29419831133988981b83e891b19739a) **fix:** Breakpoints & scripts work when not paused +* [`ecb1362`](https://github.com/buggerjs/node-inspect/commit/ecb1362c842e6ed5bc28c091a32bfd540742db75) **chore:** Pin node to 6.8 + + +### 1.8.2 + +* [`4219a98`](https://github.com/buggerjs/node-inspect/commit/4219a98d6514f1068feabce2945c21a0d5ba6561) **refactor:** Decouple source snippet from repl + + +### 1.8.1 + +* [`95402ee`](https://github.com/buggerjs/node-inspect/commit/95402ee5dff04057f074677d39db2f61ec74c151) **refactor:** Move `list` into CallFrame + + +### 1.8.0 + +* [`d0e6499`](https://github.com/buggerjs/node-inspect/commit/d0e6499084f5d656ef0c5fd470d3ab21f2e9a6b4) **feat:** `exec .scope` + + +### 1.7.0 + +* `breakOn{Exception,Uncaught,None}` - **[@jkrems](https://github.com/jkrems)** [#8](https://github.com/buggerjs/node-inspect/pull/8) + - [`fa8c4c7`](https://github.com/buggerjs/node-inspect/commit/fa8c4c7d7bb6972733c92da4d04fdd62c02b0e3b) **feat:** `breakOn{Exception,Uncaught,None}` - see: [#6](https://github.com/buggerjs/node-inspect/issues/6) + + +### 1.6.0 + +* Add `help` command - **[@jkrems](https://github.com/jkrems)** [#7](https://github.com/buggerjs/node-inspect/pull/7) + - [`09b37a0`](https://github.com/buggerjs/node-inspect/commit/09b37a02e04e16a38ce27f69538d3b098548b47c) **feat:** Add `help` command - see: [#5](https://github.com/buggerjs/node-inspect/issues/5) + + +### 1.5.0 + +* [`7e0fd99`](https://github.com/buggerjs/node-inspect/commit/7e0fd99fcfc65d8b647a2259df78f4cabf1d3d63) **feat:** Add `r` shortcut for `run` + + +### 1.4.1 + +* [`484d098`](https://github.com/buggerjs/node-inspect/commit/484d0983f06d6ff9639ab5197ba0a58313f532df) **chore:** Remove old implementation + + +### 1.4.0 + +* Properly tested implementation - **[@jkrems](https://github.com/jkrems)** [#4](https://github.com/buggerjs/node-inspect/pull/4) + - [`ba060d3`](https://github.com/buggerjs/node-inspect/commit/ba060d3ef65ae84df2a3a9b9f16d563f3c4b29be) **feat:** Error handling w/o args + - [`b39b3bc`](https://github.com/buggerjs/node-inspect/commit/b39b3bc07c13adc48fc8bb720889285c51e62548) **feat:** Launch child + - [`481693f`](https://github.com/buggerjs/node-inspect/commit/481693f676ee099b7787cd2426b980858e973602) **feat:** Connect debug client + - [`3bba0f2`](https://github.com/buggerjs/node-inspect/commit/3bba0f2416b2e3b4e6010de675003fcc328b16e8) **chore:** Disable lint for inactive code + - [`cc7bdfc`](https://github.com/buggerjs/node-inspect/commit/cc7bdfcf7f21ef5cd5c32c7800407238b0d4f100) **feat:** Properly fail with invalid host:port + - [`73f34f9`](https://github.com/buggerjs/node-inspect/commit/73f34f902634e9778597e129f46895aa8b643d72) **refactor:** Remove unused field + - [`6a23e0c`](https://github.com/buggerjs/node-inspect/commit/6a23e0cf3179f43ca6fc5a0fa2b1dd18ebc044b5) **refactor:** Better debug output & support node 6.6 + - [`63b0f9b`](https://github.com/buggerjs/node-inspect/commit/63b0f9b6ef8bd9af0f7cb14a5938a45838731fc9) **test:** Add timeout to waitFor(pattern) + - [`cfa197b`](https://github.com/buggerjs/node-inspect/commit/cfa197bf8325a1a4ca1b296f8d6971d368bfbfbb) **refactor:** Move REPL setup into own file + - [`3f46c2c`](https://github.com/buggerjs/node-inspect/commit/3f46c2c43f836e1135b66871087aa74969f6b330) **feat:** Working repl eval + - [`6911eb1`](https://github.com/buggerjs/node-inspect/commit/6911eb1a00b964bc5683506d433fa4f665f5a82c) **feat:** Enter repeats last command + - [`7d20b7d`](https://github.com/buggerjs/node-inspect/commit/7d20b7deadf1b251ea8cf2cc9167c175624932c4) **chore:** Add missing license header + - [`23c62f8`](https://github.com/buggerjs/node-inspect/commit/23c62f8375ca7c8b71d032047e728dace02f4efa) **feat:** Print break context + - [`5dbc83d`](https://github.com/buggerjs/node-inspect/commit/5dbc83df31171f9c38a974c99340bde26f2e24ec) **feat:** Stepping and breakpoints + - [`8deb8cc`](https://github.com/buggerjs/node-inspect/commit/8deb8cc36b9fca432ab8df63a82e9de7ab5adaf0) **feat:** list for printing source + - [`1ed2ec9`](https://github.com/buggerjs/node-inspect/commit/1ed2ec9937070652be611dbb6b11dfb42cb840f8) **chore:** Disable verbose output on CI + - [`625a435`](https://github.com/buggerjs/node-inspect/commit/625a435925dd8fd980bed2dc9e3fd73dd27df4ef) **fix:** Gracefully handle delayed scriptParsed + - [`8823c60`](https://github.com/buggerjs/node-inspect/commit/8823c60d347600b2313cfdd8cb5e96fe02419a8a) **chore:** Run all the tests + - [`00506f7`](https://github.com/buggerjs/node-inspect/commit/00506f763928cc440505a81030167a11b9a84e00) **feat:** backtrace/bt + - [`e1ee02d`](https://github.com/buggerjs/node-inspect/commit/e1ee02d5cc389916489d387d07d5dd161230427a) **refactor:** Leverage util.inspect.custom + - [`5dcc319`](https://github.com/buggerjs/node-inspect/commit/5dcc31922d40f56c7435319d1538390a442e8e4b) **feat:** scripts and scripts(true) + - [`085cd5a`](https://github.com/buggerjs/node-inspect/commit/085cd5a76a961edfcaa342fff5eb09bf2f9c8983) **refactor:** Consistent import style + - [`1c60f91`](https://github.com/buggerjs/node-inspect/commit/1c60f91f233848c05d865617dc7f5aacb36270b6) **feat:** Set breakpoint before file is loaded + - [`bc82ecc`](https://github.com/buggerjs/node-inspect/commit/bc82eccb2a1a7c0f5332371254f6584e748216aa) **feat:** breakpoints to list breakpoints + - [`7f48c95`](https://github.com/buggerjs/node-inspect/commit/7f48c9510696ec400d51afaca8d23a9c292640f8) **feat:** watchers & exec + - [`0f8cd13`](https://github.com/buggerjs/node-inspect/commit/0f8cd13a092e5dbeb395ff04cbe2ed97cb986423) **feat:** clearBreakpoint + - [`0d31560`](https://github.com/buggerjs/node-inspect/commit/0d315603bdcb9f4da42fab24dc569c325151269e) **feat:** version to print v8 version + - [`df6b89d`](https://github.com/buggerjs/node-inspect/commit/df6b89df580a9afcb3b8883b0e4224cbcebb384f) **feat:** Paused & global exec + - [`9e97d73`](https://github.com/buggerjs/node-inspect/commit/9e97d73073ceffd70974d45887c84fadb9159d5c) **feat:** repl to enter exec mode + - [`9ee9f90`](https://github.com/buggerjs/node-inspect/commit/9ee9f903d6202f54ed2b3b3559da4006b65d39b5) **feat:** run & restart +* [`3a752aa`](https://github.com/buggerjs/node-inspect/commit/3a752aaa773968bfe16c5f543bd739feed598bea) **feat:** kill +* [`a67e470`](https://github.com/buggerjs/node-inspect/commit/a67e47018b20d46aeeaa7abd27eb8e7770fd0b8f) **feat:** Restore breakpoints on restart + + +### 1.3.3 + +* [`eb7a54c`](https://github.com/buggerjs/node-inspect/commit/eb7a54c6fa731ed3276072c72034046fc5ffbac6) **chore:** Switch to tap for tests + + +### 1.3.2 + +* Add notes about governance - **[@jkrems](https://github.com/jkrems)** [#3](https://github.com/buggerjs/node-inspect/pull/3) + - [`e94089d`](https://github.com/buggerjs/node-inspect/commit/e94089d93689cacf5c953e94563463d1e174452d) **chore:** Add notes about governance + + +### 1.3.1 + +* [`8767137`](https://github.com/buggerjs/node-inspect/commit/8767137c53a2f6b1d36970074ea95be9871e50e3) **style:** Remove rogue console.log + + +### 1.3.0 + +* [`3ac6232`](https://github.com/buggerjs/node-inspect/commit/3ac623219ba44b0af40ef66826610a26a46c7966) **feat:** Add `version` command + + +### 1.2.0 + +* [`86b5812`](https://github.com/buggerjs/node-inspect/commit/86b581218ccab44e6bde259a17ad1e71645a6137) **feat:** scripts & listScripts(true) + + +### 1.1.1 + +* [`feaea38`](https://github.com/buggerjs/node-inspect/commit/feaea385a981e6b72a8d99277fbf575c54e15fc6) **style:** Typo in comment + + +### 1.1.0 + +* [`c64155f`](https://github.com/buggerjs/node-inspect/commit/c64155faa552f71463842a26330aa5bcbfc31670) **feat:** repl command + + +### 1.0.0 + +* [`44c4c79`](https://github.com/buggerjs/node-inspect/commit/44c4c79af5a228ccfd8906f11409b2a33390b878) **chore:** Initial commit +* [`985873c`](https://github.com/buggerjs/node-inspect/commit/985873cfb97146b38480080f9907219c473f1f6f) **feat:** Launching the example works +* [`3d92d05`](https://github.com/buggerjs/node-inspect/commit/3d92d05cca152a2c2647aa64eefc80432638bc4d) **chore:** Proper license and passing tests +* [`b3f99d9`](https://github.com/buggerjs/node-inspect/commit/b3f99d981038b17663fcfd984d2f5d6d9b51ee18) **feat:** Futile attempts to send a valid ws frame +* [`465cfb7`](https://github.com/buggerjs/node-inspect/commit/465cfb7b295aebb48b285c26f6de9c4657fe590d) **feat:** Working ws connection +* [`da9f011`](https://github.com/buggerjs/node-inspect/commit/da9f01118e2b144f2da8cd370113a608526774a1) **fix:** Fix remote connect +* [`5ef33d7`](https://github.com/buggerjs/node-inspect/commit/5ef33d7892cc49becb4c66098fc7927bc74b014a) **feat:** Working step-by-step +* [`534e1e4`](https://github.com/buggerjs/node-inspect/commit/534e1e46b307d61d51eb4c0aab4a3b17c17aea3d) **chore:** Add bin entry +* [`8cff9cf`](https://github.com/buggerjs/node-inspect/commit/8cff9cfb0138b5ecff0f5f6a7839dbfddc0684fd) **style:** Use simpler key thingy +* [`720ec53`](https://github.com/buggerjs/node-inspect/commit/720ec53a5b251ab3caf27f06b60924efb9e03a92) **doc:** Add instructions +* [`b89ad60`](https://github.com/buggerjs/node-inspect/commit/b89ad601b885a417e6433b1609477d8453f498a1) **doc:** More helpful docs +* [`de9243c`](https://github.com/buggerjs/node-inspect/commit/de9243c95eabe733d05952229340808c3cebf129) **feat:** Watchers +* [`e16978f`](https://github.com/buggerjs/node-inspect/commit/e16978ff8e4b2b2bdccf88fd7d3905f525822981) **docs:** Working usage hints +* [`2dbc204`](https://github.com/buggerjs/node-inspect/commit/2dbc2042145fd97169fc7536186a449715e27810) **refactor:** Use proxies +* [`b8c9b14`](https://github.com/buggerjs/node-inspect/commit/b8c9b147713f63181396d5a7fe4c2f737b733b4c) **style:** Remove unused var +* [`f6b4b20`](https://github.com/buggerjs/node-inspect/commit/f6b4b20a1d28d91cfe452b995f7dbe5f7c749e89) **feat:** Nicer inspect of remote values +* [`36887c6`](https://github.com/buggerjs/node-inspect/commit/36887c66bbf26d540f087f80ddfec38462a33bdf) **fix:** Properly print watchers +* [`7729442`](https://github.com/buggerjs/node-inspect/commit/77294426157a28cc76e339cb13916a205182641e) **feat:** Add pause command +* [`e39a713`](https://github.com/buggerjs/node-inspect/commit/e39a7134873f06da37baaa9b6252cede4ad38d7a) **fix:** Properly format boolean properties +* [`f8f51d7`](https://github.com/buggerjs/node-inspect/commit/f8f51d7a01e8d74023306a08a3d6e2da63d123e1) **fix:** Properly format numeric properties +* [`89e6e08`](https://github.com/buggerjs/node-inspect/commit/89e6e087220f3c3cb628ac7541c44298485a2e04) **feat:** Add backtrace command +* [`82362ac`](https://github.com/buggerjs/node-inspect/commit/82362acfc7ce22b4cccc64889ec136dedc8895ec) **feat:** Add setBreakpoint() +* [`7064cce`](https://github.com/buggerjs/node-inspect/commit/7064ccec3b103683088d532abfe5b4e7c066948b) **feat:** Add `setBreakpoint(line)` +* [`360580e`](https://github.com/buggerjs/node-inspect/commit/360580eba4353e81311e56df018eec0ca233da11) **feat:** Add run/kill/restart +* [`b1b576e`](https://github.com/buggerjs/node-inspect/commit/b1b576e2645723a8575df544e0bfb672d60d9d91) **feat:** Add `help` command +* [`2db4660`](https://github.com/buggerjs/node-inspect/commit/2db46609cd1c8543d31ebd5dc47e4c27ec254841) **feat:** Add remaining sb() variants +* [`f2ad1ae`](https://github.com/buggerjs/node-inspect/commit/f2ad1aeedafb154043d70bb9195b10986d311d26) **fix:** Display breakpoints set into the future +* [`73272f9`](https://github.com/buggerjs/node-inspect/commit/73272f9ace1f8546f8cad1d53627dbffba50bb4e) **refactor:** Make breakpoints more inspect friendly +* [`507a71d`](https://github.com/buggerjs/node-inspect/commit/507a71de345a3de7fe144517e9f5ea264ff993e3) **feat:** Add breakpoints command +* [`5fb3e5d`](https://github.com/buggerjs/node-inspect/commit/5fb3e5d17bbcfd45b264431547b3cf0b781c7640) **docs:** Link to Command Line API docs +* [`81af501`](https://github.com/buggerjs/node-inspect/commit/81af501bbf85397e2078310c7f24a9ac5b7f02dc) **chore:** Fix license field diff --git a/deps/node-inspect/CONTRIBUTING.md b/deps/node-inspect/CONTRIBUTING.md new file mode 100644 index 0000000000..08d333cce8 --- /dev/null +++ b/deps/node-inspect/CONTRIBUTING.md @@ -0,0 +1,183 @@ +# Contributing + +🎉🏅 Thanks for helping us improve this project! 🙏 + +This document outlines some of the practices we care about. +If you have any questions or suggestions about the process, +feel free to [open an issue](#reporting-issues) +. + +## Code of Conduct + +The [Node.js Code of Conduct][] applies to this repo. + +[Node.js Code of Conduct]: https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md + +## Code Contributions + +The nodereport project falls under the governance of the diagnostics +working group which is documented in: +https://github.com/nodejs/diagnostics/blob/master/GOVERNANCE.md. + +## Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +* (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +## How Can I Contribute? + +### Reporting Issues + +If you find any mistakes in the docs or a bug in the code, +please [open an issue in Github](https://github.com/nodejs/node-inspect/issues/new) so we can look into it. +You can also [create a PR](#contributing-code) fixing it yourself, or course. + +If you report a bug, please follow these guidelines: + +* Make sure the bug exists in the latest version. +* Include instructions on how to reproduce the issue. + The instructions should be as minimal as possible + and answer the three big questions: + 1. What are the exact steps you took? This includes the exact versions of node, npm, and any packages involved. + 1. What result are you expecting? + 1. What is the actual result? + +### Improving Documentation + +For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/). +The only thing to keep in mind is to prefix the commit message with "docs: ". +The detault commit message generated by Github will lead to a failing CI build. + +For larger updates to the documentation +it might be better to follow the [instructions for contributing code below](#contributing-code). + +### Contributing Code + +**Note:** If you're planning on making substantial changes, +please [open an issue first to discuss your idea](#reporting-issues). +Otherwise you might end up investing a lot of work +only to discover that it conflicts with plans the maintainers might have. + +The general steps for creating a pull request are: + +1. Create a branch for your change. + Always start your branch from the latest `master`. + We often prefix the branch name with our initials, e.g. `jk-a-change`. +1. Run `npm install` to install the dependencies. +1. If you're fixing a bug, be sure to write a test *first*. + That way you can validate that the test actually catches the bug and doesn't pass. +1. Make your changes to the code. + Remember to update the tests if you add new features or change behavior. +1. Run the tests via `npm test`. This will also run style checks and other validations. + You might see errors about uncommitted files. + This is expected until you commit your changes. +1. Once you're done, `git add .` and `git commit`. + Please follow the [commit message conventions](#commits--commit-messages) described below. +1. Push your branch to Github & create a PR. + +#### Code Style + +In addition to any linting rules the project might include, +a few general rules of thumb: + +* Try to match the style of the rest of the code. +* We prefer simple code that is easy to understand over terse, expressive code. +* We try to structure projects by semantics instead of role. + E.g. we'd rather have a `tree.js` module that contains tree traversal-related helpers + than a `helpers.js` module. +* Actually, if you create helpers you might want to put those into a separate package. + That way it's easier to reuse them. + +#### Commits & Commit Messages + +Please follow the [angular commit message conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines). +We use an automated tool for generating releases +that depends on the conventions to determine the next version and the content of the changelog. +Commit messages that don't follow the conventions will cause `npm test` (and thus CI) to fail. + +The short summary - a commit message should look like this: + +``` +<type>: <subject> + +<body> + +<references> + +<footer> +``` + +Everything but the first line is optional. +The empty lines between the different parts are required. + +* `<type>`: One of the following: + - **feat:** Introduces a new feature. This will cause the minor version to go up. + - **fix:** A bug fix. Causes a patch version bump. + - **docs:** Changes to the documentation. + This will also cause an increase of the patch version so that the changes show up in the npm registry. + - **style:** Cleanup & lint rule fixes. + Note that often it's better to just amend the previous commit if it introduced lint errors. + - **refactor:** Changes to the code structure without fixing bugs or adding features. + - **perf:** Performance optimizations. + - **test:** Fixing existing tests or adding missing ones. + Just like with **style**, if you add tests to a feature you just introduced in the previous commit, + consider keeping the tests and the feature in the same commit instead. + - **chore:** Changes to the project setup and tools, dependency bumps, house-keeping. +* `<subject>`: A [good git commit message subject](http://chris.beams.io/posts/git-commit/#limit-50). + - Keep it brief. If possible the whole first line should have at most 50 characters. + - Use imperative mood. "Create" instead of "creates" or "created". + - No period (".") at the end. +* `<body>`: Motivation for the change and any context required for understanding the choices made. + Just like the subject, it should use imperative mood. +* `<references>`: Any URLs relevant to the PR go here. + Use one line per URL and prefix it with the kind of relationship, e.g. "Closes: " or "See: ". + If you are referencing an issue in your commit body or PR description, + never use `#123` but the full URL to the issue or PR you are referencing. + That way the reference is easy to resolve from the git history without having to "guess" the correct link + even if the commit got cherry-picked or merged into a different project. +* `<footer>`: This part only applies if your commit introduces a breaking change. + It's important this is present, otherwise the major version will not increase. + See below for an example. + +##### Examples + +A feature that introduces a breaking change: + +``` +feat: Support --yes CLI option + +For existing projects all prompts can be inferred automatically. +Manual confirmation for each default provides no value in that case. + +Closes https://github.com/my/project/issues/123 + +BREAKING CHANGE: This removes support for interactive password entry. +Users will have to login beforehand. +``` + +A simple bug fix: + +``` +fix: Handle multi-byte characters in search logic +``` diff --git a/deps/node-inspect/GOVERNANCE.md b/deps/node-inspect/GOVERNANCE.md new file mode 100644 index 0000000000..238c1b8258 --- /dev/null +++ b/deps/node-inspect/GOVERNANCE.md @@ -0,0 +1,5 @@ +# node-inspect Project Governance + +The node-inspect project falls under the governance of the diagnostics +working group which is documented in: +https://github.com/nodejs/diagnostics/blob/master/GOVERNANCE.md. diff --git a/deps/node-inspect/LICENSE b/deps/node-inspect/LICENSE new file mode 100644 index 0000000000..d8d7f9437d --- /dev/null +++ b/deps/node-inspect/LICENSE @@ -0,0 +1,19 @@ +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/deps/node-inspect/README.md b/deps/node-inspect/README.md new file mode 100644 index 0000000000..ecd939b3ea --- /dev/null +++ b/deps/node-inspect/README.md @@ -0,0 +1,29 @@ +# `node-inspect` + +```bash +npm install --global node-inspect +``` + +For the old V8 debugger protocol, +node has two options: + +1. `node --debug <file>`: Start `file` with remote debugging enabled. +2. `node debug <file>`: Start an interactive CLI debugger for `<file>`. + +But for the Chrome inspector protol, +there's only one: `node --inspect <file>`. + +This project tries to provide the missing second option +by re-implementing `node debug` against the new protocol. + +``` +Usage: node-inspect script.js + node-inspect <host>:<port> +``` + +#### References + +* [Debugger Documentation](https://nodejs.org/api/debugger.html) +* [EPS: `node inspect` CLI debugger](https://github.com/nodejs/node-eps/pull/42) +* [Debugger Protocol Viewer](https://chromedevtools.github.io/debugger-protocol-viewer/) +* [Command Line API](https://developers.google.com/web/tools/chrome-devtools/debug/command-line/command-line-reference?hl=en) diff --git a/deps/node-inspect/appveyor.yml b/deps/node-inspect/appveyor.yml new file mode 100644 index 0000000000..c25a2d013a --- /dev/null +++ b/deps/node-inspect/appveyor.yml @@ -0,0 +1,13 @@ +environment: + nodejs_version: "6" + +install: +- ps: Install-Product node $env:nodejs_version +- npm install + +test_script: +- node --version +- npm --version +- npm test + +build: off diff --git a/deps/node-inspect/cli.js b/deps/node-inspect/cli.js new file mode 100755 index 0000000000..4856fd706c --- /dev/null +++ b/deps/node-inspect/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./lib/cli.js'); diff --git a/deps/node-inspect/examples/alive.js b/deps/node-inspect/examples/alive.js new file mode 100644 index 0000000000..3cf557b053 --- /dev/null +++ b/deps/node-inspect/examples/alive.js @@ -0,0 +1,6 @@ +'use strict'; +let x = 0; +function heartbeat() { + ++x; +} +setInterval(heartbeat, 50); diff --git a/deps/node-inspect/examples/backtrace.js b/deps/node-inspect/examples/backtrace.js new file mode 100644 index 0000000000..f5d71a14f9 --- /dev/null +++ b/deps/node-inspect/examples/backtrace.js @@ -0,0 +1,31 @@ +'use strict'; +const { exports: moduleScoped } = module; + +function topFn(a, b = false) { + const l1 = a; + let t = typeof l1; + var v = t.length; + debugger; + return b || t || v || moduleScoped; +} + +class Ctor { + constructor(options) { + this.options = options; + } + + m() { + const mLocal = this.options; + topFn(this); + return mLocal; + } +} + +(function () { + const theOptions = { x: 42 }; + const arr = [theOptions]; + arr.forEach(options => { + const obj = new Ctor(options); + return obj.m(); + }); +}()); diff --git a/deps/node-inspect/examples/break.js b/deps/node-inspect/examples/break.js new file mode 100644 index 0000000000..d5f2657881 --- /dev/null +++ b/deps/node-inspect/examples/break.js @@ -0,0 +1,16 @@ +const x = 10; +let name = 'World'; +name = 'Robin'; +function sayHello() { + if (x > 0) { + console.log(`Hello ${name}`); + } +} +sayHello(); +debugger; +setTimeout(sayHello, 10); + +function otherFunction() { + console.log('x = %d', x); +} +setTimeout(otherFunction, 50); diff --git a/deps/node-inspect/examples/cjs/index.js b/deps/node-inspect/examples/cjs/index.js new file mode 100644 index 0000000000..d5815e8659 --- /dev/null +++ b/deps/node-inspect/examples/cjs/index.js @@ -0,0 +1,5 @@ +'use strict'; +const { add } = require('./other'); + +const sum = add(40, 2); +module.exports = sum; diff --git a/deps/node-inspect/examples/cjs/other.js b/deps/node-inspect/examples/cjs/other.js new file mode 100644 index 0000000000..efadaa18d2 --- /dev/null +++ b/deps/node-inspect/examples/cjs/other.js @@ -0,0 +1,4 @@ +'use strict'; +exports.add = function add(a, b) { + return a + b; +}; diff --git a/deps/node-inspect/examples/empty.js b/deps/node-inspect/examples/empty.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/deps/node-inspect/examples/empty.js diff --git a/deps/node-inspect/examples/exceptions.js b/deps/node-inspect/examples/exceptions.js new file mode 100644 index 0000000000..00c48ed31b --- /dev/null +++ b/deps/node-inspect/examples/exceptions.js @@ -0,0 +1,11 @@ +'use strict'; +let error = null; +try { + throw new Error('Caught'); +} catch (e) { + error = e; +} + +if (error) { + throw new Error('Uncaught'); +} diff --git a/deps/node-inspect/examples/three-lines.js b/deps/node-inspect/examples/three-lines.js new file mode 100644 index 0000000000..4e1cb95d11 --- /dev/null +++ b/deps/node-inspect/examples/three-lines.js @@ -0,0 +1,4 @@ +'use strict'; +let x = 1; +x = x + 1; +module.exports = x; diff --git a/deps/node-inspect/lib/_inspect.js b/deps/node-inspect/lib/_inspect.js new file mode 100644 index 0000000000..5cd1cc2d68 --- /dev/null +++ b/deps/node-inspect/lib/_inspect.js @@ -0,0 +1,283 @@ +/* + * Copyright Node.js contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +'use strict'; +const { spawn } = require('child_process'); +const { EventEmitter } = require('events'); +const util = require('util'); + +const [ InspectClient, createRepl ] = + (typeof __dirname !== 'undefined') ? + // This copy of node-inspect is on-disk, relative paths make sense. + [ + require('./internal/inspect_client'), + require('./internal/inspect_repl') + ] : + // This copy of node-inspect is built into the node executable. + [ + require('node-inspect/lib/internal/inspect_client'), + require('node-inspect/lib/internal/inspect_repl') + ]; + +const debuglog = util.debuglog('inspect'); + +exports.port = 9229; + +function runScript(script, scriptArgs, inspectPort, childPrint) { + return new Promise((resolve) => { + const args = [ + '--inspect', + `--debug-brk=${inspectPort}`, + ].concat([script], scriptArgs); + const child = spawn(process.execPath, args); + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + child.stdout.on('data', childPrint); + child.stderr.on('data', childPrint); + + let output = ''; + function waitForListenHint(text) { + output += text; + if (/chrome-devtools:\/\//.test(output)) { + child.stderr.removeListener('data', waitForListenHint); + resolve(child); + } + } + + child.stderr.on('data', waitForListenHint); + }); +} + +function createAgentProxy(domain, client) { + const agent = new EventEmitter(); + agent.then = (...args) => { + // TODO: potentially fetch the protocol and pretty-print it here. + const descriptor = { + [util.inspect.custom](depth, { stylize }) { + return stylize(`[Agent ${domain}]`, 'special'); + }, + }; + return Promise.resolve(descriptor).then(...args); + }; + + return new Proxy(agent, { + get(target, name) { + if (name in target) return target[name]; + return function callVirtualMethod(params) { + return client.callMethod(`${domain}.${name}`, params); + }; + }, + }); +} + +class NodeInspector { + constructor(options, stdin, stdout) { + this.options = options; + this.stdin = stdin; + this.stdout = stdout; + + this.paused = true; + this.child = null; + + if (options.script) { + this._runScript = runScript.bind(null, + options.script, + options.scriptArgs, + options.port, + this.childPrint.bind(this)); + } else { + this._runScript = () => Promise.resolve(null); + } + + this.client = new InspectClient(options.port, options.host); + + this.domainNames = ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime']; + this.domainNames.forEach((domain) => { + this[domain] = createAgentProxy(domain, this.client); + }); + this.handleDebugEvent = (fullName, params) => { + const [domain, name] = fullName.split('.'); + if (domain in this) { + this[domain].emit(name, params); + } + }; + this.client.on('debugEvent', this.handleDebugEvent); + const startRepl = createRepl(this); + + // Handle all possible exits + process.on('exit', () => this.killChild()); + process.once('SIGTERM', process.exit.bind(process, 0)); + process.once('SIGHUP', process.exit.bind(process, 0)); + + this.run() + .then(() => { + this.repl = startRepl(); + this.repl.on('exit', () => { + process.exit(0); + }); + this.paused = false; + }) + .then(null, (error) => process.nextTick(() => { throw error; })); + } + + suspendReplWhile(fn) { + this.repl.rli.pause(); + this.stdin.pause(); + this.paused = true; + return new Promise((resolve) => { + resolve(fn()); + }).then(() => { + this.paused = false; + this.repl.rli.resume(); + this.repl.displayPrompt(); + this.stdin.resume(); + }).then(null, (error) => process.nextTick(() => { throw error; })); + } + + killChild() { + this.client.reset(); + if (this.child) { + this.child.kill(); + this.child = null; + } + } + + run() { + this.killChild(); + return this._runScript().then((child) => { + this.child = child; + + let connectionAttempts = 0; + const attemptConnect = () => { + ++connectionAttempts; + debuglog('connection attempt #%d', connectionAttempts); + this.stdout.write('.'); + return this.client.connect() + .then(() => { + debuglog('connection established'); + }, (error) => { + debuglog('connect failed', error); + // If it's failed to connect 10 times then print failed message + if (connectionAttempts >= 10) { + this.stdout.write(' failed to connect, please retry\n'); + process.exit(1); + } + + return new Promise((resolve) => setTimeout(resolve, 500)) + .then(attemptConnect); + }); + }; + + const { host, port } = this.options; + this.print(`connecting to ${host}:${port} ..`, true); + return attemptConnect(); + }); + } + + clearLine() { + if (this.stdout.isTTY) { + this.stdout.cursorTo(0); + this.stdout.clearLine(1); + } else { + this.stdout.write('\b'); + } + } + + print(text, oneline = false) { + this.clearLine(); + this.stdout.write(oneline ? text : `${text}\n`); + } + + childPrint(text) { + this.print( + text.toString() + .split(/\r\n|\r|\n/g) + .filter((chunk) => !!chunk) + .map((chunk) => `< ${chunk}`) + .join('\n') + ); + if (!this.paused) { + this.repl.displayPrompt(true); + } + if (/Waiting for the debugger to disconnect\.\.\.\n$/.test(text)) { + this.killChild(); + } + } +} + +function parseArgv([target, ...args]) { + let host = '127.0.0.1'; + let port = exports.port; + let isRemote = false; + let script = target; + let scriptArgs = args; + + const hostMatch = target.match(/^([^:]+):(\d+)$/); + const portMatch = target.match(/^--port=(\d+)$/); + if (hostMatch) { + // Connecting to remote debugger + // `node-inspect localhost:9229` + host = hostMatch[1]; + port = parseInt(hostMatch[2], 10); + isRemote = true; + script = null; + } else if (portMatch) { + // Start debugger on custom port + // `node debug --port=8058 app.js` + port = parseInt(portMatch[1], 10); + script = args[0]; + scriptArgs = args.slice(1); + } + + return { + host, port, + isRemote, script, scriptArgs, + }; +} + +function startInspect(argv = process.argv.slice(2), + stdin = process.stdin, + stdout = process.stdout) { + /* eslint-disable no-console */ + if (argv.length < 1) { + console.error('Usage: node-inspect script.js'); + console.error(' node-inspect <host>:<port>'); + process.exit(1); + } + + const options = parseArgv(argv); + const inspector = new NodeInspector(options, stdin, stdout); + + stdin.resume(); + + function handleUnexpectedError(e) { + console.error('There was an internal error in node-inspect. ' + + 'Please report this bug.'); + console.error(e.message); + console.error(e.stack); + if (inspector.child) inspector.child.kill(); + process.exit(1); + } + + process.on('uncaughtException', handleUnexpectedError); + /* eslint-enable no-console */ +} +exports.start = startInspect; diff --git a/deps/node-inspect/lib/cli.js b/deps/node-inspect/lib/cli.js new file mode 100644 index 0000000000..a4880df2d5 --- /dev/null +++ b/deps/node-inspect/lib/cli.js @@ -0,0 +1,24 @@ +/* + * Copyright Node.js contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +'use strict'; +// ~= NativeModule.require('_debugger').start(); +require('./_inspect').start(); diff --git a/deps/node-inspect/lib/internal/inspect_client.js b/deps/node-inspect/lib/internal/inspect_client.js new file mode 100644 index 0000000000..d5ce46db05 --- /dev/null +++ b/deps/node-inspect/lib/internal/inspect_client.js @@ -0,0 +1,363 @@ +/* + * Copyright Node.js contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +'use strict'; +const Buffer = require('buffer').Buffer; +const crypto = require('crypto'); +const { EventEmitter } = require('events'); +const http = require('http'); +const URL = require('url'); +const util = require('util'); + +const debuglog = util.debuglog('inspect'); + +const kOpCodeText = 0x1; +const kOpCodeClose = 0x8; + +const kFinalBit = 0x80; +const kReserved1Bit = 0x40; +const kReserved2Bit = 0x20; +const kReserved3Bit = 0x10; +const kOpCodeMask = 0xF; +const kMaskBit = 0x80; +const kPayloadLengthMask = 0x7F; + +const kMaxSingleBytePayloadLength = 125; +const kMaxTwoBytePayloadLength = 0xFFFF; +const kTwoBytePayloadLengthField = 126; +const kEightBytePayloadLengthField = 127; +const kMaskingKeyWidthInBytes = 4; + +function isEmpty(obj) { + return Object.keys(obj).length === 0; +} + +function unpackError({ code, message, data }) { + const err = new Error(`${message} - ${data}`); + err.code = code; + Error.captureStackTrace(err, unpackError); + return err; +} + +function encodeFrameHybi17(payload) { + var i; + + const dataLength = payload.length; + + let singleByteLength; + let additionalLength; + if (dataLength > kMaxTwoBytePayloadLength) { + singleByteLength = kEightBytePayloadLengthField; + additionalLength = Buffer.alloc(8); + let remaining = dataLength; + for (i = 0; i < 8; ++i) { + additionalLength[7 - i] = remaining & 0xFF; + remaining >>= 8; + } + } else if (dataLength > kMaxSingleBytePayloadLength) { + singleByteLength = kTwoBytePayloadLengthField; + additionalLength = Buffer.alloc(2); + additionalLength[0] = (dataLength & 0xFF00) >> 8; + additionalLength[1] = dataLength & 0xFF; + } else { + additionalLength = Buffer.alloc(0); + singleByteLength = dataLength; + } + + const header = Buffer.from([ + kFinalBit | kOpCodeText, + kMaskBit | singleByteLength, + ]); + + const mask = Buffer.alloc(4); + const masked = Buffer.alloc(dataLength); + for (i = 0; i < dataLength; ++i) { + masked[i] = payload[i] ^ mask[i % kMaskingKeyWidthInBytes]; + } + + return Buffer.concat([header, additionalLength, mask, masked]); +} + +function decodeFrameHybi17(data) { + const dataAvailable = data.length; + const notComplete = { closed: false, payload: null, rest: data }; + let payloadOffset = 2; + if ((dataAvailable - payloadOffset) < 0) return notComplete; + + const firstByte = data[0]; + const secondByte = data[1]; + + const final = (firstByte & kFinalBit) !== 0; + const reserved1 = (firstByte & kReserved1Bit) !== 0; + const reserved2 = (firstByte & kReserved2Bit) !== 0; + const reserved3 = (firstByte & kReserved3Bit) !== 0; + const opCode = firstByte & kOpCodeMask; + const masked = (secondByte & kMaskBit) !== 0; + const compressed = reserved1; + if (compressed) { + throw new Error('Compressed frames not supported'); + } + if (!final || reserved2 || reserved3) { + throw new Error('Only compression extension is supported'); + } + + if (masked) { + throw new Error('Masked server frame - not supported'); + } + + let closed = false; + switch (opCode) { + case kOpCodeClose: + closed = true; + break; + case kOpCodeText: + break; + default: + throw new Error(`Unsupported op code ${opCode}`); + } + + let payloadLength = secondByte & kPayloadLengthMask; + switch (payloadLength) { + case kTwoBytePayloadLengthField: + payloadOffset += 2; + payloadLength = (data[2] << 8) + data[3]; + break; + + case kEightBytePayloadLengthField: + payloadOffset += 8; + payloadLength = 0; + for (var i = 0; i < 8; ++i) { + payloadLength <<= 8; + payloadLength |= data[2 + i]; + } + break; + + default: + // Nothing. We already have the right size. + } + if ((dataAvailable - payloadOffset - payloadLength) < 0) return notComplete; + + const payloadEnd = payloadOffset + payloadLength; + return { + payload: data.slice(payloadOffset, payloadEnd), + rest: data.slice(payloadEnd), + closed, + }; +} + +class Client extends EventEmitter { + constructor(port, host) { + super(); + this.handleChunk = this._handleChunk.bind(this); + + this._port = port; + this._host = host; + + this.reset(); + } + + _handleChunk(chunk) { + this._unprocessed = Buffer.concat([this._unprocessed, chunk]); + + while (this._unprocessed.length > 2) { + const { + closed, + payload: payloadBuffer, + rest + } = decodeFrameHybi17(this._unprocessed); + this._unprocessed = rest; + + if (closed) { + this.reset(); + return; + } + if (payloadBuffer === null) break; + + const payloadStr = payloadBuffer.toString(); + debuglog('< %s', payloadStr); + const lastChar = payloadStr[payloadStr.length - 1]; + if (payloadStr[0] !== '{' || lastChar !== '}') { + throw new Error(`Payload does not look like JSON: ${payloadStr}`); + } + let payload; + try { + payload = JSON.parse(payloadStr); + } catch (parseError) { + parseError.string = payloadStr; + throw parseError; + } + + const { id, method, params, result, error } = payload; + if (id) { + const handler = this._pending[id]; + if (handler) { + delete this._pending[id]; + handler(error, result); + } + } else if (method) { + this.emit('debugEvent', method, params); + this.emit(method, params); + } else { + throw new Error(`Unsupported response: ${payloadStr}`); + } + } + } + + reset() { + if (this._http) { + this._http.destroy(); + } + this._http = null; + this._lastId = 0; + this._socket = null; + this._pending = {}; + this._unprocessed = Buffer.alloc(0); + } + + callMethod(method, params) { + return new Promise((resolve, reject) => { + if (!this._socket) { + reject(new Error('Use `run` to start the app again.')); + return; + } + const data = { id: ++this._lastId, method, params }; + this._pending[data.id] = (error, result) => { + if (error) reject(unpackError(error)); + else resolve(isEmpty(result) ? undefined : result); + }; + const json = JSON.stringify(data); + debuglog('> %s', json); + this._socket.write(encodeFrameHybi17(Buffer.from(json))); + }); + } + + _fetchJSON(urlPath) { + return new Promise((resolve, reject) => { + const httpReq = http.get({ + host: this._host, + port: this._port, + path: urlPath, + }); + + const chunks = []; + + function onResponse(httpRes) { + function parseChunks() { + const resBody = Buffer.concat(chunks).toString(); + if (httpRes.statusCode !== 200) { + reject(new Error(`Unexpected ${httpRes.statusCode}: ${resBody}`)); + return; + } + try { + resolve(JSON.parse(resBody)); + } catch (parseError) { + reject(new Error(`Response didn't contain JSON: ${resBody}`)); + return; + } + } + + httpRes.on('error', reject); + httpRes.on('data', (chunk) => chunks.push(chunk)); + httpRes.on('end', parseChunks); + } + + httpReq.on('error', reject); + httpReq.on('response', onResponse); + }); + } + + connect() { + return this._discoverWebsocketPath() + .then((urlPath) => this._connectWebsocket(urlPath)); + } + + _discoverWebsocketPath() { + return this._fetchJSON('/json') + .then(([{ webSocketDebuggerUrl }]) => + URL.parse(webSocketDebuggerUrl).path); + } + + _connectWebsocket(urlPath) { + this.reset(); + + const key1 = crypto.randomBytes(16).toString('base64'); + debuglog('request websocket', key1); + + const httpReq = this._http = http.request({ + host: this._host, + port: this._port, + path: urlPath, + headers: { + Connection: 'Upgrade', + Upgrade: 'websocket', + 'Sec-WebSocket-Key': key1, + 'Sec-WebSocket-Version': '13', + }, + }); + httpReq.on('error', (e) => { + this.emit('error', e); + }); + httpReq.on('response', (httpRes) => { + if (httpRes.statusCode >= 400) { + process.stderr.write(`Unexpected HTTP code: ${httpRes.statusCode}\n`); + httpRes.pipe(process.stderr); + } else { + httpRes.pipe(process.stderr); + } + }); + + const handshakeListener = (res, socket) => { + // TODO: we *could* validate res.headers[sec-websocket-accept] + debuglog('websocket upgrade'); + + this._socket = socket; + socket.on('data', this.handleChunk); + socket.on('close', () => { + this.emit('close'); + }); + + Promise.all([ + this.callMethod('Runtime.enable'), + this.callMethod('Debugger.enable'), + this.callMethod('Debugger.setPauseOnExceptions', { state: 'none' }), + this.callMethod('Debugger.setAsyncCallStackDepth', { maxDepth: 0 }), + this.callMethod('Profiler.enable'), + this.callMethod('Profiler.setSamplingInterval', { interval: 100 }), + this.callMethod('Debugger.setBlackboxPatterns', { patterns: [] }), + this.callMethod('Runtime.runIfWaitingForDebugger'), + ]).then(() => { + this.emit('ready'); + }, (error) => { + this.emit('error', error); + }); + }; + + return new Promise((resolve, reject) => { + this.once('error', reject); + this.once('ready', resolve); + + httpReq.on('upgrade', handshakeListener); + httpReq.end(); + }); + } +} + +module.exports = Client; diff --git a/deps/node-inspect/lib/internal/inspect_repl.js b/deps/node-inspect/lib/internal/inspect_repl.js new file mode 100644 index 0000000000..c61bbc9fb8 --- /dev/null +++ b/deps/node-inspect/lib/internal/inspect_repl.js @@ -0,0 +1,1057 @@ +/* + * Copyright Node.js contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +'use strict'; +const FS = require('fs'); +const Path = require('path'); +const Repl = require('repl'); +const util = require('util'); +const vm = require('vm'); + +const debuglog = util.debuglog('inspect'); + +const SHORTCUTS = { + cont: 'c', + next: 'n', + step: 's', + out: 'o', + backtrace: 'bt', + setBreakpoint: 'sb', + clearBreakpoint: 'cb', + run: 'r', +}; + +const HELP = ` +run, restart, r Run the application or reconnect +kill Kill a running application or disconnect + +cont, c Resume execution +next, n Continue to next line in current file +step, s Step into, potentially entering a function +out, o Step out, leaving the current function +backtrace, bt Print the current backtrace +list Print the source around the current line where execution + is currently paused + +setBreakpoint, sb Set a breakpoint +clearBreakpoint, cb Clear a breakpoint +breakpoints List all known breakpoints +breakOnException Pause execution whenever an exception is thrown +breakOnUncaught Pause execution whenever an exception isn't caught +breakOnNone Don't pause on exceptions (this is the default) + +watch(expr) Start watching the given expression +unwatch(expr) Stop watching an expression +watchers Print all watched expressions and their current values + +exec(expr) Evaluate the expression and print the value +repl Enter a debug repl that works like exec + +scripts List application scripts that are currently loaded +scripts(true) List all scripts (including node-internals) +`.trim(); + +const FUNCTION_NAME_PATTERN = /^(?:function\*? )?([^(\s]+)\(/; +function extractFunctionName(description) { + const fnNameMatch = description.match(FUNCTION_NAME_PATTERN); + return fnNameMatch ? `: ${fnNameMatch[1]}` : ''; +} + +const NATIVES = process.binding('natives'); +function isNativeUrl(url) { + return url.replace('.js', '') in NATIVES || url === 'bootstrap_node.js'; +} + +function getRelativePath(filename) { + const dir = Path.join(Path.resolve(), 'x').slice(0, -1); + + // Change path to relative, if possible + if (filename.indexOf(dir) === 0) { + return filename.slice(dir.length); + } + return filename; +} + +function toCallback(promise, callback) { + function forward(...args) { + process.nextTick(() => callback(...args)); + } + promise.then(forward.bind(null, null), forward); +} + +// Adds spaces and prefix to number +// maxN is a maximum number we should have space for +function leftPad(n, prefix, maxN) { + const s = n.toString(); + const nchars = Math.max(2, String(maxN).length) + 1; + const nspaces = nchars - s.length - 1; + + return prefix + ' '.repeat(nspaces) + s; +} + +function markSourceColumn(sourceText, position, useColors) { + if (!sourceText) return ''; + + const head = sourceText.slice(0, position); + let tail = sourceText.slice(position); + + // Colourize char if stdout supports colours + if (useColors) { + tail = tail.replace(/(.+?)([^\w]|$)/, '\u001b[32m$1\u001b[39m$2'); + } + + // Return source line with coloured char at `position` + return [head, tail].join(''); +} + +function extractErrorMessage(stack) { + if (!stack) return '<unknown>'; + const m = stack.match(/^\w+: ([^\n]+)/); + return m ? m[1] : stack; +} + +function convertResultToError(result) { + const { className, description } = result; + const err = new Error(extractErrorMessage(description)); + err.stack = description; + Object.defineProperty(err, 'name', { value: className }); + return err; +} + +class RemoteObject { + constructor(attributes) { + Object.assign(this, attributes); + if (this.type === 'number') { + this.value = + this.unserializableValue ? +this.unserializableValue : +this.value; + } + } + + [util.inspect.custom](depth, opts) { + function formatProperty(prop) { + switch (prop.type) { + case 'string': + case 'undefined': + return util.inspect(prop.value, opts); + + case 'number': + case 'boolean': + return opts.stylize(prop.value, prop.type); + + case 'object': + case 'symbol': + if (prop.subtype === 'date') { + return util.inspect(new Date(prop.value), opts); + } + if (prop.subtype === 'array') { + return opts.stylize(prop.value, 'special'); + } + return opts.stylize(prop.value, prop.subtype || 'special'); + + default: + return prop.value; + } + } + switch (this.type) { + case 'boolean': + case 'number': + case 'string': + case 'undefined': + return util.inspect(this.value, opts); + + case 'symbol': + return opts.stylize(this.description, 'special'); + + case 'function': { + const fnName = extractFunctionName(this.description); + const formatted = `[${this.className}${fnName}]`; + return opts.stylize(formatted, 'special'); + } + + case 'object': + switch (this.subtype) { + case 'date': + return util.inspect(new Date(this.description), opts); + + case 'null': + return util.inspect(null, opts); + + case 'regexp': + return opts.stylize(this.description, 'regexp'); + + default: + break; + } + if (this.preview) { + const props = this.preview.properties + .map((prop, idx) => { + const value = formatProperty(prop); + if (prop.name === `${idx}`) return value; + return `${prop.name}: ${value}`; + }); + if (this.preview.overflow) { + props.push('...'); + } + const singleLine = props.join(', '); + const propString = + singleLine.length > 60 ? props.join(',\n ') : singleLine; + + return this.subtype === 'array' ? + `[ ${propString} ]` : `{ ${propString} }`; + } + return this.description; + + default: + return this.description; + } + } + + static fromEvalResult({ result, wasThrown }) { + if (wasThrown) return convertResultToError(result); + return new RemoteObject(result); + } +} + +class ScopeSnapshot { + constructor(scope, properties) { + Object.assign(this, scope); + this.properties = new Map(properties.map((prop) => { + // console.error(prop); + const value = new RemoteObject(prop.value); + return [prop.name, value]; + })); + } + + [util.inspect.custom](depth, opts) { + const type = `${this.type[0].toUpperCase()}${this.type.slice(1)}`; + const name = this.name ? `<${this.name}>` : ''; + const prefix = `${type}${name} `; + return util.inspect(this.properties, opts) + .replace(/^Map /, prefix); + } +} + +function copyOwnProperties(target, source) { + Object.getOwnPropertyNames(source).forEach((prop) => { + const descriptor = Object.getOwnPropertyDescriptor(source, prop); + Object.defineProperty(target, prop, descriptor); + }); +} + +function aliasProperties(target, mapping) { + Object.keys(mapping).forEach((key) => { + const descriptor = Object.getOwnPropertyDescriptor(target, key); + Object.defineProperty(target, mapping[key], descriptor); + }); +} + +function createRepl(inspector) { + const { Debugger, HeapProfiler, Profiler, Runtime } = inspector; + + let repl; // eslint-disable-line prefer-const + + // Things we want to keep around + const history = { control: [], debug: [] }; + const watchedExpressions = []; + const knownBreakpoints = []; + let pauseOnExceptionState = 'none'; + let lastCommand; + + // Things we need to reset when the app restarts + let knownScripts; + let currentBacktrace; + let selectedFrame; + let exitDebugRepl; + + function resetOnStart() { + knownScripts = {}; + currentBacktrace = null; + selectedFrame = null; + + if (exitDebugRepl) exitDebugRepl(); + exitDebugRepl = null; + } + resetOnStart(); + + const INSPECT_OPTIONS = { colors: inspector.stdout.isTTY }; + function inspect(value) { + return util.inspect(value, INSPECT_OPTIONS); + } + + function print(value, oneline = false) { + const text = typeof value === 'string' ? value : inspect(value); + return inspector.print(text, oneline); + } + + function getCurrentLocation() { + if (!selectedFrame) { + throw new Error('Requires execution to be paused'); + } + return selectedFrame.location; + } + + function isCurrentScript(script) { + return selectedFrame && getCurrentLocation().scriptId === script.scriptId; + } + + function formatScripts(displayNatives = false) { + function isVisible(script) { + if (displayNatives) return true; + return !script.isNative || isCurrentScript(script); + } + + return Object.keys(knownScripts) + .map((scriptId) => knownScripts[scriptId]) + .filter(isVisible) + .map((script) => { + const isCurrent = isCurrentScript(script); + const { isNative, url } = script; + const name = `${getRelativePath(url)}${isNative ? ' <native>' : ''}`; + return `${isCurrent ? '*' : ' '} ${script.scriptId}: ${name}`; + }) + .join('\n'); + } + function listScripts(displayNatives = false) { + print(formatScripts(displayNatives)); + } + listScripts[util.inspect.custom] = function listWithoutInternal() { + return formatScripts(); + }; + + const profiles = []; + class Profile { + constructor(data) { + this.data = data; + } + + static createAndRegister({ profile }) { + const p = new Profile(profile); + profiles.push(p); + return p; + } + + [util.inspect.custom](depth, { stylize }) { + const { startTime, endTime } = this.data; + return stylize(`[Profile ${endTime - startTime}μs]`, 'special'); + } + + save(filename = 'node.cpuprofile') { + const absoluteFile = Path.resolve(filename); + const json = JSON.stringify(this.data); + FS.writeFileSync(absoluteFile, json); + print('Saved profile to ' + absoluteFile); + } + } + + class SourceSnippet { + constructor(location, delta, scriptSource) { + Object.assign(this, location); + this.scriptSource = scriptSource; + this.delta = delta; + } + + [util.inspect.custom](depth, options) { + const { scriptId, lineNumber, columnNumber, delta, scriptSource } = this; + const start = Math.max(1, lineNumber - delta + 1); + const end = lineNumber + delta + 1; + + const lines = scriptSource.split('\n'); + return lines.slice(start - 1, end).map((lineText, offset) => { + const i = start + offset; + const isCurrent = i === (lineNumber + 1); + + const markedLine = isCurrent + ? markSourceColumn(lineText, columnNumber, options.colors) + : lineText; + + let isBreakpoint = false; + knownBreakpoints.forEach(({ location }) => { + if (!location) return; + if (scriptId === location.scriptId && + i === (location.lineNumber + 1)) { + isBreakpoint = true; + } + }); + + let prefixChar = ' '; + if (isCurrent) { + prefixChar = '>'; + } else if (isBreakpoint) { + prefixChar = '*'; + } + return `${leftPad(i, prefixChar, end)} ${markedLine}`; + }).join('\n'); + } + } + + function getSourceSnippet(location, delta = 5) { + const { scriptId } = location; + return Debugger.getScriptSource({ scriptId }) + .then(({ scriptSource }) => + new SourceSnippet(location, delta, scriptSource)); + } + + class CallFrame { + constructor(callFrame) { + Object.assign(this, callFrame); + } + + loadScopes() { + return Promise.all( + this.scopeChain + .filter((scope) => scope.type !== 'global') + .map((scope) => { + const { objectId } = scope.object; + return Runtime.getProperties({ + objectId, + generatePreview: true, + }).then(({ result }) => new ScopeSnapshot(scope, result)); + }) + ); + } + + list(delta = 5) { + return getSourceSnippet(this.location, delta); + } + } + + class Backtrace extends Array { + [util.inspect.custom]() { + return this.map((callFrame, idx) => { + const { + location: { scriptId, lineNumber, columnNumber }, + functionName + } = callFrame; + const name = functionName || '(anonymous)'; + + const script = knownScripts[scriptId]; + const relativeUrl = + (script && getRelativePath(script.url)) || '<unknown>'; + const frameLocation = + `${relativeUrl}:${lineNumber + 1}:${columnNumber}`; + + return `#${idx} ${name} ${frameLocation}`; + }).join('\n'); + } + + static from(callFrames) { + return super.from(Array.from(callFrames).map((callFrame) => { + if (callFrame instanceof CallFrame) { + return callFrame; + } + return new CallFrame(callFrame); + })); + } + } + + function prepareControlCode(input) { + if (input === '\n') return lastCommand; + // exec process.title => exec("process.title"); + const match = input.match(/^\s*exec\s+([^\n]*)/); + if (match) { + lastCommand = `exec(${JSON.stringify(match[1])})`; + } else { + lastCommand = input; + } + return lastCommand; + } + + function evalInCurrentContext(code) { + // Repl asked for scope variables + if (code === '.scope') { + if (!selectedFrame) { + return Promise.reject(new Error('Requires execution to be paused')); + } + return selectedFrame.loadScopes(); + } + + if (selectedFrame) { + return Debugger.evaluateOnCallFrame({ + callFrameId: selectedFrame.callFrameId, + expression: code, + objectGroup: 'node-inspect', + generatePreview: true, + }).then(RemoteObject.fromEvalResult); + } + return Runtime.evaluate({ + expression: code, + objectGroup: 'node-inspect', + generatePreview: true, + }).then(RemoteObject.fromEvalResult); + } + + function controlEval(input, context, filename, callback) { + debuglog('eval:', input); + function returnToCallback(error, result) { + debuglog('end-eval:', input, error); + callback(error, result); + } + + try { + const code = prepareControlCode(input); + const result = vm.runInContext(code, context, filename); + + if (result && typeof result.then === 'function') { + toCallback(result, returnToCallback); + return; + } + returnToCallback(null, result); + } catch (e) { + returnToCallback(e); + } + } + + function debugEval(input, context, filename, callback) { + debuglog('eval:', input); + function returnToCallback(error, result) { + debuglog('end-eval:', input, error); + callback(error, result); + } + + try { + const result = evalInCurrentContext(input); + + if (result && typeof result.then === 'function') { + toCallback(result, returnToCallback); + return; + } + returnToCallback(null, result); + } catch (e) { + returnToCallback(e); + } + } + + function formatWatchers(verbose = false) { + if (!watchedExpressions.length) { + return Promise.resolve(''); + } + + const inspectValue = (expr) => + evalInCurrentContext(expr) + // .then(formatValue) + .catch((error) => `<${error.message}>`); + const lastIndex = watchedExpressions.length - 1; + + return Promise.all(watchedExpressions.map(inspectValue)) + .then((values) => { + const lines = watchedExpressions + .map((expr, idx) => { + const prefix = `${leftPad(idx, ' ', lastIndex)}: ${expr} =`; + const value = inspect(values[idx], { colors: true }); + if (value.indexOf('\n') === -1) { + return `${prefix} ${value}`; + } + return `${prefix}\n ${value.split('\n').join('\n ')}`; + }); + return lines.join('\n'); + }) + .then((valueList) => { + return verbose ? `Watchers:\n${valueList}\n` : valueList; + }); + } + + function watchers(verbose = false) { + return formatWatchers(verbose).then(print); + } + + // List source code + function list(delta = 5) { + return selectedFrame.list(delta) + .then(null, (error) => { + print('You can\'t list source code right now'); + throw error; + }); + } + + function handleBreakpointResolved({ breakpointId, location }) { + const script = knownScripts[location.scriptId]; + const scriptUrl = script && script.url; + if (scriptUrl) { + Object.assign(location, { scriptUrl }); + } + const isExisting = knownBreakpoints.some((bp) => { + if (bp.breakpointId === breakpointId) { + Object.assign(bp, { location }); + return true; + } + return false; + }); + if (!isExisting) { + knownBreakpoints.push({ breakpointId, location }); + } + } + + function listBreakpoints() { + if (!knownBreakpoints.length) { + print('No breakpoints yet'); + return; + } + + function formatLocation(location) { + if (!location) return '<unknown location>'; + const script = knownScripts[location.scriptId]; + const scriptUrl = script ? script.url : location.scriptUrl; + return `${getRelativePath(scriptUrl)}:${location.lineNumber + 1}`; + } + const breaklist = knownBreakpoints + .map((bp, idx) => `#${idx} ${formatLocation(bp.location)}`) + .join('\n'); + print(breaklist); + } + + function setBreakpoint(script, line, condition, silent) { + function registerBreakpoint({ breakpointId, actualLocation }) { + handleBreakpointResolved({ breakpointId, location: actualLocation }); + if (actualLocation && actualLocation.scriptId) { + if (!silent) return getSourceSnippet(actualLocation, 5); + } else { + print(`Warning: script '${script}' was not loaded yet.`); + } + return undefined; + } + + // setBreakpoint(): set breakpoint at current location + if (script === undefined) { + return Debugger + .setBreakpoint({ location: getCurrentLocation(), condition }) + .then(registerBreakpoint); + } + + // setBreakpoint(line): set breakpoint in current script at specific line + if (line === undefined && typeof script === 'number') { + const location = { + scriptId: getCurrentLocation().scriptId, + lineNumber: script - 1, + }; + return Debugger.setBreakpoint({ location, condition }) + .then(registerBreakpoint); + } + + if (typeof script !== 'string') { + throw new TypeError(`setBreakpoint() expects a string, got ${script}`); + } + + // setBreakpoint('fn()'): Break when a function is called + if (script.endsWith('()')) { + const debugExpr = `debug(${script.slice(0, -2)})`; + const debugCall = selectedFrame + ? Debugger.evaluateOnCallFrame({ + callFrameId: selectedFrame.callFrameId, + expression: debugExpr, + includeCommandLineAPI: true, + }) + : Runtime.evaluate({ + expression: debugExpr, + includeCommandLineAPI: true, + }); + return debugCall.then(({ result, wasThrown }) => { + if (wasThrown) return convertResultToError(result); + return undefined; // This breakpoint can't be removed the same way + }); + } + + // setBreakpoint('scriptname') + let scriptId = null; + let ambiguous = false; + if (knownScripts[script]) { + scriptId = script; + } else { + for (const id of Object.keys(knownScripts)) { + const scriptUrl = knownScripts[id].url; + if (scriptUrl && scriptUrl.indexOf(script) !== -1) { + if (scriptId !== null) { + ambiguous = true; + } + scriptId = id; + } + } + } + + if (ambiguous) { + print('Script name is ambiguous'); + return undefined; + } + if (line <= 0) { + print('Line should be a positive value'); + return undefined; + } + + if (scriptId !== null) { + const location = { scriptId, lineNumber: line - 1 }; + return Debugger.setBreakpoint({ location, condition }) + .then(registerBreakpoint); + } + + const escapedPath = script.replace(/([/\\.?*()^${}|[\]])/g, '\\$1'); + const urlRegex = `^(.*[\\/\\\\])?${escapedPath}$`; + + return Debugger + .setBreakpointByUrl({ urlRegex, lineNumber: line - 1, condition }) + .then((bp) => { + // TODO: handle bp.locations in case the regex matches existing files + if (!bp.location) { // Fake it for now. + Object.assign(bp, { + actualLocation: { + scriptUrl: `.*/${script}$`, + lineNumber: line - 1, + }, + }); + } + return registerBreakpoint(bp); + }); + } + + function clearBreakpoint(url, line) { + const breakpoint = knownBreakpoints.find(({ location }) => { + if (!location) return false; + const script = knownScripts[location.scriptId]; + if (!script) return false; + return ( + script.url.indexOf(url) !== -1 && (location.lineNumber + 1) === line + ); + }); + if (!breakpoint) { + print(`Could not find breakpoint at ${url}:${line}`); + return Promise.resolve(); + } + return Debugger.removeBreakpoint({ breakpointId: breakpoint.breakpointId }) + .then(() => { + const idx = knownBreakpoints.indexOf(breakpoint); + knownBreakpoints.splice(idx, 1); + }); + } + + function restoreBreakpoints() { + const lastBreakpoints = knownBreakpoints.slice(); + knownBreakpoints.length = 0; + const newBreakpoints = lastBreakpoints + .filter(({ location }) => !!location.scriptUrl) + .map(({ location }) => + setBreakpoint(location.scriptUrl, location.lineNumber + 1)); + if (!newBreakpoints.length) return; + Promise.all(newBreakpoints).then((results) => { + print(`${results.length} breakpoints restored.`); + }); + } + + function setPauseOnExceptions(state) { + return Debugger.setPauseOnExceptions({ state }) + .then(() => { + pauseOnExceptionState = state; + }); + } + + Debugger.on('paused', ({ callFrames, reason /* , hitBreakpoints */ }) => { + // Save execution context's data + currentBacktrace = Backtrace.from(callFrames); + selectedFrame = currentBacktrace[0]; + const { scriptId, lineNumber } = selectedFrame.location; + + const breakType = reason === 'other' ? 'break' : reason; + const script = knownScripts[scriptId]; + const scriptUrl = script ? getRelativePath(script.url) : '[unknown]'; + print(`${breakType} in ${scriptUrl}:${lineNumber + 1}`); + + inspector.suspendReplWhile(() => + Promise.all([formatWatchers(true), selectedFrame.list(2)]) + .then(([watcherList, context]) => { + if (watcherList) { + return `${watcherList}\n${inspect(context)}`; + } + return context; + }).then(print)); + }); + + function handleResumed() { + currentBacktrace = null; + selectedFrame = null; + } + + Debugger.on('resumed', handleResumed); + + Debugger.on('breakpointResolved', handleBreakpointResolved); + + Debugger.on('scriptParsed', (script) => { + const { scriptId, url } = script; + if (url) { + knownScripts[scriptId] = Object.assign({ + isNative: isNativeUrl(url), + }, script); + } + }); + + Profiler.on('consoleProfileFinished', ({ profile }) => { + Profile.createAndRegister({ profile }); + print([ + 'Captured new CPU profile.', + `Access it with profiles[${profiles.length - 1}]` + ].join('\n')); + }); + + function initializeContext(context) { + inspector.domainNames.forEach((domain) => { + Object.defineProperty(context, domain, { + value: inspector[domain], + enumerable: true, + configurable: true, + writeable: false, + }); + }); + + copyOwnProperties(context, { + get help() { + print(HELP); + }, + + get run() { + return inspector.run(); + }, + + get kill() { + return inspector.killChild(); + }, + + get restart() { + return inspector.run(); + }, + + get cont() { + handleResumed(); + return Debugger.resume(); + }, + + get next() { + handleResumed(); + return Debugger.stepOver(); + }, + + get step() { + handleResumed(); + return Debugger.stepInto(); + }, + + get out() { + handleResumed(); + return Debugger.stepOut(); + }, + + get pause() { + return Debugger.pause(); + }, + + get backtrace() { + return currentBacktrace; + }, + + get breakpoints() { + return listBreakpoints(); + }, + + exec(expr) { + return evalInCurrentContext(expr); + }, + + get profile() { + return Profiler.start(); + }, + + get profileEnd() { + return Profiler.stop() + .then(Profile.createAndRegister); + }, + + get profiles() { + return profiles; + }, + + takeHeapSnapshot(filename = 'node.heapsnapshot') { + return new Promise((resolve, reject) => { + const absoluteFile = Path.resolve(filename); + const writer = FS.createWriteStream(absoluteFile); + let totalSize; + let sizeWritten = 0; + function onProgress({ done, total, finished }) { + totalSize = total; + if (finished) { + print('Heap snaphost prepared.'); + } else { + print(`Heap snapshot: ${done}/${total}`, true); + } + } + function onChunk({ chunk }) { + sizeWritten += chunk.length; + writer.write(chunk); + print(`Writing snapshot: ${sizeWritten}/${totalSize}`, true); + if (sizeWritten >= totalSize) { + writer.end(); + teardown(); + print(`Wrote snapshot: ${absoluteFile}`); + resolve(); + } + } + function teardown() { + HeapProfiler.removeListener( + 'reportHeapSnapshotProgress', onProgress); + HeapProfiler.removeListener('addHeapSnapshotChunk', onChunk); + } + + HeapProfiler.on('reportHeapSnapshotProgress', onProgress); + HeapProfiler.on('addHeapSnapshotChunk', onChunk); + + print('Heap snapshot: 0/0', true); + HeapProfiler.takeHeapSnapshot({ reportProgress: true }) + .then(null, (error) => { + teardown(); + reject(error); + }); + }); + }, + + get watchers() { + return watchers(); + }, + + watch(expr) { + watchedExpressions.push(expr); + }, + + unwatch(expr) { + const index = watchedExpressions.indexOf(expr); + + // Unwatch by expression + // or + // Unwatch by watcher number + watchedExpressions.splice(index !== -1 ? index : +expr, 1); + }, + + get repl() { + // Don't display any default messages + const listeners = repl.rli.listeners('SIGINT').slice(0); + repl.rli.removeAllListeners('SIGINT'); + + const oldContext = repl.context; + + exitDebugRepl = () => { + // Restore all listeners + process.nextTick(() => { + listeners.forEach((listener) => { + repl.rli.on('SIGINT', listener); + }); + }); + + // Exit debug repl + repl.eval = controlEval; + + // Swap history + history.debug = repl.rli.history; + repl.rli.history = history.control; + + repl.context = oldContext; + repl.rli.setPrompt('debug> '); + repl.displayPrompt(); + + repl.rli.removeListener('SIGINT', exitDebugRepl); + repl.removeListener('exit', exitDebugRepl); + + exitDebugRepl = null; + }; + + // Exit debug repl on SIGINT + repl.rli.on('SIGINT', exitDebugRepl); + + // Exit debug repl on repl exit + repl.on('exit', exitDebugRepl); + + // Set new + repl.eval = debugEval; + repl.context = {}; + + // Swap history + history.control = repl.rli.history; + repl.rli.history = history.debug; + + repl.rli.setPrompt('> '); + + print('Press Ctrl + C to leave debug repl'); + repl.displayPrompt(); + }, + + get version() { + return Runtime.evaluate({ + expression: 'process.versions.v8', + contextId: 1, + returnByValue: true, + }).then(({ result }) => { + print(result.value); + }); + }, + + scripts: listScripts, + + setBreakpoint, + clearBreakpoint, + setPauseOnExceptions, + get breakOnException() { + return setPauseOnExceptions('all'); + }, + get breakOnUncaught() { + return setPauseOnExceptions('uncaught'); + }, + get breakOnNone() { + return setPauseOnExceptions('none'); + }, + + list, + }); + aliasProperties(context, SHORTCUTS); + } + + return function startRepl() { + const replOptions = { + prompt: 'debug> ', + input: inspector.stdin, + output: inspector.stdout, + eval: controlEval, + useGlobal: false, + ignoreUndefined: true, + }; + repl = Repl.start(replOptions); // eslint-disable-line prefer-const + initializeContext(repl.context); + repl.on('reset', initializeContext); + + repl.defineCommand('interrupt', () => { + // We want this for testing purposes where sending CTRL-C can be tricky. + repl.rli.emit('SIGINT'); + }); + + inspector.client.on('close', () => { + resetOnStart(); + }); + + inspector.client.on('ready', () => { + restoreBreakpoints(); + Debugger.setPauseOnExceptions({ state: pauseOnExceptionState }); + }); + + return repl; + }; +} +module.exports = createRepl; diff --git a/deps/node-inspect/package.json b/deps/node-inspect/package.json new file mode 100644 index 0000000000..f4b3aec6d9 --- /dev/null +++ b/deps/node-inspect/package.json @@ -0,0 +1,45 @@ +{ + "name": "node-inspect", + "version": "1.10.4", + "description": "Node Inspect", + "license": "MIT", + "main": "lib/_inspect.js", + "bin": "cli.js", + "homepage": "https://github.com/buggerjs/node-inspect", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/buggerjs/node-inspect" + }, + "bugs": { + "url": "https://github.com/buggerjs/node-inspect/issues" + }, + "scripts": { + "pretest": "eslint --rulesdir=tools/eslint-rules lib test", + "test": "tap \"test/**/*.test.js\"", + "posttest": "nlm verify" + }, + "nlm": { + "license": { + "files": [ + "lib" + ] + } + }, + "dependencies": {}, + "devDependencies": { + "eslint": "^3.10.2", + "nlm": "^3.0.0", + "tap": "^7.1.2" + }, + "author": { + "name": "Jan Krems", + "email": "jan.krems@gmail.com" + }, + "files": [ + "*.js", + "lib" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org" + } +} diff --git a/deps/node-inspect/test/cli/backtrace.test.js b/deps/node-inspect/test/cli/backtrace.test.js new file mode 100644 index 0000000000..2e2ba4a342 --- /dev/null +++ b/deps/node-inspect/test/cli/backtrace.test.js @@ -0,0 +1,30 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('display and navigate backtrace', (t) => { + const script = Path.join('examples', 'backtrace.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.stepCommand('c')) + .then(() => cli.command('bt')) + .then(() => { + t.match(cli.output, `#0 topFn ${script}:8:2`); + }) + .then(() => cli.command('backtrace')) + .then(() => { + t.match(cli.output, `#0 topFn ${script}:8:2`); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/break.test.js b/deps/node-inspect/test/cli/break.test.js new file mode 100644 index 0000000000..1c662d63fc --- /dev/null +++ b/deps/node-inspect/test/cli/break.test.js @@ -0,0 +1,195 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('stepping through breakpoints', (t) => { + const script = Path.join('examples', 'break.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match( + cli.output, + `break in ${script}:1`, + 'pauses in the first line of the script'); + t.match( + cli.output, + /> 1 \(function \([^)]+\) \{ const x = 10;/, + 'shows the source and marks the current line'); + }) + .then(() => cli.stepCommand('n')) + .then(() => { + t.match( + cli.output, + `break in ${script}:2`, + 'pauses in next line of the script'); + t.match( + cli.output, + '> 2 let name = \'World\';', + 'marks the 2nd line'); + }) + .then(() => cli.stepCommand('next')) + .then(() => { + t.match( + cli.output, + `break in ${script}:3`, + 'pauses in next line of the script'); + t.match( + cli.output, + '> 3 name = \'Robin\';', + 'marks the 3nd line'); + }) + .then(() => cli.stepCommand('cont')) + .then(() => { + t.match( + cli.output, + `break in ${script}:10`, + 'pauses on the next breakpoint'); + t.match( + cli.output, + '>10 debugger;', + 'marks the debugger line'); + }) + + // Prepare additional breakpoints + .then(() => cli.command('sb("break.js", 6)')) + .then(() => t.notMatch(cli.output, 'Could not resolve breakpoint')) + .then(() => cli.command('sb("otherFunction()")')) + .then(() => cli.command('sb(16)')) + .then(() => t.notMatch(cli.output, 'Could not resolve breakpoint')) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, `#0 ${script}:6`); + t.match(cli.output, `#1 ${script}:16`); + }) + + .then(() => cli.command('list()')) + .then(() => { + t.match(cli.output, '>10 debugger;', 'prints and marks current line'); + t.strictDeepEqual( + cli.parseSourceLines(), + [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + 'prints 5 lines before and after'); + }) + .then(() => cli.command('list(2)')) + .then(() => { + t.match(cli.output, '>10 debugger;', 'prints and marks current line'); + t.strictDeepEqual( + cli.parseSourceLines(), + [8, 9, 10, 11, 12], + 'prints 2 lines before and after'); + }) + + .then(() => cli.stepCommand('s')) + .then(() => cli.stepCommand('')) + .then(() => { + t.match( + cli.output, + 'break in timers.js', + 'entered timers.js'); + }) + .then(() => cli.stepCommand('cont')) + .then(() => { + t.match( + cli.output, + `break in ${script}:16`, + 'found breakpoint we set above w/ line number only'); + }) + .then(() => cli.stepCommand('cont')) + .then(() => { + t.match( + cli.output, + `break in ${script}:6`, + 'found breakpoint we set above w/ line number & script'); + }) + .then(() => cli.stepCommand('')) + .then(() => { + t.match( + cli.output, + `debugCommand in ${script}:14`, + 'found function breakpoint we set above'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); + +test('sb before loading file', (t) => { + const script = Path.join('examples', 'cjs', 'index.js'); + const otherScript = Path.join('examples', 'cjs', 'other.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('sb("other.js", 3)')) + .then(() => { + t.match( + cli.output, + 'not loaded yet', + 'warns that the script was not loaded yet'); + }) + .then(() => cli.stepCommand('cont')) + .then(() => { + t.match( + cli.output, + `break in ${otherScript}:3`, + 'found breakpoint in file that was not loaded yet'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); + +test('clearBreakpoint', (t) => { + const script = Path.join('examples', 'break.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('sb("break.js", 3)')) + .then(() => cli.command('sb("break.js", 9)')) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, `#0 ${script}:3`); + t.match(cli.output, `#1 ${script}:9`); + }) + .then(() => cli.command('clearBreakpoint("break.js", 4)')) + .then(() => { + t.match(cli.output, 'Could not find breakpoint'); + }) + .then(() => cli.command('clearBreakpoint("not-such-script.js", 3)')) + .then(() => { + t.match(cli.output, 'Could not find breakpoint'); + }) + .then(() => cli.command('clearBreakpoint("break.js", 3)')) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, `#0 ${script}:9`); + }) + .then(() => cli.stepCommand('cont')) + .then(() => { + t.match( + cli.output, + `break in ${script}:9`, + 'hits the 2nd breakpoint because the 1st was cleared'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/exceptions.test.js b/deps/node-inspect/test/cli/exceptions.test.js new file mode 100644 index 0000000000..6d3ff68f05 --- /dev/null +++ b/deps/node-inspect/test/cli/exceptions.test.js @@ -0,0 +1,63 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('break on (uncaught) exceptions', (t) => { + const script = Path.join('examples', 'exceptions.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match(cli.output, `break in ${script}:1`); + }) + // making sure it will die by default: + .then(() => cli.command('c')) + .then(() => cli.waitFor(/disconnect/)) + + // Next run: With `breakOnException` it pauses in both places + .then(() => cli.stepCommand('r')) + .then(() => { + t.match(cli.output, `break in ${script}:1`); + }) + .then(() => cli.command('breakOnException')) + .then(() => cli.stepCommand('c')) + .then(() => { + t.match(cli.output, `exception in ${script}:4`); + }) + .then(() => cli.stepCommand('c')) + .then(() => { + t.match(cli.output, `exception in ${script}:10`); + }) + + // Next run: With `breakOnUncaught` it only pauses on the 2nd exception + .then(() => cli.command('breakOnUncaught')) + .then(() => cli.stepCommand('r')) // also, the setting survives the restart + .then(() => { + t.match(cli.output, `break in ${script}:1`); + }) + .then(() => cli.stepCommand('c')) + .then(() => { + t.match(cli.output, `exception in ${script}:10`); + }) + + // Next run: Back to the initial state! It should die again. + .then(() => cli.command('breakOnNone')) + .then(() => cli.stepCommand('r')) + .then(() => { + t.match(cli.output, `break in ${script}:1`); + }) + .then(() => cli.command('c')) + .then(() => cli.waitFor(/disconnect/)) + + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/exec.test.js b/deps/node-inspect/test/cli/exec.test.js new file mode 100644 index 0000000000..5c647134d7 --- /dev/null +++ b/deps/node-inspect/test/cli/exec.test.js @@ -0,0 +1,77 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('examples/alive.js', (t) => { + const cli = startCLI(['examples/alive.js']); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) + .then(() => { + t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/o paren'); + }) + .then(() => cli.command('repl')) + .then(() => { + t.match( + cli.output, + 'Press Ctrl + C to leave debug repl\n> ', + 'shows hint for how to leave repl'); + t.notMatch(cli.output, 'debug>', 'changes the repl style'); + }) + .then(() => cli.command('[typeof heartbeat, typeof process.exit]')) + .then(() => cli.waitFor(/function/)) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match( + cli.output, + '[ \'function\', \'function\' ]', 'can evaluate in the repl'); + t.match(cli.output, /> $/); + }) + .then(() => cli.ctrlC()) + .then(() => cli.waitFor(/debug> $/)) + .then(() => cli.command('exec("[typeof heartbeat, typeof process.exit]")')) + .then(() => { + t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/ paren'); + }) + .then(() => cli.command('cont')) + .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) + .then(() => { + t.match( + cli.output, + '[ \'undefined\', \'function\' ]', + 'non-paused exec can see global but not module-scope values'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); + +test('exec .scope', (t) => { + const cli = startCLI(['examples/backtrace.js']); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.stepCommand('c')) + .then(() => cli.command('exec .scope')) + .then(() => { + t.match( + cli.output, + '\'moduleScoped\'', 'displays closure from module body'); + t.match(cli.output, '\'a\'', 'displays local / function arg'); + t.match(cli.output, '\'l1\'', 'displays local scope'); + t.notMatch(cli.output, '\'encodeURIComponent\'', 'omits global scope'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/help.test.js b/deps/node-inspect/test/cli/help.test.js new file mode 100644 index 0000000000..11a9358834 --- /dev/null +++ b/deps/node-inspect/test/cli/help.test.js @@ -0,0 +1,22 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('examples/empty.js', (t) => { + const cli = startCLI(['examples/empty.js']); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('help')) + .then(() => { + t.match(cli.output, /run, restart, r\s+/m); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/invalid-args.test.js b/deps/node-inspect/test/cli/invalid-args.test.js new file mode 100644 index 0000000000..c831d799a0 --- /dev/null +++ b/deps/node-inspect/test/cli/invalid-args.test.js @@ -0,0 +1,25 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('launch CLI w/o args', (t) => { + const cli = startCLI([]); + return cli.quit() + .then((code) => { + t.equal(code, 1, 'exits with non-zero exit code'); + t.match(cli.output, /^Usage:/, 'Prints usage info'); + }); +}); + +test('launch w/ invalid host:port', (t) => { + const cli = startCLI(['localhost:914']); + return cli.quit() + .then((code) => { + t.match( + cli.output, + 'failed to connect', + 'Tells the user that the connection failed'); + t.equal(code, 1, 'exits with non-zero exit code'); + }); +}); diff --git a/deps/node-inspect/test/cli/launch.test.js b/deps/node-inspect/test/cli/launch.test.js new file mode 100644 index 0000000000..962197e84f --- /dev/null +++ b/deps/node-inspect/test/cli/launch.test.js @@ -0,0 +1,110 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('examples/empty.js', (t) => { + const script = Path.join('examples', 'empty.js'); + const cli = startCLI([script]); + return cli.waitForPrompt() + .then(() => { + t.match(cli.output, 'debug>', 'prints a prompt'); + t.match( + cli.output, + '< Debugger listening on port 9229', + 'forwards child output'); + }) + .then(() => cli.command('["hello", "world"].join(" ")')) + .then(() => { + t.match(cli.output, 'hello world', 'prints the result'); + }) + .then(() => cli.command('')) + .then(() => { + t.match(cli.output, 'hello world', 'repeats the last command on <enter>'); + }) + .then(() => cli.command('version')) + .then(() => { + t.match(cli.output, process.versions.v8, 'version prints the v8 version'); + }) + .then(() => cli.quit()) + .then((code) => { + t.equal(code, 0, 'exits with success'); + }); +}); + +test('run after quit / restart', (t) => { + const script = Path.join('examples', 'three-lines.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.stepCommand('n')) + .then(() => { + t.match( + cli.output, + `break in ${script}:2`, + 'steps to the 2nd line'); + }) + .then(() => cli.command('cont')) + .then(() => cli.waitFor(/disconnect/)) + .then(() => { + t.match( + cli.output, + 'Waiting for the debugger to disconnect', + 'the child was done'); + }) + .then(() => { + // On windows the socket won't close by itself + return cli.command('kill'); + }) + .then(() => cli.command('cont')) + .then(() => cli.waitFor(/start the app/)) + .then(() => { + t.match(cli.output, 'Use `run` to start the app again'); + }) + .then(() => cli.stepCommand('run')) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match( + cli.output, + `break in ${script}:1`, + 'is back at the beginning'); + }) + .then(() => cli.stepCommand('n')) + .then(() => { + t.match( + cli.output, + `break in ${script}:2`, + 'steps to the 2nd line'); + }) + .then(() => cli.stepCommand('restart')) + .then(() => { + t.match( + cli.output, + `break in ${script}:1`, + 'is back at the beginning'); + }) + .then(() => cli.command('kill')) + .then(() => cli.command('cont')) + .then(() => cli.waitFor(/start the app/)) + .then(() => { + t.match(cli.output, 'Use `run` to start the app again'); + }) + .then(() => cli.stepCommand('run')) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match( + cli.output, + `break in ${script}:1`, + 'is back at the beginning'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/low-level.test.js b/deps/node-inspect/test/cli/low-level.test.js new file mode 100644 index 0000000000..b6301b2f23 --- /dev/null +++ b/deps/node-inspect/test/cli/low-level.test.js @@ -0,0 +1,31 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('Debugger agent direct access', (t) => { + const cli = startCLI(['examples/empty.js']); + const scriptPattern = /^\* (\d+): examples(?:\/|\\)empty.js/; + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('scripts')) + .then(() => { + const [, scriptId] = cli.output.match(scriptPattern); + return cli.command( + `Debugger.getScriptSource({ scriptId: '${scriptId}' })` + ); + }) + .then(() => { + t.match( + cli.output, + /scriptSource: '\(function \([^)]+\) \{ \\n}\);'/); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/preserve-breaks.test.js b/deps/node-inspect/test/cli/preserve-breaks.test.js new file mode 100644 index 0000000000..a248b3aa25 --- /dev/null +++ b/deps/node-inspect/test/cli/preserve-breaks.test.js @@ -0,0 +1,56 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('run after quit / restart', (t) => { + const script = Path.join('examples', 'three-lines.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, 'No breakpoints yet'); + }) + .then(() => cli.command('sb(2)')) + .then(() => cli.command('sb(3)')) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, `#0 ${script}:2`); + t.match(cli.output, `#1 ${script}:3`); + }) + .then(() => cli.stepCommand('c')) // hit line 2 + .then(() => cli.stepCommand('c')) // hit line 3 + .then(() => { + t.match(cli.output, `break in ${script}:3`); + }) + .then(() => cli.command('restart')) + .then(() => cli.waitFor([/break in examples/, /breakpoints restored/])) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match(cli.output, `break in ${script}:1`); + }) + .then(() => cli.stepCommand('c')) + .then(() => { + t.match(cli.output, `break in ${script}:2`); + }) + .then(() => cli.stepCommand('c')) + .then(() => { + t.match(cli.output, `break in ${script}:3`); + }) + .then(() => cli.command('breakpoints')) + .then(() => { + t.match(cli.output, `#0 ${script}:2`); + t.match(cli.output, `#1 ${script}:3`); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/profile.test.js b/deps/node-inspect/test/cli/profile.test.js new file mode 100644 index 0000000000..3ef1896200 --- /dev/null +++ b/deps/node-inspect/test/cli/profile.test.js @@ -0,0 +1,32 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +test('profiles', (t) => { + const cli = startCLI(['examples/empty.js']); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('exec console.profile()')) + .then(() => { + t.match(cli.output, 'undefined'); + }) + .then(() => cli.command('exec console.profileEnd()')) + .then(() => delay(250)) + .then(() => { + t.match(cli.output, 'undefined'); + t.match(cli.output, 'Captured new CPU profile.'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/scripts.test.js b/deps/node-inspect/test/cli/scripts.test.js new file mode 100644 index 0000000000..cd26411790 --- /dev/null +++ b/deps/node-inspect/test/cli/scripts.test.js @@ -0,0 +1,43 @@ +'use strict'; +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('list scripts', (t) => { + const script = Path.join('examples', 'empty.js'); + const cli = startCLI([script]); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('scripts')) + .then(() => { + t.match( + cli.output, + /^\* \d+: examples(?:\/|\\)empty\.js/, + 'lists the user script'); + t.notMatch( + cli.output, + /\d+: module\.js <native>/, + 'omits node-internal scripts'); + }) + .then(() => cli.command('scripts(true)')) + .then(() => { + t.match( + cli.output, + /\* \d+: examples(?:\/|\\)empty\.js/, + 'lists the user script'); + t.match( + cli.output, + /\d+: module\.js <native>/, + 'includes node-internal scripts'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/cli/start-cli.js b/deps/node-inspect/test/cli/start-cli.js new file mode 100644 index 0000000000..267aac57f4 --- /dev/null +++ b/deps/node-inspect/test/cli/start-cli.js @@ -0,0 +1,136 @@ +'use strict'; +const spawn = require('child_process').spawn; + +const CLI = + process.env.USE_EMBEDDED_NODE_INSPECT === '1' ? + 'inspect' : + require.resolve('../../cli.js'); + +function startCLI(args) { + const child = spawn(process.execPath, [CLI, ...args]); + let isFirstStdoutChunk = true; + + const outputBuffer = []; + function bufferOutput(chunk) { + if (isFirstStdoutChunk) { + isFirstStdoutChunk = false; + outputBuffer.push(chunk.replace(/^debug>\s*/, '')); + } else { + outputBuffer.push(chunk); + } + } + + function getOutput() { + return outputBuffer.join('').toString() + .replace(/^[^\n]*?[\b]/mg, ''); + } + + child.stdout.setEncoding('utf8'); + child.stdout.on('data', bufferOutput); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', bufferOutput); + + if (process.env.VERBOSE === '1') { + child.stdout.pipe(process.stderr); + child.stderr.pipe(process.stderr); + } + + return { + flushOutput() { + const output = this.output; + outputBuffer.length = 0; + return output; + }, + + waitFor(pattern, timeout = 2000) { + function checkPattern(str) { + if (Array.isArray(pattern)) { + return pattern.every((p) => p.test(str)); + } + return pattern.test(str); + } + + return new Promise((resolve, reject) => { + function checkOutput() { + if (checkPattern(getOutput())) { + tearDown(); // eslint-disable-line no-use-before-define + resolve(); + } + } + + function onChildExit() { + tearDown(); // eslint-disable-line no-use-before-define + reject(new Error( + `Child quit while waiting for ${pattern}; found: ${this.output}`)); + } + + const timer = setTimeout(() => { + tearDown(); // eslint-disable-line no-use-before-define + reject(new Error([ + `Timeout (${timeout}) while waiting for ${pattern}`, + `found: ${this.output}`, + ].join('; '))); + }, timeout); + + function tearDown() { + clearTimeout(timer); + child.stdout.removeListener('data', checkOutput); + child.removeListener('exit', onChildExit); + } + + child.on('exit', onChildExit); + child.stdout.on('data', checkOutput); + checkOutput(); + }); + }, + + waitForPrompt(timeout = 2000) { + return this.waitFor(/>\s+$/, timeout); + }, + + ctrlC() { + return this.command('.interrupt'); + }, + + get output() { + return getOutput(); + }, + + get rawOutput() { + return outputBuffer.join('').toString(); + }, + + parseSourceLines() { + return getOutput().split('\n') + .map((line) => line.match(/(?:\*|>)?\s*(\d+)/)) + .filter((match) => match !== null) + .map((match) => +match[1]); + }, + + command(input) { + this.flushOutput(); + child.stdin.write(input); + child.stdin.write('\n'); + return this.waitForPrompt(); + }, + + stepCommand(input) { + this.flushOutput(); + child.stdin.write(input); + child.stdin.write('\n'); + return this + .waitFor( + /(?:assert|break|debugCommand|exception|other|promiseRejection) in/ + ) + .then(() => this.waitForPrompt()); + }, + + quit() { + return new Promise((resolve) => { + child.stdin.end(); + child.on('exit', resolve); + }); + }, + }; +} +module.exports = startCLI; diff --git a/deps/node-inspect/test/cli/watchers.test.js b/deps/node-inspect/test/cli/watchers.test.js new file mode 100644 index 0000000000..d66f00814c --- /dev/null +++ b/deps/node-inspect/test/cli/watchers.test.js @@ -0,0 +1,42 @@ +'use strict'; +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +test('stepping through breakpoints', (t) => { + const cli = startCLI(['examples/break.js']); + + function onFatal(error) { + cli.quit(); + throw error; + } + + return cli.waitFor(/break/) + .then(() => cli.waitForPrompt()) + .then(() => cli.command('watch("x")')) + .then(() => cli.command('watch("\\"Hello\\"")')) + .then(() => cli.command('watch("42")')) + .then(() => cli.command('watch("NaN")')) + .then(() => cli.command('watch("true")')) + .then(() => cli.command('watch("[1, 2]")')) + .then(() => cli.command('watch("process.env")')) + .then(() => cli.command('watchers')) + .then(() => { + t.match(cli.output, 'x is not defined'); + }) + .then(() => cli.command('unwatch("42")')) + .then(() => cli.stepCommand('n')) + .then(() => { + t.match(cli.output, '0: x = 10'); + t.match(cli.output, '1: "Hello" = \'Hello\''); + t.match(cli.output, '2: NaN = NaN'); + t.match(cli.output, '3: true = true'); + t.match(cli.output, '4: [1, 2] = [ 1, 2 ]'); + t.match( + cli.output, + /5: process\.env =\n\s+\{[\s\S]+,\n\s+\.\.\. \}/, + 'shows "..." for process.env'); + }) + .then(() => cli.quit()) + .then(null, onFatal); +}); diff --git a/deps/node-inspect/test/node-inspect.test.js b/deps/node-inspect/test/node-inspect.test.js new file mode 100644 index 0000000000..12e7313116 --- /dev/null +++ b/deps/node-inspect/test/node-inspect.test.js @@ -0,0 +1,9 @@ +'use strict'; +const tap = require('tap'); + +const nodeInspect = require('../'); + +tap.equal( + 9229, + nodeInspect.port, + 'Uses the --inspect default port'); diff --git a/deps/node-inspect/tools/eslint-rules/align-function-arguments.js b/deps/node-inspect/tools/eslint-rules/align-function-arguments.js new file mode 100644 index 0000000000..015552489a --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/align-function-arguments.js @@ -0,0 +1,76 @@ +/** + * @fileoverview Align arguments in multiline function calls + * @author Rich Trott + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +function checkArgumentAlignment(context, node) { + + function isNodeFirstInLine(node, byEndLocation) { + const firstToken = byEndLocation === true ? context.getLastToken(node, 1) : + context.getTokenBefore(node); + const startLine = byEndLocation === true ? node.loc.end.line : + node.loc.start.line; + const endLine = firstToken ? firstToken.loc.end.line : -1; + + return startLine !== endLine; + } + + if (node.arguments.length === 0) + return; + + var msg = ''; + const first = node.arguments[0]; + var currentLine = first.loc.start.line; + const firstColumn = first.loc.start.column; + + const ignoreTypes = [ + 'ArrowFunctionExpression', + 'FunctionExpression', + 'ObjectExpression', + ]; + + const args = node.arguments; + + // For now, don't bother trying to validate potentially complicating things + // like closures. Different people will have very different ideas and it's + // probably best to implement configuration options. + if (args.some((node) => { return ignoreTypes.indexOf(node.type) !== -1; })) { + return; + } + + if (!isNodeFirstInLine(node)) { + return; + } + + var misaligned; + + args.slice(1).forEach((argument) => { + if (!misaligned) { + if (argument.loc.start.line === currentLine + 1) { + if (argument.loc.start.column !== firstColumn) { + if (isNodeFirstInLine(argument)) { + msg = 'Function argument in column ' + + `${argument.loc.start.column + 1}, ` + + `expected in ${firstColumn + 1}`; + misaligned = argument; + } + } + } + } + currentLine = argument.loc.start.line; + }); + + if (msg) + context.report(misaligned, msg); +} + +module.exports = function(context) { + return { + 'CallExpression': (node) => checkArgumentAlignment(context, node) + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/align-multiline-assignment.js b/deps/node-inspect/tools/eslint-rules/align-multiline-assignment.js new file mode 100644 index 0000000000..80896b5e96 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/align-multiline-assignment.js @@ -0,0 +1,68 @@ +/** + * @fileoverview Align multiline variable assignments + * @author Rich Trott + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +function getBinaryExpressionStarts(binaryExpression, starts) { + function getStartsFromOneSide(side, starts) { + starts.push(side.loc.start); + if (side.type === 'BinaryExpression') { + starts = getBinaryExpressionStarts(side, starts); + } + return starts; + } + + starts = getStartsFromOneSide(binaryExpression.left, starts); + starts = getStartsFromOneSide(binaryExpression.right, starts); + return starts; +} + +function checkExpressionAlignment(expression) { + if (!expression) + return; + + var msg = ''; + + switch (expression.type) { + case 'BinaryExpression': + var starts = getBinaryExpressionStarts(expression, []); + var startLine = starts[0].line; + const startColumn = starts[0].column; + starts.forEach((loc) => { + if (loc.line > startLine) { + startLine = loc.line; + if (loc.column !== startColumn) { + msg = 'Misaligned multiline assignment'; + } + } + }); + break; + } + return msg; +} + +function testAssignment(context, node) { + const msg = checkExpressionAlignment(node.right); + if (msg) + context.report(node, msg); +} + +function testDeclaration(context, node) { + node.declarations.forEach((declaration) => { + const msg = checkExpressionAlignment(declaration.init); + // const start = declaration.init.loc.start; + if (msg) + context.report(node, msg); + }); +} + +module.exports = function(context) { + return { + 'AssignmentExpression': (node) => testAssignment(context, node), + 'VariableDeclaration': (node) => testDeclaration(context, node) + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/assert-fail-single-argument.js b/deps/node-inspect/tools/eslint-rules/assert-fail-single-argument.js new file mode 100644 index 0000000000..4ce7902381 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/assert-fail-single-argument.js @@ -0,0 +1,30 @@ +/** + * @fileoverview Prohibit use of a single argument only in `assert.fail()`. It + * is almost always an error. + * @author Rich Trott + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const msg = 'assert.fail() message should be third argument'; + +function isAssert(node) { + return node.callee.object && node.callee.object.name === 'assert'; +} + +function isFail(node) { + return node.callee.property && node.callee.property.name === 'fail'; +} + +module.exports = function(context) { + return { + 'CallExpression': function(node) { + if (isAssert(node) && isFail(node) && node.arguments.length === 1) { + context.report(node, msg); + } + } + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/buffer-constructor.js b/deps/node-inspect/tools/eslint-rules/buffer-constructor.js new file mode 100644 index 0000000000..938598e8db --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/buffer-constructor.js @@ -0,0 +1,25 @@ +/** + * @fileoverview Require use of new Buffer constructor methods in lib + * @author James M Snell + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +const msg = 'Use of the Buffer() constructor has been deprecated. ' + + 'Please use either Buffer.alloc(), Buffer.allocUnsafe(), ' + + 'or Buffer.from()'; + +function test(context, node) { + if (node.callee.name === 'Buffer') { + context.report(node, msg); + } +} + +module.exports = function(context) { + return { + 'NewExpression': (node) => test(context, node), + 'CallExpression': (node) => test(context, node) + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/new-with-error.js b/deps/node-inspect/tools/eslint-rules/new-with-error.js new file mode 100644 index 0000000000..655f34bf08 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/new-with-error.js @@ -0,0 +1,31 @@ +/** + * @fileoverview Require `throw new Error()` rather than `throw Error()` + * @author Rich Trott + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + + var errorList = context.options.length !== 0 ? context.options : ['Error']; + + return { + 'ThrowStatement': function(node) { + if (node.argument.type === 'CallExpression' && + errorList.indexOf(node.argument.callee.name) !== -1) { + context.report(node, 'Use new keyword when throwing.'); + } + } + }; +}; + +module.exports.schema = { + 'type': 'array', + 'additionalItems': { + 'type': 'string' + }, + 'uniqueItems': true +}; diff --git a/deps/node-inspect/tools/eslint-rules/no-let-in-for-declaration.js b/deps/node-inspect/tools/eslint-rules/no-let-in-for-declaration.js new file mode 100644 index 0000000000..8b1a6783e0 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/no-let-in-for-declaration.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Prohibit the use of `let` as the loop variable + * in the initialization of for, and the left-hand + * iterator in forIn and forOf loops. + * + * @author Jessica Quynh Tran + */ + +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + create(context) { + + const msg = 'Use of `let` as the loop variable in a for-loop is ' + + 'not recommended. Please use `var` instead.'; + + /** + * Report function to test if the for-loop is declared using `let`. + */ + function testForLoop(node) { + if (node.init && node.init.kind === 'let') { + context.report(node.init, msg); + } + } + + /** + * Report function to test if the for-in or for-of loop + * is declared using `let`. + */ + function testForInOfLoop(node) { + if (node.left && node.left.kind === 'let') { + context.report(node.left, msg); + } + } + + return { + 'ForStatement': testForLoop, + 'ForInStatement': testForInOfLoop, + 'ForOfStatement': testForInOfLoop + }; + } +}; diff --git a/deps/node-inspect/tools/eslint-rules/prefer-assert-methods.js b/deps/node-inspect/tools/eslint-rules/prefer-assert-methods.js new file mode 100644 index 0000000000..fa345eb7c3 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/prefer-assert-methods.js @@ -0,0 +1,39 @@ +'use strict'; + +function isAssert(node) { + return node.expression && + node.expression.type === 'CallExpression' && + node.expression.callee && + node.expression.callee.name === 'assert'; +} + +function getFirstArg(expression) { + return expression.arguments && expression.arguments[0]; +} + +function parseError(method, op) { + return `'assert.${method}' should be used instead of '${op}'`; +} + +const preferedAssertMethod = { + '===': 'strictEqual', + '!==': 'notStrictEqual', + '==': 'equal', + '!=': 'notEqual' +}; + +module.exports = function(context) { + return { + ExpressionStatement(node) { + if (isAssert(node)) { + const arg = getFirstArg(node.expression); + if (arg && arg.type === 'BinaryExpression') { + const assertMethod = preferedAssertMethod[arg.operator]; + if (assertMethod) { + context.report(node, parseError(assertMethod, arg.operator)); + } + } + } + } + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/require-buffer.js b/deps/node-inspect/tools/eslint-rules/require-buffer.js new file mode 100644 index 0000000000..c9818cb758 --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/require-buffer.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = function(context) { + function flagIt(reference) { + const msg = 'Use const Buffer = require(\'buffer\').Buffer; ' + + 'at the beginning of this file'; + context.report(reference.identifier, msg); + } + + return { + 'Program:exit': function() { + const globalScope = context.getScope(); + const variable = globalScope.set.get('Buffer'); + if (variable) { + variable.references.forEach(flagIt); + } + } + }; +}; diff --git a/deps/node-inspect/tools/eslint-rules/required-modules.js b/deps/node-inspect/tools/eslint-rules/required-modules.js new file mode 100644 index 0000000000..3e4a8e8aad --- /dev/null +++ b/deps/node-inspect/tools/eslint-rules/required-modules.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Require usage of specified node modules. + * @author Rich Trott + */ +'use strict'; + +var path = require('path'); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + // trim required module names + var requiredModules = context.options; + + var foundModules = []; + + // if no modules are required we don't need to check the CallExpressions + if (requiredModules.length === 0) { + return {}; + } + + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isString(node) { + return node && node.type === 'Literal' && typeof node.value === 'string'; + } + + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return node.callee.type === 'Identifier' && node.callee.name === 'require'; + } + + /** + * Function to check if a node has an argument that is a required module and + * return its name. + * @param {ASTNode} node The node to check + * @returns {undefined|String} required module name or undefined + */ + function getRequiredModuleName(node) { + var moduleName; + + // node has arguments and first argument is string + if (node.arguments.length && isString(node.arguments[0])) { + var argValue = path.basename(node.arguments[0].value.trim()); + + // check if value is in required modules array + if (requiredModules.indexOf(argValue) !== -1) { + moduleName = argValue; + } + } + + return moduleName; + } + + return { + 'CallExpression': function(node) { + if (isRequireCall(node)) { + var requiredModuleName = getRequiredModuleName(node); + + if (requiredModuleName) { + foundModules.push(requiredModuleName); + } + } + }, + 'Program:exit': function(node) { + if (foundModules.length < requiredModules.length) { + var missingModules = requiredModules.filter( + function(module) { + return foundModules.indexOf(module === -1); + } + ); + missingModules.forEach(function(moduleName) { + context.report( + node, + 'Mandatory module "{{moduleName}}" must be loaded.', + { moduleName: moduleName } + ); + }); + } + } + }; +}; + +module.exports.schema = { + 'type': 'array', + 'additionalItems': { + 'type': 'string' + }, + 'uniqueItems': true +}; |