├── examples ├── empty.js ├── three-lines.js ├── use-strict.js ├── cjs │ ├── other.js │ └── index.js ├── alive.js ├── exceptions.js ├── break.js └── backtrace.js ├── cli.js ├── .gitignore ├── .npmrc ├── GOVERNANCE.md ├── .editorconfig ├── appveyor.yml ├── .travis.yml ├── test └── cli │ ├── help.test.js │ ├── use-strict.test.js │ ├── backtrace.test.js │ ├── heap-profiler.test.js │ ├── profile.test.js │ ├── low-level.test.js │ ├── scripts.test.js │ ├── watchers.test.js │ ├── pid.test.js │ ├── invalid-args.test.js │ ├── preserve-breaks.test.js │ ├── exceptions.test.js │ ├── exec.test.js │ ├── start-cli.js │ ├── launch.test.js │ └── break.test.js ├── tools └── eslint-rules │ ├── require-buffer.js │ ├── buffer-constructor.js │ ├── new-with-error.js │ ├── assert-fail-single-argument.js │ ├── prefer-assert-methods.js │ ├── no-let-in-for-declaration.js │ ├── align-multiline-assignment.js │ ├── align-function-arguments.js │ └── required-modules.js ├── README.md ├── package.json ├── LICENSE ├── lib ├── cli.js ├── internal │ ├── inspect_client.js │ └── inspect_repl.js └── _inspect.js ├── .eslintrc ├── CONTRIBUTING.md └── CHANGELOG.md /examples/empty.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./lib/cli.js'); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | /tmp 4 | /.vs 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /examples/three-lines.js: -------------------------------------------------------------------------------- 1 | let x = 1; 2 | x = x + 1; 3 | module.exports = x; 4 | -------------------------------------------------------------------------------- /examples/use-strict.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('first real line'); 3 | -------------------------------------------------------------------------------- /examples/cjs/other.js: -------------------------------------------------------------------------------- 1 | exports.add = function add(a, b) { 2 | return a + b; 3 | }; 4 | -------------------------------------------------------------------------------- /examples/alive.js: -------------------------------------------------------------------------------- 1 | let x = 0; 2 | function heartbeat() { 3 | ++x; 4 | } 5 | setInterval(heartbeat, 50); 6 | -------------------------------------------------------------------------------- /examples/cjs/index.js: -------------------------------------------------------------------------------- 1 | const fourty = 40; 2 | const { add } = require('./other'); 3 | 4 | const sum = add(fourty, 2); 5 | module.exports = sum; 6 | -------------------------------------------------------------------------------- /examples/exceptions.js: -------------------------------------------------------------------------------- 1 | let error = null; 2 | try { 3 | throw new Error('Caught'); 4 | } catch (e) { 5 | error = e; 6 | } 7 | 8 | if (error) { 9 | throw new Error('Uncaught'); 10 | } 11 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # node-inspect Project Governance 2 | 3 | The node-inspect project is governed by the Node.js Diagnostics WG as described 4 | at . 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "6" 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - npm install 7 | 8 | test_script: 9 | - node --version 10 | - npm --version 11 | - npm test 12 | 13 | build: off 14 | -------------------------------------------------------------------------------- /examples/break.js: -------------------------------------------------------------------------------- 1 | const x = 10; 2 | let name = 'World'; 3 | name = 'Robin'; 4 | function sayHello() { 5 | if (x > 0) { 6 | console.log(`Hello ${name}`); 7 | } 8 | } 9 | sayHello(); 10 | debugger; 11 | setTimeout(sayHello, 10); 12 | 13 | function otherFunction() { 14 | console.log('x = %d', x); 15 | } 16 | setTimeout(otherFunction, 50); 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.8' 4 | before_deploy: 5 | - git config --global user.email "jan.krems@gmail.com" 6 | - git config --global user.name "Jan Krems" 7 | deploy: 8 | provider: script 9 | script: ./node_modules/.bin/nlm release 10 | skip_cleanup: true 11 | 'on': 12 | branch: master 13 | node: '6.8' 14 | -------------------------------------------------------------------------------- /test/cli/help.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('examples/empty.js', (t) => { 7 | const cli = startCLI(['examples/empty.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('help')) 17 | .then(() => { 18 | t.match(cli.output, /run, restart, r\s+/m); 19 | }) 20 | .then(() => cli.quit()) 21 | .then(null, onFatal); 22 | }); 23 | -------------------------------------------------------------------------------- /tools/eslint-rules/require-buffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(context) { 4 | function flagIt(reference) { 5 | const msg = 'Use const Buffer = require(\'buffer\').Buffer; ' + 6 | 'at the beginning of this file'; 7 | context.report(reference.identifier, msg); 8 | } 9 | 10 | return { 11 | 'Program:exit': function() { 12 | const globalScope = context.getScope(); 13 | const variable = globalScope.set.get('Buffer'); 14 | if (variable) { 15 | variable.references.forEach(flagIt); 16 | } 17 | } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /examples/backtrace.js: -------------------------------------------------------------------------------- 1 | const { exports: moduleScoped } = module; 2 | 3 | function topFn(a, b = false) { 4 | const l1 = a; 5 | let t = typeof l1; 6 | var v = t.length; 7 | debugger; 8 | return b || t || v || moduleScoped; 9 | } 10 | 11 | class Ctor { 12 | constructor(options) { 13 | this.options = options; 14 | } 15 | 16 | m() { 17 | const mLocal = this.options; 18 | topFn(this); 19 | return mLocal; 20 | } 21 | } 22 | 23 | (function () { 24 | const theOptions = { x: 42 }; 25 | const arr = [theOptions]; 26 | arr.forEach(options => { 27 | const obj = new Ctor(options); 28 | return obj.m(); 29 | }); 30 | }()); 31 | -------------------------------------------------------------------------------- /test/cli/use-strict.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('for whiles that starts with strict directive', (t) => { 9 | const script = Path.join('examples', 'use-strict.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => { 20 | t.match( 21 | cli.output, 22 | /break in [^:]+:(?:1|2)[^\d]/, 23 | 'pauses either on strict directive or first "real" line'); 24 | }) 25 | .then(() => cli.quit()) 26 | .then(null, onFatal); 27 | }); 28 | -------------------------------------------------------------------------------- /test/cli/backtrace.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('display and navigate backtrace', (t) => { 9 | const script = Path.join('examples', 'backtrace.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.stepCommand('c')) 20 | .then(() => cli.command('bt')) 21 | .then(() => { 22 | t.match(cli.output, `#0 topFn ${script}:7:2`); 23 | }) 24 | .then(() => cli.command('backtrace')) 25 | .then(() => { 26 | t.match(cli.output, `#0 topFn ${script}:7:2`); 27 | }) 28 | .then(() => cli.quit()) 29 | .then(null, onFatal); 30 | }); 31 | -------------------------------------------------------------------------------- /tools/eslint-rules/buffer-constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require use of new Buffer constructor methods in lib 3 | * @author James M Snell 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | const msg = 'Use of the Buffer() constructor has been deprecated. ' + 11 | 'Please use either Buffer.alloc(), Buffer.allocUnsafe(), ' + 12 | 'or Buffer.from()'; 13 | 14 | function test(context, node) { 15 | if (node.callee.name === 'Buffer') { 16 | context.report(node, msg); 17 | } 18 | } 19 | 20 | module.exports = function(context) { 21 | return { 22 | 'NewExpression': (node) => test(context, node), 23 | 'CallExpression': (node) => test(context, node) 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /test/cli/heap-profiler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | const { readFileSync, unlinkSync } = require('fs'); 4 | 5 | const startCLI = require('./start-cli'); 6 | const filename = 'node.heapsnapshot'; 7 | 8 | function cleanup() { 9 | try { 10 | unlinkSync(filename); 11 | } catch (_) { 12 | // Ignore. 13 | } 14 | } 15 | 16 | cleanup(); 17 | 18 | test('Heap profiler take snapshot', (t) => { 19 | const cli = startCLI(['examples/empty.js']); 20 | 21 | function onFatal(error) { 22 | cli.quit(); 23 | throw error; 24 | } 25 | 26 | // Check that the snapshot is valid JSON. 27 | return cli.waitForInitialBreak() 28 | .then(() => cli.waitForPrompt()) 29 | .then(() => cli.command('takeHeapSnapshot()')) 30 | .then(() => JSON.parse(readFileSync(filename, 'utf8'))) 31 | .then(() => cleanup()) 32 | .then(() => cli.quit()) 33 | .then(null, onFatal); 34 | }); 35 | -------------------------------------------------------------------------------- /test/cli/profile.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | function delay(ms) { 7 | return new Promise((resolve) => setTimeout(resolve, ms)); 8 | } 9 | 10 | test('profiles', (t) => { 11 | const cli = startCLI(['examples/empty.js']); 12 | 13 | function onFatal(error) { 14 | cli.quit(); 15 | throw error; 16 | } 17 | 18 | return cli.waitForInitialBreak() 19 | .then(() => cli.waitForPrompt()) 20 | .then(() => cli.command('exec console.profile()')) 21 | .then(() => { 22 | t.match(cli.output, 'undefined'); 23 | }) 24 | .then(() => cli.command('exec console.profileEnd()')) 25 | .then(() => delay(250)) 26 | .then(() => { 27 | t.match(cli.output, 'undefined'); 28 | t.match(cli.output, 'Captured new CPU profile.'); 29 | }) 30 | .then(() => cli.quit()) 31 | .then(null, onFatal); 32 | }); 33 | -------------------------------------------------------------------------------- /tools/eslint-rules/new-with-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require `throw new Error()` rather than `throw Error()` 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var errorList = context.options.length !== 0 ? context.options : ['Error']; 14 | 15 | return { 16 | 'ThrowStatement': function(node) { 17 | if (node.argument.type === 'CallExpression' && 18 | errorList.indexOf(node.argument.callee.name) !== -1) { 19 | context.report(node, 'Use new keyword when throwing.'); 20 | } 21 | } 22 | }; 23 | }; 24 | 25 | module.exports.schema = { 26 | 'type': 'array', 27 | 'additionalItems': { 28 | 'type': 'string' 29 | }, 30 | 'uniqueItems': true 31 | }; 32 | -------------------------------------------------------------------------------- /tools/eslint-rules/assert-fail-single-argument.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prohibit use of a single argument only in `assert.fail()`. It 3 | * is almost always an error. 4 | * @author Rich Trott 5 | */ 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | const msg = 'assert.fail() message should be third argument'; 13 | 14 | function isAssert(node) { 15 | return node.callee.object && node.callee.object.name === 'assert'; 16 | } 17 | 18 | function isFail(node) { 19 | return node.callee.property && node.callee.property.name === 'fail'; 20 | } 21 | 22 | module.exports = function(context) { 23 | return { 24 | 'CallExpression': function(node) { 25 | if (isAssert(node) && isFail(node) && node.arguments.length === 1) { 26 | context.report(node, msg); 27 | } 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /test/cli/low-level.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('Debugger agent direct access', (t) => { 7 | const cli = startCLI(['examples/three-lines.js']); 8 | const scriptPattern = /^\* (\d+): examples(?:\/|\\)three-lines.js/; 9 | 10 | function onFatal(error) { 11 | cli.quit(); 12 | throw error; 13 | } 14 | 15 | return cli.waitForInitialBreak() 16 | .then(() => cli.waitForPrompt()) 17 | .then(() => cli.command('scripts')) 18 | .then(() => { 19 | const [, scriptId] = cli.output.match(scriptPattern); 20 | return cli.command( 21 | `Debugger.getScriptSource({ scriptId: '${scriptId}' })` 22 | ); 23 | }) 24 | .then(() => { 25 | t.match( 26 | cli.output, 27 | /scriptSource: '\(function \(/); 28 | t.match( 29 | cli.output, 30 | /let x = 1;/); 31 | }) 32 | .then(() => cli.quit()) 33 | .then(null, onFatal); 34 | }); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `node-inspect` 2 | 3 | ```bash 4 | npm install --global node-inspect 5 | ``` 6 | 7 | For the old V8 debugger protocol, 8 | node has two options: 9 | 10 | 1. `node --debug `: Start `file` with remote debugging enabled. 11 | 2. `node debug `: Start an interactive CLI debugger for ``. 12 | 13 | But for the Chrome inspector protocol, 14 | there's only one: `node --inspect `. 15 | 16 | This project tries to provide the missing second option 17 | by re-implementing `node debug` against the new protocol. 18 | 19 | ``` 20 | Usage: node-inspect script.js 21 | node-inspect : 22 | ``` 23 | 24 | #### References 25 | 26 | * [Debugger Documentation](https://nodejs.org/api/debugger.html) 27 | * [EPS: `node inspect` CLI debugger](https://github.com/nodejs/node-eps/pull/42) 28 | * [Debugger Protocol Viewer](https://chromedevtools.github.io/debugger-protocol-viewer/) 29 | * [Command Line API](https://developers.google.com/web/tools/chrome-devtools/debug/command-line/command-line-reference?hl=en) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-inspect", 3 | "version": "1.11.5", 4 | "description": "Node Inspect", 5 | "license": "MIT", 6 | "main": "lib/_inspect.js", 7 | "bin": "cli.js", 8 | "homepage": "https://github.com/nodejs/node-inspect", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/nodejs/node-inspect" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/nodejs/node-inspect/issues" 15 | }, 16 | "scripts": { 17 | "pretest": "eslint --rulesdir=tools/eslint-rules lib test", 18 | "test": "tap test", 19 | "posttest": "nlm verify" 20 | }, 21 | "nlm": { 22 | "license": { 23 | "files": [ 24 | "lib" 25 | ] 26 | } 27 | }, 28 | "dependencies": {}, 29 | "devDependencies": { 30 | "eslint": "^3.10.2", 31 | "nlm": "^3.0.0", 32 | "tap": "^10.7.0" 33 | }, 34 | "author": { 35 | "name": "Jan Krems", 36 | "email": "jan.krems@gmail.com" 37 | }, 38 | "files": [ 39 | "*.js", 40 | "lib" 41 | ], 42 | "publishConfig": { 43 | "registry": "https://registry.npmjs.org" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Node.js contributors. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /tools/eslint-rules/prefer-assert-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isAssert(node) { 4 | return node.expression && 5 | node.expression.type === 'CallExpression' && 6 | node.expression.callee && 7 | node.expression.callee.name === 'assert'; 8 | } 9 | 10 | function getFirstArg(expression) { 11 | return expression.arguments && expression.arguments[0]; 12 | } 13 | 14 | function parseError(method, op) { 15 | return `'assert.${method}' should be used instead of '${op}'`; 16 | } 17 | 18 | const preferedAssertMethod = { 19 | '===': 'strictEqual', 20 | '!==': 'notStrictEqual', 21 | '==': 'equal', 22 | '!=': 'notEqual' 23 | }; 24 | 25 | module.exports = function(context) { 26 | return { 27 | ExpressionStatement(node) { 28 | if (isAssert(node)) { 29 | const arg = getFirstArg(node.expression); 30 | if (arg && arg.type === 'BinaryExpression') { 31 | const assertMethod = preferedAssertMethod[arg.operator]; 32 | if (assertMethod) { 33 | context.report(node, parseError(assertMethod, arg.operator)); 34 | } 35 | } 36 | } 37 | } 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /test/cli/scripts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('list scripts', (t) => { 9 | const script = Path.join('examples', 'three-lines.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.command('scripts')) 20 | .then(() => { 21 | t.match( 22 | cli.output, 23 | /^\* \d+: examples(?:\/|\\)three-lines\.js/, 24 | 'lists the user script'); 25 | t.notMatch( 26 | cli.output, 27 | /\d+: module\.js /, 28 | 'omits node-internal scripts'); 29 | }) 30 | .then(() => cli.command('scripts(true)')) 31 | .then(() => { 32 | t.match( 33 | cli.output, 34 | /\* \d+: examples(?:\/|\\)three-lines\.js/, 35 | 'lists the user script'); 36 | t.match( 37 | cli.output, 38 | /\d+: module\.js /, 39 | 'includes node-internal scripts'); 40 | }) 41 | .then(() => cli.quit()) 42 | .then(null, onFatal); 43 | }); 44 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Node.js contributors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 'use strict'; 23 | // ~= NativeModule.require('_debugger').start(); 24 | require('./_inspect').start(); 25 | -------------------------------------------------------------------------------- /tools/eslint-rules/no-let-in-for-declaration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prohibit the use of `let` as the loop variable 3 | * in the initialization of for, and the left-hand 4 | * iterator in forIn and forOf loops. 5 | * 6 | * @author Jessica Quynh Tran 7 | */ 8 | 9 | 'use strict'; 10 | 11 | //------------------------------------------------------------------------------ 12 | // Rule Definition 13 | //------------------------------------------------------------------------------ 14 | 15 | module.exports = { 16 | create(context) { 17 | 18 | const msg = 'Use of `let` as the loop variable in a for-loop is ' + 19 | 'not recommended. Please use `var` instead.'; 20 | 21 | /** 22 | * Report function to test if the for-loop is declared using `let`. 23 | */ 24 | function testForLoop(node) { 25 | if (node.init && node.init.kind === 'let') { 26 | context.report(node.init, msg); 27 | } 28 | } 29 | 30 | /** 31 | * Report function to test if the for-in or for-of loop 32 | * is declared using `let`. 33 | */ 34 | function testForInOfLoop(node) { 35 | if (node.left && node.left.kind === 'let') { 36 | context.report(node.left, msg); 37 | } 38 | } 39 | 40 | return { 41 | 'ForStatement': testForLoop, 42 | 'ForInStatement': testForInOfLoop, 43 | 'ForOfStatement': testForInOfLoop 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /test/cli/watchers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('stepping through breakpoints', (t) => { 7 | const cli = startCLI(['examples/break.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('watch("x")')) 17 | .then(() => cli.command('watch("\\"Hello\\"")')) 18 | .then(() => cli.command('watch("42")')) 19 | .then(() => cli.command('watch("NaN")')) 20 | .then(() => cli.command('watch("true")')) 21 | .then(() => cli.command('watch("[1, 2]")')) 22 | .then(() => cli.command('watch("process.env")')) 23 | .then(() => cli.command('watchers')) 24 | .then(() => { 25 | t.match(cli.output, 'x is not defined'); 26 | }) 27 | .then(() => cli.command('unwatch("42")')) 28 | .then(() => cli.stepCommand('n')) 29 | .then(() => { 30 | t.match(cli.output, '0: x = 10'); 31 | t.match(cli.output, '1: "Hello" = \'Hello\''); 32 | t.match(cli.output, '2: NaN = NaN'); 33 | t.match(cli.output, '3: true = true'); 34 | t.match(cli.output, '4: [1, 2] = [ 1, 2 ]'); 35 | t.match( 36 | cli.output, 37 | /5: process\.env =\n\s+\{[\s\S]+,\n\s+\.\.\. \}/, 38 | 'shows "..." for process.env'); 39 | }) 40 | .then(() => cli.quit()) 41 | .then(null, onFatal); 42 | }); 43 | -------------------------------------------------------------------------------- /test/cli/pid.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { spawn } = require('child_process'); 3 | const Path = require('path'); 4 | 5 | const { test } = require('tap'); 6 | 7 | const startCLI = require('./start-cli'); 8 | 9 | function launchTarget(...args) { 10 | const childProc = spawn(process.execPath, args); 11 | return Promise.resolve(childProc); 12 | } 13 | 14 | // process.debugPort is our proxy for "the version of node used to run this 15 | // test suite doesn't support SIGUSR1 for enabling --inspect for a process". 16 | const defaultsToOldProtocol = process.debugPort === 5858; 17 | 18 | test('examples/alive.js', { skip: defaultsToOldProtocol }, (t) => { 19 | const script = Path.join('examples', 'alive.js'); 20 | let cli = null; 21 | let target = null; 22 | 23 | function cleanup(error) { 24 | if (cli) { 25 | cli.quit(); 26 | cli = null; 27 | } 28 | if (target) { 29 | target.kill(); 30 | target = null; 31 | } 32 | if (error) throw error; 33 | } 34 | 35 | return launchTarget(script) 36 | .then((childProc) => { 37 | target = childProc; 38 | cli = startCLI(['-p', `${target.pid}`]); 39 | return cli.waitForPrompt(); 40 | }) 41 | .then(() => cli.command('sb("alive.js", 3)')) 42 | .then(() => cli.waitFor(/break/)) 43 | .then(() => cli.waitForPrompt()) 44 | .then(() => { 45 | t.match( 46 | cli.output, 47 | '> 3 ++x;', 48 | 'marks the 3rd line'); 49 | }) 50 | .then(() => cleanup()) 51 | .then(null, cleanup); 52 | }); 53 | -------------------------------------------------------------------------------- /test/cli/invalid-args.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | const { createServer } = require('net'); 4 | 5 | const { test } = require('tap'); 6 | 7 | const startCLI = require('./start-cli'); 8 | 9 | test('launch CLI w/o args', (t) => { 10 | const cli = startCLI([]); 11 | return cli.quit() 12 | .then((code) => { 13 | t.equal(code, 1, 'exits with non-zero exit code'); 14 | t.match(cli.output, /^Usage:/, 'Prints usage info'); 15 | }); 16 | }); 17 | 18 | test('launch w/ invalid host:port', (t) => { 19 | const cli = startCLI(['localhost:914']); 20 | return cli.quit() 21 | .then((code) => { 22 | t.match( 23 | cli.output, 24 | 'failed to connect', 25 | 'Tells the user that the connection failed'); 26 | t.equal(code, 1, 'exits with non-zero exit code'); 27 | }); 28 | }); 29 | 30 | test('launch w/ unavailable port', async (t) => { 31 | const blocker = createServer((socket) => socket.end()); 32 | const port = await new Promise((resolve, reject) => { 33 | blocker.on('error', reject); 34 | blocker.listen(0, '127.0.0.1', () => resolve(blocker.address().port)); 35 | }); 36 | 37 | try { 38 | const script = Path.join('examples', 'three-lines.js'); 39 | const cli = startCLI([`--port=${port}`, script]); 40 | const code = await cli.quit(); 41 | 42 | t.notMatch( 43 | cli.output, 44 | 'report this bug', 45 | 'Omits message about reporting this as a bug'); 46 | t.match( 47 | cli.output, 48 | `waiting for 127.0.0.1:${port} to be free`, 49 | 'Tells the user that the port wasn\'t available'); 50 | t.equal(code, 1, 'exits with non-zero exit code'); 51 | } finally { 52 | blocker.close(); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /tools/eslint-rules/align-multiline-assignment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Align multiline variable assignments 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | function getBinaryExpressionStarts(binaryExpression, starts) { 11 | function getStartsFromOneSide(side, starts) { 12 | starts.push(side.loc.start); 13 | if (side.type === 'BinaryExpression') { 14 | starts = getBinaryExpressionStarts(side, starts); 15 | } 16 | return starts; 17 | } 18 | 19 | starts = getStartsFromOneSide(binaryExpression.left, starts); 20 | starts = getStartsFromOneSide(binaryExpression.right, starts); 21 | return starts; 22 | } 23 | 24 | function checkExpressionAlignment(expression) { 25 | if (!expression) 26 | return; 27 | 28 | var msg = ''; 29 | 30 | switch (expression.type) { 31 | case 'BinaryExpression': 32 | var starts = getBinaryExpressionStarts(expression, []); 33 | var startLine = starts[0].line; 34 | const startColumn = starts[0].column; 35 | starts.forEach((loc) => { 36 | if (loc.line > startLine) { 37 | startLine = loc.line; 38 | if (loc.column !== startColumn) { 39 | msg = 'Misaligned multiline assignment'; 40 | } 41 | } 42 | }); 43 | break; 44 | } 45 | return msg; 46 | } 47 | 48 | function testAssignment(context, node) { 49 | const msg = checkExpressionAlignment(node.right); 50 | if (msg) 51 | context.report(node, msg); 52 | } 53 | 54 | function testDeclaration(context, node) { 55 | node.declarations.forEach((declaration) => { 56 | const msg = checkExpressionAlignment(declaration.init); 57 | // const start = declaration.init.loc.start; 58 | if (msg) 59 | context.report(node, msg); 60 | }); 61 | } 62 | 63 | module.exports = function(context) { 64 | return { 65 | 'AssignmentExpression': (node) => testAssignment(context, node), 66 | 'VariableDeclaration': (node) => testDeclaration(context, node) 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /test/cli/preserve-breaks.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('run after quit / restart', (t) => { 9 | const script = Path.join('examples', 'three-lines.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.command('breakpoints')) 20 | .then(() => { 21 | t.match(cli.output, 'No breakpoints yet'); 22 | }) 23 | .then(() => cli.command('sb(2)')) 24 | .then(() => cli.command('sb(3)')) 25 | .then(() => cli.command('breakpoints')) 26 | .then(() => { 27 | t.match(cli.output, `#0 ${script}:2`); 28 | t.match(cli.output, `#1 ${script}:3`); 29 | }) 30 | .then(() => cli.stepCommand('c')) // hit line 2 31 | .then(() => cli.stepCommand('c')) // hit line 3 32 | .then(() => { 33 | t.match(cli.output, `break in ${script}:3`); 34 | }) 35 | .then(() => cli.command('restart')) 36 | .then(() => cli.waitForInitialBreak()) 37 | .then(() => { 38 | t.match(cli.output, `break in ${script}:1`); 39 | }) 40 | .then(() => cli.stepCommand('c')) 41 | .then(() => { 42 | t.match(cli.output, `break in ${script}:2`); 43 | }) 44 | .then(() => cli.stepCommand('c')) 45 | .then(() => { 46 | t.match(cli.output, `break in ${script}:3`); 47 | }) 48 | .then(() => cli.command('breakpoints')) 49 | .then(() => { 50 | if (process.platform === 'aix') { 51 | // TODO: There is a known issue on AIX where the breakpoints aren't 52 | // properly resolved yet when we reach this point. 53 | // Eventually that should be figured out but for now we don't want 54 | // to fail builds because of it. 55 | t.match(cli.output, /#0 [^\n]+three-lines\.js\$?:2/); 56 | t.match(cli.output, /#1 [^\n]+three-lines\.js\$?:3/); 57 | } else { 58 | t.match(cli.output, `#0 ${script}:2`); 59 | t.match(cli.output, `#1 ${script}:3`); 60 | } 61 | }) 62 | .then(() => cli.quit()) 63 | .then(null, onFatal); 64 | }); 65 | -------------------------------------------------------------------------------- /test/cli/exceptions.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('break on (uncaught) exceptions', (t) => { 9 | const script = Path.join('examples', 'exceptions.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => { 20 | t.match(cli.output, `break in ${script}:1`); 21 | }) 22 | // making sure it will die by default: 23 | .then(() => cli.command('c')) 24 | // TODO: Remove FATAL ERROR once node doesn't show a FATAL ERROR anymore 25 | .then(() => cli.waitFor(/disconnect|FATAL ERROR/)) 26 | 27 | // Next run: With `breakOnException` it pauses in both places 28 | .then(() => cli.stepCommand('r')) 29 | .then(() => cli.waitForInitialBreak()) 30 | .then(() => { 31 | t.match(cli.output, `break in ${script}:1`); 32 | }) 33 | .then(() => cli.command('breakOnException')) 34 | .then(() => cli.stepCommand('c')) 35 | .then(() => { 36 | t.match(cli.output, `exception in ${script}:3`); 37 | }) 38 | .then(() => cli.stepCommand('c')) 39 | .then(() => { 40 | t.match(cli.output, `exception in ${script}:9`); 41 | }) 42 | 43 | // Next run: With `breakOnUncaught` it only pauses on the 2nd exception 44 | .then(() => cli.command('breakOnUncaught')) 45 | .then(() => cli.stepCommand('r')) // also, the setting survives the restart 46 | .then(() => cli.waitForInitialBreak()) 47 | .then(() => { 48 | t.match(cli.output, `break in ${script}:1`); 49 | }) 50 | .then(() => cli.stepCommand('c')) 51 | .then(() => { 52 | t.match(cli.output, `exception in ${script}:9`); 53 | }) 54 | 55 | // Next run: Back to the initial state! It should die again. 56 | .then(() => cli.command('breakOnNone')) 57 | .then(() => cli.stepCommand('r')) 58 | .then(() => cli.waitForInitialBreak()) 59 | .then(() => { 60 | t.match(cli.output, `break in ${script}:1`); 61 | }) 62 | .then(() => cli.command('c')) 63 | // TODO: Remove FATAL ERROR once node doesn't show a FATAL ERROR anymore 64 | .then(() => cli.waitFor(/disconnect|FATAL ERROR/)) 65 | 66 | .then(() => cli.quit()) 67 | .then(null, onFatal); 68 | }); 69 | -------------------------------------------------------------------------------- /tools/eslint-rules/align-function-arguments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Align arguments in multiline function calls 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | function checkArgumentAlignment(context, node) { 12 | 13 | function isNodeFirstInLine(node, byEndLocation) { 14 | const firstToken = byEndLocation === true ? context.getLastToken(node, 1) : 15 | context.getTokenBefore(node); 16 | const startLine = byEndLocation === true ? node.loc.end.line : 17 | node.loc.start.line; 18 | const endLine = firstToken ? firstToken.loc.end.line : -1; 19 | 20 | return startLine !== endLine; 21 | } 22 | 23 | if (node.arguments.length === 0) 24 | return; 25 | 26 | var msg = ''; 27 | const first = node.arguments[0]; 28 | var currentLine = first.loc.start.line; 29 | const firstColumn = first.loc.start.column; 30 | 31 | const ignoreTypes = [ 32 | 'ArrowFunctionExpression', 33 | 'FunctionExpression', 34 | 'ObjectExpression', 35 | ]; 36 | 37 | const args = node.arguments; 38 | 39 | // For now, don't bother trying to validate potentially complicating things 40 | // like closures. Different people will have very different ideas and it's 41 | // probably best to implement configuration options. 42 | if (args.some((node) => { return ignoreTypes.indexOf(node.type) !== -1; })) { 43 | return; 44 | } 45 | 46 | if (!isNodeFirstInLine(node)) { 47 | return; 48 | } 49 | 50 | var misaligned; 51 | 52 | args.slice(1).forEach((argument) => { 53 | if (!misaligned) { 54 | if (argument.loc.start.line === currentLine + 1) { 55 | if (argument.loc.start.column !== firstColumn) { 56 | if (isNodeFirstInLine(argument)) { 57 | msg = 'Function argument in column ' + 58 | `${argument.loc.start.column + 1}, ` + 59 | `expected in ${firstColumn + 1}`; 60 | misaligned = argument; 61 | } 62 | } 63 | } 64 | } 65 | currentLine = argument.loc.start.line; 66 | }); 67 | 68 | if (msg) 69 | context.report(misaligned, msg); 70 | } 71 | 72 | module.exports = function(context) { 73 | return { 74 | 'CallExpression': (node) => checkArgumentAlignment(context, node) 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /test/cli/exec.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('examples/alive.js', (t) => { 7 | const cli = startCLI(['examples/alive.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) 17 | .then(() => { 18 | t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/o paren'); 19 | }) 20 | .then(() => cli.command('repl')) 21 | .then(() => { 22 | t.match( 23 | cli.output, 24 | 'Press Ctrl + C to leave debug repl\n> ', 25 | 'shows hint for how to leave repl'); 26 | t.notMatch(cli.output, 'debug>', 'changes the repl style'); 27 | }) 28 | .then(() => cli.command('[typeof heartbeat, typeof process.exit]')) 29 | .then(() => cli.waitFor(/function/)) 30 | .then(() => cli.waitForPrompt()) 31 | .then(() => { 32 | t.match( 33 | cli.output, 34 | '[ \'function\', \'function\' ]', 'can evaluate in the repl'); 35 | t.match(cli.output, /> $/); 36 | }) 37 | .then(() => cli.ctrlC()) 38 | .then(() => cli.waitFor(/debug> $/)) 39 | .then(() => cli.command('exec("[typeof heartbeat, typeof process.exit]")')) 40 | .then(() => { 41 | t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/ paren'); 42 | }) 43 | .then(() => cli.command('cont')) 44 | .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) 45 | .then(() => { 46 | t.match( 47 | cli.output, 48 | '[ \'undefined\', \'function\' ]', 49 | 'non-paused exec can see global but not module-scope values'); 50 | }) 51 | .then(() => cli.quit()) 52 | .then(null, onFatal); 53 | }); 54 | 55 | test('exec .scope', (t) => { 56 | const cli = startCLI(['examples/backtrace.js']); 57 | 58 | function onFatal(error) { 59 | cli.quit(); 60 | throw error; 61 | } 62 | 63 | return cli.waitForInitialBreak() 64 | .then(() => cli.waitForPrompt()) 65 | .then(() => cli.stepCommand('c')) 66 | .then(() => cli.command('exec .scope')) 67 | .then(() => { 68 | t.match( 69 | cli.output, 70 | '\'moduleScoped\'', 'displays closure from module body'); 71 | t.match(cli.output, '\'a\'', 'displays local / function arg'); 72 | t.match(cli.output, '\'l1\'', 'displays local scope'); 73 | t.notMatch(cli.output, '\'encodeURIComponent\'', 'omits global scope'); 74 | }) 75 | .then(() => cli.quit()) 76 | .then(null, onFatal); 77 | }); 78 | -------------------------------------------------------------------------------- /tools/eslint-rules/required-modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require usage of specified node modules. 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | var path = require('path'); 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = function(context) { 14 | // trim required module names 15 | var requiredModules = context.options; 16 | 17 | var foundModules = []; 18 | 19 | // if no modules are required we don't need to check the CallExpressions 20 | if (requiredModules.length === 0) { 21 | return {}; 22 | } 23 | 24 | /** 25 | * Function to check if a node is a string literal. 26 | * @param {ASTNode} node The node to check. 27 | * @returns {boolean} If the node is a string literal. 28 | */ 29 | function isString(node) { 30 | return node && node.type === 'Literal' && typeof node.value === 'string'; 31 | } 32 | 33 | /** 34 | * Function to check if a node is a require call. 35 | * @param {ASTNode} node The node to check. 36 | * @returns {boolean} If the node is a require call. 37 | */ 38 | function isRequireCall(node) { 39 | return node.callee.type === 'Identifier' && node.callee.name === 'require'; 40 | } 41 | 42 | /** 43 | * Function to check if a node has an argument that is a required module and 44 | * return its name. 45 | * @param {ASTNode} node The node to check 46 | * @returns {undefined|String} required module name or undefined 47 | */ 48 | function getRequiredModuleName(node) { 49 | var moduleName; 50 | 51 | // node has arguments and first argument is string 52 | if (node.arguments.length && isString(node.arguments[0])) { 53 | var argValue = path.basename(node.arguments[0].value.trim()); 54 | 55 | // check if value is in required modules array 56 | if (requiredModules.indexOf(argValue) !== -1) { 57 | moduleName = argValue; 58 | } 59 | } 60 | 61 | return moduleName; 62 | } 63 | 64 | return { 65 | 'CallExpression': function(node) { 66 | if (isRequireCall(node)) { 67 | var requiredModuleName = getRequiredModuleName(node); 68 | 69 | if (requiredModuleName) { 70 | foundModules.push(requiredModuleName); 71 | } 72 | } 73 | }, 74 | 'Program:exit': function(node) { 75 | if (foundModules.length < requiredModules.length) { 76 | var missingModules = requiredModules.filter( 77 | function(module) { 78 | return foundModules.indexOf(module === -1); 79 | } 80 | ); 81 | missingModules.forEach(function(moduleName) { 82 | context.report( 83 | node, 84 | 'Mandatory module "{{moduleName}}" must be loaded.', 85 | { moduleName: moduleName } 86 | ); 87 | }); 88 | } 89 | } 90 | }; 91 | }; 92 | 93 | module.exports.schema = { 94 | 'type': 'array', 95 | 'additionalItems': { 96 | 'type': 'string' 97 | }, 98 | 'uniqueItems': true 99 | }; 100 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | root: true 2 | 3 | env: 4 | node: true 5 | es6: true 6 | 7 | parserOptions: 8 | ecmaVersion: 2017 9 | 10 | rules: 11 | # Possible Errors 12 | # http://eslint.org/docs/rules/#possible-errors 13 | comma-dangle: [2, only-multiline] 14 | no-control-regex: 2 15 | no-debugger: 2 16 | no-dupe-args: 2 17 | no-dupe-keys: 2 18 | no-duplicate-case: 2 19 | no-empty-character-class: 2 20 | no-ex-assign: 2 21 | no-extra-boolean-cast: 2 22 | no-extra-parens: [2, functions] 23 | no-extra-semi: 2 24 | no-func-assign: 2 25 | no-invalid-regexp: 2 26 | no-irregular-whitespace: 2 27 | no-obj-calls: 2 28 | no-proto: 2 29 | no-template-curly-in-string: 2 30 | no-unexpected-multiline: 2 31 | no-unreachable: 2 32 | no-unsafe-negation: 2 33 | use-isnan: 2 34 | valid-typeof: 2 35 | 36 | # Best Practices 37 | # http://eslint.org/docs/rules/#best-practices 38 | dot-location: [2, property] 39 | no-fallthrough: 2 40 | no-global-assign: 2 41 | no-multi-spaces: 2 42 | no-octal: 2 43 | no-redeclare: 2 44 | no-self-assign: 2 45 | no-unused-labels: 2 46 | no-useless-call: 2 47 | no-useless-escape: 2 48 | no-void: 2 49 | no-with: 2 50 | 51 | # Strict Mode 52 | # http://eslint.org/docs/rules/#strict-mode 53 | strict: [2, global] 54 | 55 | # Variables 56 | # http://eslint.org/docs/rules/#variables 57 | no-delete-var: 2 58 | no-undef: 2 59 | no-unused-vars: [2, {args: none}] 60 | 61 | # Node.js and CommonJS 62 | # http://eslint.org/docs/rules/#nodejs-and-commonjs 63 | no-mixed-requires: 2 64 | no-new-require: 2 65 | no-path-concat: 2 66 | no-restricted-modules: [2, sys, _linklist] 67 | no-restricted-properties: [2, { 68 | object: assert, 69 | property: deepEqual, 70 | message: Please use assert.deepStrictEqual(). 71 | }, { 72 | property: __defineGetter__, 73 | message: __defineGetter__ is deprecated. 74 | }, { 75 | property: __defineSetter__, 76 | message: __defineSetter__ is deprecated. 77 | }] 78 | 79 | # Stylistic Issues 80 | # http://eslint.org/docs/rules/#stylistic-issues 81 | brace-style: [2, 1tbs, {allowSingleLine: true}] 82 | comma-spacing: 2 83 | comma-style: 2 84 | computed-property-spacing: 2 85 | eol-last: 2 86 | func-call-spacing: 2 87 | func-name-matching: 2 88 | indent: [2, 2, {SwitchCase: 1, MemberExpression: 1}] 89 | key-spacing: [2, {mode: minimum}] 90 | keyword-spacing: 2 91 | linebreak-style: [2, unix] 92 | max-len: [2, 80, 2] 93 | new-parens: 2 94 | no-mixed-spaces-and-tabs: 2 95 | no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] 96 | no-tabs: 2 97 | no-trailing-spaces: 2 98 | quotes: [2, single, avoid-escape] 99 | semi: 2 100 | semi-spacing: 2 101 | space-before-blocks: [2, always] 102 | space-before-function-paren: [2, never] 103 | space-in-parens: [2, never] 104 | space-infix-ops: 2 105 | space-unary-ops: 2 106 | 107 | # ECMAScript 6 108 | # http://eslint.org/docs/rules/#ecmascript-6 109 | arrow-parens: [2, always] 110 | arrow-spacing: [2, {before: true, after: true}] 111 | constructor-super: 2 112 | no-class-assign: 2 113 | no-confusing-arrow: 2 114 | no-const-assign: 2 115 | no-dupe-class-members: 2 116 | no-new-symbol: 2 117 | no-this-before-super: 2 118 | prefer-const: [2, {ignoreReadBeforeAssign: true}] 119 | rest-spread-spacing: 2 120 | template-curly-spacing: 2 121 | 122 | # Custom rules in tools/eslint-rules 123 | align-function-arguments: 2 124 | align-multiline-assignment: 2 125 | assert-fail-single-argument: 2 126 | new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] 127 | 128 | # Global scoped method and vars 129 | globals: 130 | COUNTER_HTTP_CLIENT_REQUEST: false 131 | COUNTER_HTTP_CLIENT_RESPONSE: false 132 | COUNTER_HTTP_SERVER_REQUEST: false 133 | COUNTER_HTTP_SERVER_RESPONSE: false 134 | COUNTER_NET_SERVER_CONNECTION: false 135 | COUNTER_NET_SERVER_CONNECTION_CLOSE: false 136 | DTRACE_HTTP_CLIENT_REQUEST: false 137 | DTRACE_HTTP_CLIENT_RESPONSE: false 138 | DTRACE_HTTP_SERVER_REQUEST: false 139 | DTRACE_HTTP_SERVER_RESPONSE: false 140 | DTRACE_NET_SERVER_CONNECTION: false 141 | DTRACE_NET_STREAM_END: false 142 | LTTNG_HTTP_CLIENT_REQUEST: false 143 | LTTNG_HTTP_CLIENT_RESPONSE: false 144 | LTTNG_HTTP_SERVER_REQUEST: false 145 | LTTNG_HTTP_SERVER_RESPONSE: false 146 | LTTNG_NET_SERVER_CONNECTION: false 147 | LTTNG_NET_STREAM_END: false 148 | -------------------------------------------------------------------------------- /test/cli/start-cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const spawn = require('child_process').spawn; 3 | 4 | // This allows us to keep the helper inside of `test/` without tap warning 5 | // about "pending" test files. 6 | const tap = require('tap'); 7 | tap.test('startCLI', (t) => t.end()); 8 | 9 | const CLI = 10 | process.env.USE_EMBEDDED_NODE_INSPECT === '1' ? 11 | 'inspect' : 12 | require.resolve('../../cli.js'); 13 | 14 | const BREAK_MESSAGE = new RegExp('(?:' + [ 15 | 'assert', 'break', 'break on start', 'debugCommand', 16 | 'exception', 'other', 'promiseRejection', 17 | ].join('|') + ') in', 'i'); 18 | 19 | function startCLI(args, flags = []) { 20 | const child = spawn(process.execPath, [...flags, CLI, ...args]); 21 | let isFirstStdoutChunk = true; 22 | 23 | const outputBuffer = []; 24 | function bufferOutput(chunk) { 25 | if (isFirstStdoutChunk) { 26 | isFirstStdoutChunk = false; 27 | outputBuffer.push(chunk.replace(/^debug>\s*/, '')); 28 | } else { 29 | outputBuffer.push(chunk); 30 | } 31 | } 32 | 33 | function getOutput() { 34 | return outputBuffer.join('').toString() 35 | .replace(/^[^\n]*?[\b]/mg, ''); 36 | } 37 | 38 | child.stdout.setEncoding('utf8'); 39 | child.stdout.on('data', bufferOutput); 40 | child.stderr.setEncoding('utf8'); 41 | child.stderr.on('data', bufferOutput); 42 | 43 | if (process.env.VERBOSE === '1') { 44 | child.stdout.pipe(process.stderr); 45 | child.stderr.pipe(process.stderr); 46 | } 47 | 48 | return { 49 | flushOutput() { 50 | const output = this.output; 51 | outputBuffer.length = 0; 52 | return output; 53 | }, 54 | 55 | waitFor(pattern, timeout = 2000) { 56 | function checkPattern(str) { 57 | if (Array.isArray(pattern)) { 58 | return pattern.every((p) => p.test(str)); 59 | } 60 | return pattern.test(str); 61 | } 62 | 63 | return new Promise((resolve, reject) => { 64 | function checkOutput() { 65 | if (checkPattern(getOutput())) { 66 | tearDown(); // eslint-disable-line no-use-before-define 67 | resolve(); 68 | } 69 | } 70 | 71 | function onChildExit() { 72 | tearDown(); // eslint-disable-line no-use-before-define 73 | reject(new Error( 74 | `Child quit while waiting for ${pattern}; found: ${this.output}`)); 75 | } 76 | 77 | const timer = setTimeout(() => { 78 | tearDown(); // eslint-disable-line no-use-before-define 79 | reject(new Error([ 80 | `Timeout (${timeout}) while waiting for ${pattern}`, 81 | `found: ${this.output}`, 82 | ].join('; '))); 83 | }, timeout); 84 | 85 | function tearDown() { 86 | clearTimeout(timer); 87 | child.stdout.removeListener('data', checkOutput); 88 | child.removeListener('exit', onChildExit); 89 | } 90 | 91 | child.on('exit', onChildExit); 92 | child.stdout.on('data', checkOutput); 93 | checkOutput(); 94 | }); 95 | }, 96 | 97 | waitForPrompt(timeout = 2000) { 98 | return this.waitFor(/>\s+$/, timeout); 99 | }, 100 | 101 | waitForInitialBreak(timeout = 2000) { 102 | return this.waitFor(/break (?:on start )?in/i, timeout) 103 | .then(() => { 104 | if (/Break on start/.test(this.output)) { 105 | return this.command('next', false) 106 | .then(() => this.waitFor(/break in/, timeout)); 107 | } 108 | }); 109 | }, 110 | 111 | ctrlC() { 112 | return this.command('.interrupt'); 113 | }, 114 | 115 | get output() { 116 | return getOutput(); 117 | }, 118 | 119 | get rawOutput() { 120 | return outputBuffer.join('').toString(); 121 | }, 122 | 123 | parseSourceLines() { 124 | return getOutput().split('\n') 125 | .map((line) => line.match(/(?:\*|>)?\s*(\d+)/)) 126 | .filter((match) => match !== null) 127 | .map((match) => +match[1]); 128 | }, 129 | 130 | command(input, flush = true) { 131 | if (flush) { 132 | this.flushOutput(); 133 | } 134 | child.stdin.write(input); 135 | child.stdin.write('\n'); 136 | return this.waitForPrompt(); 137 | }, 138 | 139 | stepCommand(input) { 140 | this.flushOutput(); 141 | child.stdin.write(input); 142 | child.stdin.write('\n'); 143 | return this 144 | .waitFor(BREAK_MESSAGE) 145 | .then(() => this.waitForPrompt()); 146 | }, 147 | 148 | quit() { 149 | return new Promise((resolve) => { 150 | child.stdin.end(); 151 | child.on('exit', resolve); 152 | }); 153 | }, 154 | }; 155 | } 156 | module.exports = startCLI; 157 | -------------------------------------------------------------------------------- /test/cli/launch.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('custom port', (t) => { 9 | const CUSTOM_PORT = '9230'; 10 | const script = Path.join('examples', 'three-lines.js'); 11 | 12 | const cli = startCLI([`--port=${CUSTOM_PORT}`, script]); 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => { 17 | t.match(cli.output, 'debug>', 'prints a prompt'); 18 | t.match( 19 | cli.output, 20 | new RegExp(`< Debugger listening on [^\n]*${CUSTOM_PORT}`), 21 | 'forwards child output'); 22 | }) 23 | .then(() => cli.quit()) 24 | .then((code) => { 25 | t.equal(code, 0, 'exits with success'); 26 | }); 27 | }); 28 | 29 | test('random port', (t) => { 30 | const script = Path.join('examples', 'three-lines.js'); 31 | 32 | const cli = startCLI(['--port=0', script]); 33 | 34 | return cli.waitForInitialBreak() 35 | .then(() => cli.waitForPrompt()) 36 | .then(() => { 37 | t.match(cli.output, 'debug>', 'prints a prompt'); 38 | t.match( 39 | cli.output, 40 | /< Debugger listening on /, 41 | 'forwards child output'); 42 | }) 43 | .then(() => cli.quit()) 44 | .then((code) => { 45 | t.equal(code, 0, 'exits with success'); 46 | }); 47 | }); 48 | 49 | test('random port with --inspect-port=0', (t) => { 50 | const script = Path.join('examples', 'three-lines.js'); 51 | 52 | const cli = startCLI([script], ['--inspect-port=0']); 53 | 54 | return cli.waitForInitialBreak() 55 | .then(() => cli.waitForPrompt()) 56 | .then(() => { 57 | t.match(cli.output, 'debug>', 'prints a prompt'); 58 | t.match( 59 | cli.output, 60 | /< Debugger listening on /, 61 | 'forwards child output'); 62 | }) 63 | .then(() => cli.quit()) 64 | .then((code) => { 65 | t.equal(code, 0, 'exits with success'); 66 | }); 67 | }); 68 | 69 | test('examples/three-lines.js', (t) => { 70 | const script = Path.join('examples', 'three-lines.js'); 71 | const cli = startCLI([script]); 72 | 73 | return cli.waitForInitialBreak() 74 | .then(() => cli.waitForPrompt()) 75 | .then(() => { 76 | t.match(cli.output, 'debug>', 'prints a prompt'); 77 | t.match( 78 | cli.output, 79 | /< Debugger listening on [^\n]*9229/, 80 | 'forwards child output'); 81 | }) 82 | .then(() => cli.command('["hello", "world"].join(" ")')) 83 | .then(() => { 84 | t.match(cli.output, 'hello world', 'prints the result'); 85 | }) 86 | .then(() => cli.command('')) 87 | .then(() => { 88 | t.match(cli.output, 'hello world', 'repeats the last command on '); 89 | }) 90 | .then(() => cli.command('version')) 91 | .then(() => { 92 | t.match(cli.output, process.versions.v8, 'version prints the v8 version'); 93 | }) 94 | .then(() => cli.quit()) 95 | .then((code) => { 96 | t.equal(code, 0, 'exits with success'); 97 | }); 98 | }); 99 | 100 | test('run after quit / restart', (t) => { 101 | const script = Path.join('examples', 'three-lines.js'); 102 | const cli = startCLI([script]); 103 | 104 | function onFatal(error) { 105 | cli.quit(); 106 | throw error; 107 | } 108 | 109 | return cli.waitForInitialBreak() 110 | .then(() => cli.waitForPrompt()) 111 | .then(() => cli.stepCommand('n')) 112 | .then(() => { 113 | t.match( 114 | cli.output, 115 | `break in ${script}:2`, 116 | 'steps to the 2nd line'); 117 | }) 118 | .then(() => cli.command('cont')) 119 | .then(() => cli.waitFor(/disconnect/)) 120 | .then(() => { 121 | t.match( 122 | cli.output, 123 | 'Waiting for the debugger to disconnect', 124 | 'the child was done'); 125 | }) 126 | .then(() => { 127 | // On windows the socket won't close by itself 128 | return cli.command('kill'); 129 | }) 130 | .then(() => cli.command('cont')) 131 | .then(() => cli.waitFor(/start the app/)) 132 | .then(() => { 133 | t.match(cli.output, 'Use `run` to start the app again'); 134 | }) 135 | .then(() => cli.stepCommand('run')) 136 | .then(() => cli.waitForInitialBreak()) 137 | .then(() => cli.waitForPrompt()) 138 | .then(() => { 139 | t.match( 140 | cli.output, 141 | `break in ${script}:1`, 142 | 'is back at the beginning'); 143 | }) 144 | .then(() => cli.stepCommand('n')) 145 | .then(() => { 146 | t.match( 147 | cli.output, 148 | `break in ${script}:2`, 149 | 'steps to the 2nd line'); 150 | }) 151 | .then(() => cli.stepCommand('restart')) 152 | .then(() => cli.waitForInitialBreak()) 153 | .then(() => { 154 | t.match( 155 | cli.output, 156 | `break in ${script}:1`, 157 | 'is back at the beginning'); 158 | }) 159 | .then(() => cli.command('kill')) 160 | .then(() => cli.command('cont')) 161 | .then(() => cli.waitFor(/start the app/)) 162 | .then(() => { 163 | t.match(cli.output, 'Use `run` to start the app again'); 164 | }) 165 | .then(() => cli.stepCommand('run')) 166 | .then(() => cli.waitForInitialBreak()) 167 | .then(() => cli.waitForPrompt()) 168 | .then(() => { 169 | t.match( 170 | cli.output, 171 | `break in ${script}:1`, 172 | 'is back at the beginning'); 173 | }) 174 | .then(() => cli.quit()) 175 | .then(null, onFatal); 176 | }); 177 | -------------------------------------------------------------------------------- /test/cli/break.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('stepping through breakpoints', (t) => { 9 | const script = Path.join('examples', 'break.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => { 20 | t.match( 21 | cli.output, 22 | `break in ${script}:1`, 23 | 'pauses in the first line of the script'); 24 | t.match( 25 | cli.output, 26 | /> 1 \(function \([^)]+\) \{ const x = 10;/, 27 | 'shows the source and marks the current line'); 28 | }) 29 | .then(() => cli.stepCommand('n')) 30 | .then(() => { 31 | t.match( 32 | cli.output, 33 | `break in ${script}:2`, 34 | 'pauses in next line of the script'); 35 | t.match( 36 | cli.output, 37 | '> 2 let name = \'World\';', 38 | 'marks the 2nd line'); 39 | }) 40 | .then(() => cli.stepCommand('next')) 41 | .then(() => { 42 | t.match( 43 | cli.output, 44 | `break in ${script}:3`, 45 | 'pauses in next line of the script'); 46 | t.match( 47 | cli.output, 48 | '> 3 name = \'Robin\';', 49 | 'marks the 3nd line'); 50 | }) 51 | .then(() => cli.stepCommand('cont')) 52 | .then(() => { 53 | t.match( 54 | cli.output, 55 | `break in ${script}:10`, 56 | 'pauses on the next breakpoint'); 57 | t.match( 58 | cli.output, 59 | '>10 debugger;', 60 | 'marks the debugger line'); 61 | }) 62 | 63 | // Prepare additional breakpoints 64 | .then(() => cli.command('sb("break.js", 6)')) 65 | .then(() => t.notMatch(cli.output, 'Could not resolve breakpoint')) 66 | .then(() => cli.command('sb("otherFunction()")')) 67 | .then(() => cli.command('sb(16)')) 68 | .then(() => t.notMatch(cli.output, 'Could not resolve breakpoint')) 69 | .then(() => cli.command('breakpoints')) 70 | .then(() => { 71 | t.match(cli.output, `#0 ${script}:6`); 72 | t.match(cli.output, `#1 ${script}:16`); 73 | }) 74 | 75 | .then(() => cli.command('list()')) 76 | .then(() => { 77 | t.match(cli.output, '>10 debugger;', 'prints and marks current line'); 78 | t.strictDeepEqual( 79 | cli.parseSourceLines(), 80 | [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 81 | 'prints 5 lines before and after'); 82 | }) 83 | .then(() => cli.command('list(2)')) 84 | .then(() => { 85 | t.match(cli.output, '>10 debugger;', 'prints and marks current line'); 86 | t.strictDeepEqual( 87 | cli.parseSourceLines(), 88 | [8, 9, 10, 11, 12], 89 | 'prints 2 lines before and after'); 90 | }) 91 | 92 | .then(() => cli.stepCommand('s')) 93 | .then(() => cli.stepCommand('')) 94 | .then(() => { 95 | t.match( 96 | cli.output, 97 | 'break in timers.js', 98 | 'entered timers.js'); 99 | }) 100 | .then(() => cli.stepCommand('cont')) 101 | .then(() => { 102 | t.match( 103 | cli.output, 104 | `break in ${script}:16`, 105 | 'found breakpoint we set above w/ line number only'); 106 | }) 107 | .then(() => cli.stepCommand('cont')) 108 | .then(() => { 109 | t.match( 110 | cli.output, 111 | `break in ${script}:6`, 112 | 'found breakpoint we set above w/ line number & script'); 113 | }) 114 | .then(() => cli.stepCommand('')) 115 | .then(() => { 116 | t.match( 117 | cli.output, 118 | `debugCommand in ${script}:14`, 119 | 'found function breakpoint we set above'); 120 | }) 121 | .then(() => cli.quit()) 122 | .then(null, onFatal); 123 | }); 124 | 125 | test('sb before loading file', (t) => { 126 | const script = Path.join('examples', 'cjs', 'index.js'); 127 | const otherScript = Path.join('examples', 'cjs', 'other.js'); 128 | const cli = startCLI([script]); 129 | 130 | function onFatal(error) { 131 | cli.quit(); 132 | throw error; 133 | } 134 | 135 | return cli.waitForInitialBreak() 136 | .then(() => cli.waitForPrompt()) 137 | .then(() => cli.command('sb("other.js", 2)')) 138 | .then(() => { 139 | t.match( 140 | cli.output, 141 | 'not loaded yet', 142 | 'warns that the script was not loaded yet'); 143 | }) 144 | .then(() => cli.stepCommand('cont')) 145 | .then(() => { 146 | t.match( 147 | cli.output, 148 | `break in ${otherScript}:2`, 149 | 'found breakpoint in file that was not loaded yet'); 150 | }) 151 | .then(() => cli.quit()) 152 | .then(null, onFatal); 153 | }); 154 | 155 | test('clearBreakpoint', (t) => { 156 | const script = Path.join('examples', 'break.js'); 157 | const cli = startCLI([script]); 158 | 159 | function onFatal(error) { 160 | cli.quit(); 161 | throw error; 162 | } 163 | 164 | return cli.waitForInitialBreak() 165 | .then(() => cli.waitForPrompt()) 166 | .then(() => cli.command('sb("break.js", 3)')) 167 | .then(() => cli.command('sb("break.js", 9)')) 168 | .then(() => cli.command('breakpoints')) 169 | .then(() => { 170 | t.match(cli.output, `#0 ${script}:3`); 171 | t.match(cli.output, `#1 ${script}:9`); 172 | }) 173 | .then(() => cli.command('clearBreakpoint("break.js", 4)')) 174 | .then(() => { 175 | t.match(cli.output, 'Could not find breakpoint'); 176 | }) 177 | .then(() => cli.command('clearBreakpoint("not-such-script.js", 3)')) 178 | .then(() => { 179 | t.match(cli.output, 'Could not find breakpoint'); 180 | }) 181 | .then(() => cli.command('clearBreakpoint("break.js", 3)')) 182 | .then(() => cli.command('breakpoints')) 183 | .then(() => { 184 | t.match(cli.output, `#0 ${script}:9`); 185 | }) 186 | .then(() => cli.stepCommand('cont')) 187 | .then(() => { 188 | t.match( 189 | cli.output, 190 | `break in ${script}:9`, 191 | 'hits the 2nd breakpoint because the 1st was cleared'); 192 | }) 193 | .then(() => cli.quit()) 194 | .then(null, onFatal); 195 | }); 196 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 🎉🏅 Thanks for helping us improve this project! 🙏 4 | 5 | This document outlines some of the practices we care about. 6 | If you have any questions or suggestions about the process, 7 | feel free to [open an issue](#reporting-issues). 8 | 9 | ## Code of Conduct 10 | 11 | The [Node.js Code of Conduct][] applies to this repo. 12 | 13 | [Node.js Code of Conduct]: https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md 14 | 15 | ## Governance 16 | 17 | This project falls under the governance of the Node.js Diagnostics WG as 18 | described at . 19 | 20 | ## Developer's Certificate of Origin 1.1 21 | 22 | By making a contribution to this project, I certify that: 23 | 24 | * (a) The contribution was created in whole or in part by me and I 25 | have the right to submit it under the open source license 26 | indicated in the file; or 27 | 28 | * (b) The contribution is based upon previous work that, to the best 29 | of my knowledge, is covered under an appropriate open source 30 | license and I have the right under that license to submit that 31 | work with modifications, whether created in whole or in part 32 | by me, under the same open source license (unless I am 33 | permitted to submit under a different license), as indicated 34 | in the file; or 35 | 36 | * (c) The contribution was provided directly to me by some other 37 | person who certified (a), (b) or (c) and I have not modified 38 | it. 39 | 40 | * (d) I understand and agree that this project and the contribution 41 | are public and that a record of the contribution (including all 42 | personal information I submit with it, including my sign-off) is 43 | maintained indefinitely and may be redistributed consistent with 44 | this project or the open source license(s) involved. 45 | 46 | ## How Can I Contribute? 47 | 48 | ### Reporting Issues 49 | 50 | If you find any mistakes in the docs or a bug in the code, 51 | please [open an issue in Github](https://github.com/nodejs/node-inspect/issues/new) so we can look into it. 52 | You can also [create a PR](#contributing-code) fixing it yourself of course. 53 | 54 | If you report a bug, please follow these guidelines: 55 | 56 | * Make sure the bug exists in the latest version. 57 | * Include instructions on how to reproduce the issue. 58 | The instructions should be as minimal as possible 59 | and answer the three big questions: 60 | 1. What are the exact steps you took? This includes the exact versions of node, npm, and any packages involved. 61 | 1. What result are you expecting? 62 | 1. What is the actual result? 63 | 64 | ### Improving Documentation 65 | 66 | For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/). 67 | The only thing to keep in mind is to prefix the commit message with "docs: ". 68 | The default commit message generated by Github will lead to a failing CI build. 69 | 70 | For larger updates to the documentation 71 | it might be better to follow the [instructions for contributing code below](#contributing-code). 72 | 73 | ### Contributing Code 74 | 75 | **Note:** If you're planning on making substantial changes, 76 | please [open an issue first to discuss your idea](#reporting-issues). 77 | Otherwise you might end up investing a lot of work 78 | only to discover that it conflicts with plans the maintainers might have. 79 | 80 | The general steps for creating a pull request are: 81 | 82 | 1. Create a branch for your change. 83 | Always start your branch from the latest `master`. 84 | We often prefix the branch name with our initials, e.g. `jk-a-change`. 85 | 1. Run `npm install` to install the dependencies. 86 | 1. If you're fixing a bug, be sure to write a test *first*. 87 | That way you can validate that the test actually catches the bug and doesn't pass. 88 | 1. Make your changes to the code. 89 | Remember to update the tests if you add new features or change behavior. 90 | 1. Run the tests via `npm test`. This will also run style checks and other validations. 91 | You might see errors about uncommitted files. 92 | This is expected until you commit your changes. 93 | 1. Once you're done, `git add .` and `git commit`. 94 | Please follow the [commit message conventions](#commits--commit-messages) described below. 95 | 1. Push your branch to Github & create a PR. 96 | 97 | #### Code Style 98 | 99 | In addition to any linting rules the project might include, 100 | a few general rules of thumb: 101 | 102 | * Try to match the style of the rest of the code. 103 | * We prefer simple code that is easy to understand over terse, expressive code. 104 | * We try to structure projects by semantics instead of role. 105 | E.g. we'd rather have a `tree.js` module that contains tree traversal-related helpers 106 | than a `helpers.js` module. 107 | * Actually, if you create helpers you might want to put those into a separate package. 108 | That way it's easier to reuse them. 109 | 110 | #### Commits & Commit Messages 111 | 112 | Please follow the [angular commit message conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines). 113 | We use an automated tool for generating releases 114 | that depends on the conventions to determine the next version and the content of the changelog. 115 | Commit messages that don't follow the conventions will cause `npm test` (and thus CI) to fail. 116 | 117 | The short summary - a commit message should look like this: 118 | 119 | ``` 120 | : 121 | 122 | 123 | 124 | 125 | 126 |