├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bench.mjs ├── dist └── index.cjs ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src └── index.mjs └── test ├── fixtures ├── assignment │ ├── expected.js │ └── fixture.js ├── assignment_singlevar │ ├── expected.js │ └── fixture.js ├── cjs │ ├── expected.cjs │ ├── postlude.cjs │ └── prelude.cjs ├── custom_modules_cjs │ ├── expected.js │ └── fixture.js ├── custom_modules_mjs │ ├── expected.js │ └── fixture.js ├── mjs │ ├── expected.mjs │ └── postlude.mjs ├── non_block_statement │ ├── expected.js │ └── fixture.js ├── not_an_expression_statement │ ├── expected.js │ └── fixture.js ├── variable_declarator_singlevar │ ├── expected.js │ └── fixture.js └── various_assertion_methods │ ├── expected.js │ └── fixture.js └── test.mjs /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [2.0.2](https://github.com/unassert-js/unassert/releases/tag/v2.0.2) (2023-04-20) 2 | 3 | #### Bug Fixes 4 | 5 | * [Ensure tooling can access package.json](https://github.com/unassert-js/unassert/pull/46) by [@ljharb](https://github.com/ljharb) 6 | 7 | 8 | ### [2.0.1](https://github.com/unassert-js/unassert/releases/tag/v2.0.1) (2023-04-19) 9 | 10 | #### Bug Fixes 11 | 12 | * [Fix "exports" for node 13.0-13.6](https://github.com/unassert-js/unassert/pull/41) by [@ljharb](https://github.com/ljharb) 13 | 14 | 15 | ## [2.0.0](https://github.com/unassert-js/unassert/releases/tag/v2.0.0) (2022-08-01) 16 | 17 | 18 | #### Features 19 | 20 | * Variable Tracking 21 | * [Variable Tracking: remove assertion calls based on their imported variable names](https://github.com/unassert-js/unassert/pull/34) 22 | 23 | * Restructured codebase 24 | * [Migrate codebase to ESM and provide CJS/ESM dual package](https://github.com/unassert-js/unassert/pull/29) 25 | * [Replace default exported `unassert` with named exported `unassertAst`](https://github.com/unassert-js/unassert/pull/27) 26 | 27 | * Performance improvement 28 | * [Replace AST matcher with simpler and robust logic](https://github.com/unassert-js/unassert/pull/25) 29 | * [Add Benchmark Suite to run benchmark continuously](https://github.com/unassert-js/unassert/pull/39) 30 | * v2.0.0 is 20 times faster than v1.6.0 31 | 32 | * Newly supported syntaxes and features 33 | * [Support strict assertion mode newly exposed as 'node:assert/strict'](https://github.com/unassert-js/unassert/pull/31) 34 | * [Support destructured assignment of strict property](https://github.com/unassert-js/unassert/pull/32) 35 | * [Support safe removal of loop invariants in for-of statement](https://github.com/unassert-js/unassert/pull/35) 36 | * [Support removal of async assertion such as `assert.rejects`](https://github.com/unassert-js/unassert/pull/36) 37 | 38 | 39 | #### Bug Fixes 40 | 41 | * [unassert causes SyntaxError when body of LabeledStatement is single ExpressionStatement](https://github.com/unassert-js/unassert/pull/37) 42 | 43 | 44 | #### Breaking Changes 45 | 46 | * [Replace default exported `unassert` with named exported `unassertAst`](https://github.com/unassert-js/unassert/pull/27) 47 | 48 | `unassert` function is removed in favor of named exports aiming ESM era. Please use `unassert.unassertAst` instead. 49 | 50 | before: 51 | ```js 52 | const unassert = require('unassert'); 53 | ``` 54 | 55 | after: 56 | ```js 57 | const { unassertAst } = require('unassert'); 58 | ``` 59 | 60 | 61 | * [Replace AST matcher with simpler and robust logic](https://github.com/unassert-js/unassert/pull/25) 62 | 63 | Configuration options are simplified a lot. Patterns are aggregated into `modules`. 64 | 65 | before: 66 | ```js 67 | { 68 | assertionPatterns: [ 69 | 'assert(value, [message])', 70 | 'assert.ok(value, [message])', 71 | 'assert.equal(actual, expected, [message])', 72 | 'assert.notEqual(actual, expected, [message])', 73 | 'assert.strictEqual(actual, expected, [message])', 74 | 'assert.notStrictEqual(actual, expected, [message])', 75 | 'assert.deepEqual(actual, expected, [message])', 76 | 'assert.notDeepEqual(actual, expected, [message])', 77 | 'assert.deepStrictEqual(actual, expected, [message])', 78 | 'assert.notDeepStrictEqual(actual, expected, [message])', 79 | 'assert.fail(actual, expected, message, operator)', 80 | 'assert.throws(block, [error], [message])', 81 | 'assert.doesNotThrow(block, [message])', 82 | 'assert.ifError(value)', 83 | 'console.assert(value, [message])' 84 | ], 85 | requirePatterns: [ 86 | 'assert = require("assert")' 87 | ], 88 | importPatterns: [ 89 | 'import assert from "assert"', 90 | 'import * as assert from "assert"' 91 | ] 92 | } 93 | ``` 94 | 95 | after: 96 | ```js 97 | { 98 | modules: [ 99 | 'assert', 100 | 'assert/strict', 101 | 'node:assert', 102 | 'node:assert/strict' 103 | ] 104 | } 105 | ``` 106 | 107 | 108 | * [Drop power-assert support from default patterns since power-assert works transparently](https://github.com/unassert-js/unassert/pull/28) 109 | 110 | Move power-assert support away from default patterns since power-assert empowers default assert function transparently, so should not be required explicitly. If power-assert is still required explicitly, add 'power-assert' to `modules` in customized configuration. 111 | 112 | after: 113 | ```js 114 | { 115 | modules: [ 116 | 'assert', 117 | 'assert/strict', 118 | 'node:assert', 119 | 'node:assert/strict', 120 | 'power-assert' 121 | ] 122 | } 123 | ``` 124 | 125 | 126 | ## [1.6.0](https://github.com/unassert-js/unassert/releases/tag/v1.6.0) (2019-09-20) 127 | 128 | #### Chores 129 | 130 | * [Dependency cleanup and updates](https://github.com/unassert-js/unassert/pull/15) by [@goto-bus-stop](https://github.com/goto-bus-stop) 131 | 132 | 133 | ### [1.5.1](https://github.com/unassert-js/unassert/releases/tag/v1.5.1) (2017-01-01) 134 | 135 | 136 | #### Chores 137 | 138 | * switch to call-matcher ([de4172d](https://github.com/unassert-js/unassert/commit/de4172d532fc5edcabcdc5365ed310af118d88e1)) 139 | 140 | 141 | ## [1.5.0](https://github.com/unassert-js/unassert/releases/tag/v1.5.0) (2016-12-19) 142 | 143 | 144 | #### Features 145 | 146 | * [Expose `createVisitor` to make assertion and declaration patterns configurable](https://github.com/unassert-js/unassert/pull/9) 147 | * transfer to unassert-js organization ([39164de5](https://github.com/unassert-js/unassert/commit/39164de555ee88c00b01085b9244029ff53f319b)) 148 | 149 | 150 | ### [1.4.1](https://github.com/unassert-js/unassert/releases/tag/v1.4.1) (2016-07-22) 151 | 152 | 153 | #### Bug Fixes 154 | 155 | * [Add empty block if parent is non-block statement](https://github.com/unassert-js/unassert/pull/8) 156 | 157 | 158 | ## [1.4.0](https://github.com/unassert-js/unassert/releases/tag/v1.4.0) (2016-05-02) 159 | 160 | 161 | #### Features 162 | 163 | * [Support ImportNamespaceSpecifier](https://github.com/unassert-js/unassert/pull/6) 164 | 165 | 166 | ### [1.3.1](https://github.com/unassert-js/unassert/releases/tag/v1.3.1) (2015-12-08) 167 | 168 | 169 | #### Bug Fixes 170 | 171 | * remove assertion if and only if its parent is an ExpressionStatement ([6515857a](https://github.com/unassert-js/unassert/commit/6515857a28f96ac6de9a92eeeb97629210c239eb), closes [#4](https://github.com/unassert-js/unassert/issues/4)) 172 | 173 | 174 | ## [1.3.0](https://github.com/unassert-js/unassert/releases/tag/v1.3.0) (2015-10-06) 175 | 176 | 177 | #### Features 178 | 179 | * [Support removal of ES6 import declaration](https://github.com/unassert-js/unassert/pull/3) 180 | 181 | 182 | ### [1.2.1](https://github.com/unassert-js/unassert/releases/tag/v1.2.1) (2015-09-29) 183 | 184 | 185 | #### Bug Fixes 186 | 187 | * remove assignment if and only if operator is `=` ([f14bcd3e](https://github.com/unassert-js/unassert/commit/f14bcd3efd030d33d27ab48f6c89f2ad059cd476)) 188 | 189 | 190 | ## [1.2.0](https://github.com/unassert-js/unassert/releases/tag/v1.2.0) (2015-09-25) 191 | 192 | 193 | #### Features 194 | 195 | * support removal of assert variable assignment ([82cbeea8](https://github.com/unassert-js/unassert/commit/82cbeea801257e2a776a50996666112d96ef42b4)) 196 | 197 | 198 | ## [1.1.0](https://github.com/unassert-js/unassert/releases/tag/v1.1.0) (2015-08-11) 199 | 200 | 201 | #### Features 202 | 203 | * support removal of CommonJS assert declaration ([1c3dc425](https://github.com/unassert-js/unassert/commit/1c3dc425f93f1d8b3790e1ea909a14ff0a6f076f)) 204 | * support removal of CommonJS power-assert declaration ([5925b38a](https://github.com/unassert-js/unassert/commit/5925b38a351596afab4de2f027fed9dc2ed82602)) 205 | 206 | 207 | ## [1.0.0](https://github.com/unassert-js/unassert/releases/tag/v1.0.0) (2015-05-27) 208 | 209 | 210 | The first release. 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2022 Takuto Wada, https://github.com/unassert-js/unassert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![unassert][unassert-banner]][unassert-url] 2 | 3 | Encourages [programming with assertions](https://en.wikipedia.org/wiki/Assertion_(software_development)) by providing tools to compile them away. 4 | 5 | [![Build Status][ci-image]][ci-url] 6 | [![NPM version][npm-image]][npm-url] 7 | [![Code Style][style-image]][style-url] 8 | [![License][license-image]][license-url] 9 | 10 | See: "[unassert - encourage reliable programming by writing assertions in production](http://www.slideshare.net/t_wada/unassert)" -- talk at NodeFest 2015, and "One more thing..." in talk at NodeFest 2016, titled "[From Library to Tool - power-assert as a General Purpose Assertion Enhancement Tool](https://speakerdeck.com/twada/from-library-to-tool-power-assert-as-a-general-purpose-assertion-enhancement-tool)" 11 | 12 | 13 | #### RELATED MODULES 14 | 15 | - [unassertify](https://github.com/unassert-js/unassertify): Browserify transform for unassert 16 | - [babel-plugin-unassert](https://github.com/unassert-js/babel-plugin-unassert): Babel plugin for unassert 17 | - [webpack-unassert-loader](https://github.com/unassert-js/webpack-unassert-loader): Webpack loader for unassert 18 | - [gulp-unassert](https://github.com/unassert-js/gulp-unassert): Gulp plugin for unassert 19 | - [unassert-cli](https://github.com/unassert-js/unassert-cli): CLI for unassert 20 | - [rollup-plugin-unassert](https://github.com/unassert-js/rollup-plugin-unassert): RollupJS plugin for unassert 21 | 22 | 23 | INSTALL 24 | --------------------------------------- 25 | 26 | ``` 27 | $ npm install --save-dev unassert 28 | ``` 29 | 30 | 31 | EXAMPLE 32 | --------------------------------------- 33 | 34 | For given `math.js` below, 35 | 36 | ```javascript 37 | const assert = require('node:assert'); 38 | 39 | function add (a, b) { 40 | assert(typeof a === 'number'); 41 | console.assert(!isNaN(a)); 42 | assert.equal(typeof b, 'number'); 43 | assert.ok(!isNaN(b)); 44 | return a + b; 45 | } 46 | ``` 47 | 48 | Apply `unassertAst` then generate modified code to console. 49 | 50 | via CJS code 51 | ```javascript 52 | const { unassertAst } = require('unassert'); 53 | const { parse } = require('acorn'); 54 | const { generate } = require('escodegen'); 55 | const { readFileSync } = require('node:fs'); 56 | const { join, dirname } = require('node:path'); 57 | 58 | const filepath = join(__dirname, 'math.js'); 59 | const ast = parse(readFileSync(filepath), { ecmaVersion: '2022' }); 60 | const modifiedAst = unassertAst(ast); 61 | 62 | console.log(generate(modifiedAst)); 63 | ``` 64 | 65 | or via ESM code 66 | ```javascript 67 | import { unassertAst } from 'unassert'; 68 | import { parse } from 'acorn'; 69 | import { generate } from 'escodegen'; 70 | import { readFileSync } from 'node:fs'; 71 | import { join, dirname } from 'node:path'; 72 | import { fileURLToPath } from 'node:url'; 73 | const __dirname = dirname(fileURLToPath(import.meta.url)); 74 | 75 | const filepath = join(__dirname, 'math.js'); 76 | const ast = parse(readFileSync(filepath), { ecmaVersion: '2022' }); 77 | const modifiedAst = unassertAst(ast); 78 | 79 | console.log(generate(modifiedAst)); 80 | ``` 81 | 82 | Then you will see assert calls disappear. 83 | 84 | ```javascript 85 | function add(a, b) { 86 | return a + b; 87 | } 88 | ``` 89 | 90 | 91 | API 92 | --------------------------------------- 93 | 94 | unassert package exports three functions. [`unassertAst`](https://github.com/unassert-js/unassert#const-modifiedast--unassertastast-options) is the main function. [`createVisitor`](https://github.com/unassert-js/unassert#const-visitor--createvisitoroptions) and [`defaultOptions`](https://github.com/unassert-js/unassert#const-options--defaultoptions) are for customization. 95 | 96 | 97 | ### const modifiedAst = unassertAst(ast, options) 98 | 99 | ```javascript 100 | const { unassertAst } = require('unassert') 101 | ``` 102 | 103 | ```javascript 104 | import { unassertAst } from 'unassert' 105 | ``` 106 | 107 | | return type | 108 | |:--------------------------------------------------------------| 109 | | `object` ([ECMAScript AST](https://github.com/estree/estree)) | 110 | 111 | Remove assertion calls from `ast` ([ECMAScript AST](https://github.com/estree/estree)). Default behaviour can be customized by `options`. `ast` is manipulated directly so returned `modifiedAst` will be the same instance of `ast`. 112 | 113 | #### options 114 | 115 | Object for configuration options. passed `options` is `Object.assign`ed with default options. If not passed, default options will be used. 116 | 117 | ##### options.modules 118 | 119 | Target module names for assertion call removal. 120 | 121 | For example, the default target modules are as follows. 122 | 123 | ```javascript 124 | { 125 | modules: [ 126 | 'assert', 127 | 'assert/strict', 128 | 'node:assert', 129 | 'node:assert/strict' 130 | ] 131 | ``` 132 | 133 | In this case, unassert will remove assert variable declarations such as, 134 | 135 | * `import assert from "assert"` 136 | * `import assert from "assert/strict"` 137 | * `import assert from "node:assert"` 138 | * `import assert from "node:assert/strict"` 139 | * `import * as assert from "assert"` 140 | * `import * as assert from "node:assert"` 141 | * `import * as assert from "assert/strict"` 142 | * `import * as assert from "node:assert/strict"` 143 | * `import { strict as assert } from "assert"` 144 | * `import { strict as assert } from "node:assert"` 145 | * `import { default as assert } from "assert"` 146 | * `import { default as assert } from "node:assert"` 147 | * `const assert = require("assert")` 148 | * `const assert = require("node:assert")` 149 | * `const assert = require("assert/strict")` 150 | * `const assert = require("node:assert/strict")` 151 | * `const assert = require("assert").strict` 152 | * `const assert = require("node:assert").strict` 153 | * `const { strict: assert } = require("assert")` 154 | * `const { strict: assert } = require("node:assert")` 155 | 156 | and assignments. 157 | 158 | * `assert = require("assert")` 159 | * `assert = require("node:assert")` 160 | * `assert = require("assert/strict")` 161 | * `assert = require("node:assert/strict")` 162 | * `assert = require("assert").strict` 163 | * `assert = require("node:assert").strict` 164 | 165 | In this default case, unassert will remove assertion calls such as, 166 | 167 | * `assert(value, [message])` 168 | * `assert.ok(value, [message])` 169 | * `assert.equal(actual, expected, [message])` 170 | * `assert.notEqual(actual, expected, [message])` 171 | * `assert.strictEqual(actual, expected, [message])` 172 | * `assert.notStrictEqual(actual, expected, [message])` 173 | * `assert.deepEqual(actual, expected, [message])` 174 | * `assert.notDeepEqual(actual, expected, [message])` 175 | * `assert.deepStrictEqual(actual, expected, [message])` 176 | * `assert.notDeepStrictEqual(actual, expected, [message])` 177 | * `assert.match(string, regexp[, message])` 178 | * `assert.doesNotMatch(string, regexp[, message])` 179 | * `assert.throws(block, [error], [message])` 180 | * `assert.doesNotThrow(block, [message])` 181 | * `await assert.rejects(asyncFn, [error], [message])` 182 | * `await assert.doesNotReject(asyncFn, [error], [message])` 183 | * `assert.fail([message])` 184 | * `assert.fail(actual, expected, message, operator)` 185 | * `assert.ifError(value)` 186 | 187 | in addition, unassert removes `console.assert` calls as well. 188 | 189 | * `console.assert(value, [message])` 190 | 191 | 192 | #### Auto Variable Tracking 193 | 194 | unassert automatically removes assertion calls based on their imported variable names. 195 | 196 | So if import declaration is as follows, 197 | 198 | * `import strictAssert, { ok, equal as eq } from 'node:assert/strict';` 199 | 200 | unassert removes all `strictAssert`, `ok`, `eq` calls. 201 | 202 | Please see [customization example](https://github.com/unassert-js/unassert#example-1) for more details. 203 | 204 | 205 | ### const visitor = createVisitor(options) 206 | 207 | ```javascript 208 | const { createVisitor } = require('unassert') 209 | ``` 210 | 211 | ```javascript 212 | import { createVisitor } from 'unassert' 213 | ``` 214 | 215 | | return type | 216 | |:----------------------------------------------------------------------------------| 217 | | `object` (visitor object for [estraverse](https://github.com/estools/estraverse)) | 218 | 219 | Create visitor object to be used with `estraverse.replace`. Visitor can be customized by `options`. 220 | 221 | 222 | ### const options = defaultOptions() 223 | 224 | ```javascript 225 | const { defaultOptions } = require('unassert') 226 | ``` 227 | 228 | ```javascript 229 | import { defaultOptions } from 'unassert' 230 | ``` 231 | 232 | Returns default options object for `unassertAst` and `createVisitor` function. In other words, returns 233 | 234 | ```javascript 235 | { 236 | modules: [ 237 | 'assert', 238 | 'assert/strict', 239 | 'node:assert', 240 | 'node:assert/strict' 241 | ] 242 | } 243 | ``` 244 | 245 | CUSTOMIZATION 246 | --------------------------------------- 247 | 248 | You can customize options such as target module names. 249 | 250 | ```javascript 251 | { 252 | modules: [ 253 | 'node:assert', 254 | 'node:assert/strict', 255 | 'power-assert', 256 | 'invariant', 257 | 'nanoassert', 258 | 'uvu/assert' 259 | ] 260 | } 261 | ``` 262 | 263 | ### example 264 | 265 | For given `custom.js` below, 266 | 267 | ```javascript 268 | import invariant from 'invariant'; 269 | import nassert from 'nanoassert'; 270 | import * as uvuassert from 'uvu/assert'; 271 | import { strict as powerAssert } from 'power-assert'; 272 | import { default as looseAssert } from 'node:assert'; 273 | import strictAssert, { ok, equal as eq } from 'node:assert/strict'; 274 | 275 | async function add (a, b) { 276 | strictAssert(!isNaN(a)); 277 | looseAssert(typeof a === 'number'); 278 | eq(typeof b, 'number'); 279 | ok(!isNaN(b)); 280 | powerAssert(typeof a === typeof b); 281 | 282 | nassert(!isNaN(a)); 283 | 284 | uvuassert.is(Math.sqrt(4), 2); 285 | uvuassert.is(Math.sqrt(144), 12); 286 | uvuassert.is(Math.sqrt(2), Math.SQRT2); 287 | 288 | invariant(someTruthyVal, 'This will not throw'); 289 | invariant(someFalseyVal, 'This will throw an error with this message'); 290 | 291 | await strictAssert.rejects(prms); 292 | await strictAssert.doesNotReject(prms2); 293 | 294 | return a + b; 295 | } 296 | ``` 297 | 298 | Apply `unassertAst` with customized options then generate modified code to console. 299 | 300 | ```javascript 301 | import { unassertAst } from 'unassert'; 302 | import { parse } from 'acorn'; 303 | import { generate } from 'escodegen'; 304 | import { readFileSync } from 'node:fs'; 305 | import { join, dirname } from 'node:path'; 306 | import { fileURLToPath } from 'node:url'; 307 | const __dirname = dirname(fileURLToPath(import.meta.url)); 308 | 309 | const filepath = join(__dirname, 'custom.js'); 310 | const ast = parse(readFileSync(filepath), { ecmaVersion: '2022' }); 311 | const modifiedAst = unassertAst(ast, { 312 | modules: [ 313 | 'node:assert', 314 | 'node:assert/strict', 315 | 'power-assert', 316 | 'invariant', 317 | 'nanoassert', 318 | 'uvu/assert' 319 | ] 320 | }); 321 | 322 | console.log(generate(modifiedAst)); 323 | ``` 324 | 325 | Then you will see all assert calls disappear. 326 | 327 | ```javascript 328 | async function add(a, b) { 329 | return a + b; 330 | } 331 | ``` 332 | 333 | 334 | OUR SUPPORT POLICY 335 | --------------------------------------- 336 | 337 | We support Node under maintenance. In other words, we stop supporting old Node version when [their maintenance ends](https://github.com/nodejs/LTS). 338 | 339 | This means that any other environment is not supported. 340 | 341 | NOTE: If unassert works in any of the unsupported environments, it is purely coincidental and has no bearing on future compatibility. Use at your own risk. 342 | 343 | 344 | AUTHOR 345 | --------------------------------------- 346 | * [Takuto Wada](https://github.com/twada) 347 | 348 | 349 | CONTRIBUTORS 350 | --------------------------------------- 351 | * [Renée Kooi](https://github.com/goto-bus-stop) 352 | * [Jordan Harband](https://github.com/ljharb) 353 | 354 | 355 | LICENSE 356 | --------------------------------------- 357 | Licensed under the [MIT](https://github.com/unassert-js/unassert/blob/master/LICENSE) license. 358 | 359 | 360 | [unassert-url]: https://github.com/unassert-js/unassert 361 | [unassert-banner]: https://raw.githubusercontent.com/unassert-js/unassert-js-logo/master/banner/banner-official-fullcolor.png 362 | 363 | [npm-url]: https://npmjs.org/package/unassert 364 | [npm-image]: https://badge.fury.io/js/unassert.svg 365 | 366 | [ci-image]: https://github.com/unassert-js/unassert/workflows/Node.js%20CI/badge.svg 367 | [ci-url]: https://github.com/unassert-js/unassert/actions?query=workflow%3A%22Node.js+CI%22 368 | 369 | [style-url]: https://github.com/standard/semistandard 370 | [style-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg 371 | 372 | [license-url]: https://github.com/unassert-js/unassert/blob/master/LICENSE 373 | [license-image]: https://img.shields.io/badge/license-MIT-brightgreen.svg 374 | -------------------------------------------------------------------------------- /bench.mjs: -------------------------------------------------------------------------------- 1 | import { runBenchmark } from '@twada/benchmark-commits'; 2 | import { parse } from 'acorn'; 3 | import { readFileSync } from 'node:fs'; 4 | import { join, dirname } from 'node:path'; 5 | import { fileURLToPath, pathToFileURL } from 'node:url'; 6 | import assert from 'node:assert/strict'; 7 | const __dirname = dirname(fileURLToPath(import.meta.url)); 8 | const targetCode = readFileSync(join(__dirname, 'node_modules', 'rimraf', 'rimraf.js')); 9 | 10 | const commits = [ 11 | 'v1.6.0', 12 | 'master' 13 | ]; 14 | 15 | runBenchmark(commits, async ({ suite, spec, dir }) => { 16 | let unassertAst; 17 | if (spec.git === 'v1.6.0') { 18 | unassertAst = (await import(pathToFileURL(`${dir}/index.js`))).default; 19 | } else if (spec.git === 'master') { 20 | unassertAst = (await import(pathToFileURL(`${dir}/src/index.mjs`))).unassertAst; 21 | } else { 22 | assert.fail('cannot be here'); 23 | } 24 | return () => { 25 | const ast = parse(targetCode, { ecmaVersion: '2022' }); 26 | const modifiedAst = unassertAst(ast); 27 | assert(modifiedAst); 28 | }; 29 | }).then((suite) => { 30 | console.log('FINISHED'); 31 | }); 32 | -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const estraverse = require('estraverse'); 4 | 5 | /** 6 | * unassert 7 | * Encourages programming with assertions by providing tools to compile them away. 8 | * 9 | * https://github.com/unassert-js/unassert 10 | * 11 | * Copyright (c) 2015-2022 Takuto Wada 12 | * Licensed under the MIT license. 13 | * https://github.com/unassert-js/unassert/blob/master/LICENSE 14 | */ 15 | 16 | function isLiteral (node) { 17 | return node && node.type === 'Literal'; 18 | } 19 | function isIdentifier (node) { 20 | return node && node.type === 'Identifier'; 21 | } 22 | function isObjectPattern (node) { 23 | return node && node.type === 'ObjectPattern'; 24 | } 25 | function isMemberExpression (node) { 26 | return node && node.type === 'MemberExpression'; 27 | } 28 | function isCallExpression (node) { 29 | return node && node.type === 'CallExpression'; 30 | } 31 | function isExpressionStatement (node) { 32 | return node && node.type === 'ExpressionStatement'; 33 | } 34 | function isIfStatement (node) { 35 | return node && node.type === 'IfStatement'; 36 | } 37 | function isImportDeclaration (node) { 38 | return node && node.type === 'ImportDeclaration'; 39 | } 40 | 41 | function isBodyOfNodeHavingNonBlockStatementAsBody (node, key) { 42 | if (!node) { 43 | return false; 44 | } 45 | if (key !== 'body') { 46 | return false; 47 | } 48 | switch (node.type) { 49 | case 'DoWhileStatement': 50 | case 'ForInStatement': 51 | case 'ForOfStatement': 52 | case 'ForStatement': 53 | case 'LabeledStatement': 54 | case 'WithStatement': 55 | case 'WhileStatement': 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | function isBodyOfIfStatement (node, key) { 62 | return isIfStatement(node) && (key === 'consequent' || key === 'alternate'); 63 | } 64 | 65 | function isNonBlockChildOfParentNode (currentNode, parentNode, key) { 66 | return isExpressionStatement(currentNode) && isCallExpression(currentNode.expression) && 67 | (isBodyOfIfStatement(parentNode, key) || isBodyOfNodeHavingNonBlockStatementAsBody(parentNode, key)); 68 | } 69 | 70 | function createVisitor (options) { 71 | const config = Object.assign(defaultOptions(), options); 72 | const targetModules = new Set(config.modules); 73 | const targetVariables = new Set(config.variables); 74 | 75 | function isAssertionModuleName (lit) { 76 | return isLiteral(lit) && targetModules.has(lit.value); 77 | } 78 | 79 | function isAssertionVariableName (id) { 80 | return isIdentifier(id) && targetVariables.has(id.name); 81 | } 82 | 83 | function isAssertionMethod (callee) { 84 | if (!isMemberExpression(callee)) { 85 | return false; 86 | } 87 | const obj = callee.object; 88 | if (isMemberExpression(obj)) { 89 | return isAssertionMethod(obj); 90 | } else { 91 | return isAssertionVariableName(obj); 92 | } 93 | } 94 | 95 | function isAssertionFunction (callee) { 96 | return isAssertionVariableName(callee); 97 | } 98 | 99 | function isConsoleAssert (callee) { 100 | if (!isMemberExpression(callee)) { 101 | return false; 102 | } 103 | const { object: obj, property: prop } = callee; 104 | return isIdentifier(obj) && obj.name === 'console' && 105 | isIdentifier(prop) && prop.name === 'assert'; 106 | } 107 | 108 | function registerIdentifierAsAssertionVariable (id) { 109 | if (isIdentifier(id)) { 110 | targetVariables.add(id.name); 111 | } 112 | } 113 | 114 | function handleDestructuredAssertionAssignment (objectPattern) { 115 | for (const { value } of objectPattern.properties) { 116 | registerIdentifierAsAssertionVariable(value); 117 | } 118 | } 119 | 120 | function handleImportSpecifiers (importDeclaration) { 121 | for (const { local } of importDeclaration.specifiers) { 122 | registerIdentifierAsAssertionVariable(local); 123 | } 124 | } 125 | 126 | function registerAssertionVariables (node) { 127 | if (isIdentifier(node)) { 128 | registerIdentifierAsAssertionVariable(node); 129 | } else if (isObjectPattern(node)) { 130 | handleDestructuredAssertionAssignment(node); 131 | } else if (isImportDeclaration(node)) { 132 | handleImportSpecifiers(node); 133 | } 134 | } 135 | 136 | function isRequireAssert (id, init) { 137 | if (!isCallExpression(init)) { 138 | return false; 139 | } 140 | const callee = init.callee; 141 | if (!isIdentifier(callee) || callee.name !== 'require') { 142 | return false; 143 | } 144 | const arg = init.arguments[0]; 145 | if (!isLiteral(arg) || !isAssertionModuleName(arg)) { 146 | return false; 147 | } 148 | return isIdentifier(id) || isObjectPattern(id); 149 | } 150 | 151 | function isRequireAssertDotStrict (id, init) { 152 | if (!isMemberExpression(init)) { 153 | return false; 154 | } 155 | if (!isRequireAssert(id, init.object)) { 156 | return false; 157 | } 158 | const prop = init.property; 159 | if (!isIdentifier(prop)) { 160 | return false; 161 | } 162 | return prop.name === 'strict'; 163 | } 164 | 165 | function isRemovalTargetRequire (id, init) { 166 | return isRequireAssert(id, init) || isRequireAssertDotStrict(id, init); 167 | } 168 | 169 | function isRemovalTargetAssertion (callee) { 170 | return isAssertionFunction(callee) || isAssertionMethod(callee) || isConsoleAssert(callee); 171 | } 172 | 173 | const nodeToRemove = new WeakSet(); 174 | 175 | return { 176 | enter: function (currentNode, parentNode) { 177 | switch (currentNode.type) { 178 | case 'ImportDeclaration': { 179 | const source = currentNode.source; 180 | if (!(isAssertionModuleName(source))) { 181 | return; 182 | } 183 | // remove current ImportDeclaration 184 | nodeToRemove.add(currentNode); 185 | this.skip(); 186 | // register local identifier(s) as assertion variable 187 | registerAssertionVariables(currentNode); 188 | break; 189 | } 190 | case 'VariableDeclarator': { 191 | if (isRemovalTargetRequire(currentNode.id, currentNode.init)) { 192 | if (parentNode.declarations.length === 1) { 193 | // remove parent VariableDeclaration 194 | nodeToRemove.add(parentNode); 195 | } else { 196 | // single var pattern 197 | // remove current VariableDeclarator 198 | nodeToRemove.add(currentNode); 199 | } 200 | this.skip(); 201 | // register local identifier(s) as assertion variable 202 | registerAssertionVariables(currentNode.id); 203 | } 204 | break; 205 | } 206 | case 'AssignmentExpression': { 207 | if (currentNode.operator !== '=') { 208 | return; 209 | } 210 | if (!isExpressionStatement(parentNode)) { 211 | return; 212 | } 213 | if (isRemovalTargetRequire(currentNode.left, currentNode.right)) { 214 | // remove parent ExpressionStatement 215 | nodeToRemove.add(parentNode); 216 | this.skip(); 217 | // register local identifier(s) as assertion variable 218 | registerAssertionVariables(currentNode.left); 219 | } 220 | break; 221 | } 222 | case 'CallExpression': { 223 | if (!isExpressionStatement(parentNode)) { 224 | return; 225 | } 226 | const callee = currentNode.callee; 227 | if (isRemovalTargetAssertion(callee)) { 228 | // remove parent ExpressionStatement 229 | nodeToRemove.add(parentNode); 230 | this.skip(); 231 | } 232 | break; 233 | } 234 | case 'AwaitExpression': { 235 | const childNode = currentNode.argument; 236 | if (isExpressionStatement(parentNode) && isCallExpression(childNode)) { 237 | const callee = childNode.callee; 238 | if (isRemovalTargetAssertion(callee)) { 239 | // remove parent ExpressionStatement 240 | nodeToRemove.add(parentNode); 241 | this.skip(); 242 | } 243 | } 244 | break; 245 | } 246 | } 247 | }, 248 | leave: function (currentNode, parentNode) { 249 | switch (currentNode.type) { 250 | case 'ImportDeclaration': 251 | case 'VariableDeclarator': 252 | case 'VariableDeclaration': 253 | case 'ExpressionStatement': 254 | break; 255 | default: 256 | return undefined; 257 | } 258 | if (nodeToRemove.has(currentNode)) { 259 | if (isExpressionStatement(currentNode)) { 260 | const path = this.path(); 261 | const key = path[path.length - 1]; 262 | if (isNonBlockChildOfParentNode(currentNode, parentNode, key)) { 263 | return { 264 | type: 'BlockStatement', 265 | body: [] 266 | }; 267 | } 268 | } 269 | this.remove(); 270 | } 271 | return undefined; 272 | } 273 | }; 274 | } 275 | 276 | function unassertAst (ast, options) { 277 | return estraverse.replace(ast, createVisitor(options)); 278 | } 279 | 280 | function defaultOptions () { 281 | return { 282 | modules: [ 283 | 'assert', 284 | 'assert/strict', 285 | 'node:assert', 286 | 'node:assert/strict' 287 | ] 288 | }; 289 | } 290 | 291 | exports.createVisitor = createVisitor; 292 | exports.defaultOptions = defaultOptions; 293 | exports.unassertAst = unassertAst; 294 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unassert", 3 | "description": "Encourages programming with assertions by providing tools to compile them away", 4 | "version": "2.0.2", 5 | "author": { 6 | "name": "Takuto Wada", 7 | "email": "takuto.wada@gmail.com", 8 | "url": "https://github.com/twada" 9 | }, 10 | "bugs": "https://github.com/unassert-js/unassert/issues", 11 | "contributors": [ 12 | { 13 | "name": "Renée Kooi", 14 | "url": "https://github.com/goto-bus-stop" 15 | }, 16 | { 17 | "name": "Jordan Harband", 18 | "url": "https://github.com/ljharb" 19 | } 20 | ], 21 | "dependencies": { 22 | "estraverse": "^5.0.0" 23 | }, 24 | "devDependencies": { 25 | "@twada/benchmark-commits": "^0.1.0", 26 | "acorn": "^8.0.0", 27 | "escodegen": "^2.0.0", 28 | "mocha": "^10.0.0", 29 | "rimraf": "^3.0.2", 30 | "rollup": "^2.77.0", 31 | "semistandard": "^16.0.0", 32 | "snazzy": "^9.0.0" 33 | }, 34 | "files": [ 35 | "README.md", 36 | "CHANGELOG.md", 37 | "LICENSE", 38 | "src", 39 | "dist" 40 | ], 41 | "main": "./dist/index.cjs", 42 | "exports": { 43 | ".": [ 44 | { 45 | "import": "./src/index.mjs", 46 | "require": "./dist/index.cjs", 47 | "default": "./dist/index.cjs" 48 | }, 49 | "./dist/index.cjs" 50 | ], 51 | "./package.json": "./package.json" 52 | }, 53 | "homepage": "https://github.com/unassert-js/unassert", 54 | "keywords": [ 55 | "DbC", 56 | "unassert", 57 | "assert", 58 | "assertion" 59 | ], 60 | "license": "MIT", 61 | "repository": { 62 | "type": "git", 63 | "url": "https://github.com/unassert-js/unassert.git" 64 | }, 65 | "scripts": { 66 | "preversion": "npm run build && npm test", 67 | "build": "rimraf dist && rollup -c", 68 | "lint": "semistandard --verbose src/*.mjs test/*.mjs | snazzy", 69 | "fmt": "semistandard --fix src/*.mjs test/*.mjs", 70 | "test": "npm run lint && mocha test" 71 | }, 72 | "semistandard": { 73 | "globals": [ 74 | "describe", 75 | "beforeEach", 76 | "it" 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | 3 | const srcDir = 'src'; 4 | const destDir = 'dist'; 5 | 6 | const srcFiles = await fs.readdir(new URL(`./${srcDir}`, import.meta.url)); 7 | 8 | export default { 9 | input: srcFiles.filter((file) => file.endsWith('.mjs')).map((x) => `${srcDir}/${x}`), 10 | output: { 11 | dir: destDir, 12 | format: 'cjs', 13 | entryFileNames: '[name].cjs', 14 | // create a module for each module in the input, instead of trying to chunk them together. 15 | preserveModules: true, 16 | // do not add `Object.defineProperty(exports, '__esModule', { value: true })` 17 | esModule: false, 18 | // use const instead of var when creating statements 19 | preferConst: true, 20 | // do not add _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 21 | interop: false, 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * unassert 3 | * Encourages programming with assertions by providing tools to compile them away. 4 | * 5 | * https://github.com/unassert-js/unassert 6 | * 7 | * Copyright (c) 2015-2022 Takuto Wada 8 | * Licensed under the MIT license. 9 | * https://github.com/unassert-js/unassert/blob/master/LICENSE 10 | */ 11 | import { replace } from 'estraverse'; 12 | 13 | function isLiteral (node) { 14 | return node && node.type === 'Literal'; 15 | } 16 | function isIdentifier (node) { 17 | return node && node.type === 'Identifier'; 18 | } 19 | function isObjectPattern (node) { 20 | return node && node.type === 'ObjectPattern'; 21 | } 22 | function isMemberExpression (node) { 23 | return node && node.type === 'MemberExpression'; 24 | } 25 | function isCallExpression (node) { 26 | return node && node.type === 'CallExpression'; 27 | } 28 | function isExpressionStatement (node) { 29 | return node && node.type === 'ExpressionStatement'; 30 | } 31 | function isIfStatement (node) { 32 | return node && node.type === 'IfStatement'; 33 | } 34 | function isImportDeclaration (node) { 35 | return node && node.type === 'ImportDeclaration'; 36 | } 37 | 38 | function isBodyOfNodeHavingNonBlockStatementAsBody (node, key) { 39 | if (!node) { 40 | return false; 41 | } 42 | if (key !== 'body') { 43 | return false; 44 | } 45 | switch (node.type) { 46 | case 'DoWhileStatement': 47 | case 'ForInStatement': 48 | case 'ForOfStatement': 49 | case 'ForStatement': 50 | case 'LabeledStatement': 51 | case 'WithStatement': 52 | case 'WhileStatement': 53 | return true; 54 | } 55 | return false; 56 | } 57 | 58 | function isBodyOfIfStatement (node, key) { 59 | return isIfStatement(node) && (key === 'consequent' || key === 'alternate'); 60 | } 61 | 62 | function isNonBlockChildOfParentNode (currentNode, parentNode, key) { 63 | return isExpressionStatement(currentNode) && isCallExpression(currentNode.expression) && 64 | (isBodyOfIfStatement(parentNode, key) || isBodyOfNodeHavingNonBlockStatementAsBody(parentNode, key)); 65 | } 66 | 67 | function createVisitor (options) { 68 | const config = Object.assign(defaultOptions(), options); 69 | const targetModules = new Set(config.modules); 70 | const targetVariables = new Set(config.variables); 71 | 72 | function isAssertionModuleName (lit) { 73 | return isLiteral(lit) && targetModules.has(lit.value); 74 | } 75 | 76 | function isAssertionVariableName (id) { 77 | return isIdentifier(id) && targetVariables.has(id.name); 78 | } 79 | 80 | function isAssertionMethod (callee) { 81 | if (!isMemberExpression(callee)) { 82 | return false; 83 | } 84 | const obj = callee.object; 85 | if (isMemberExpression(obj)) { 86 | return isAssertionMethod(obj); 87 | } else { 88 | return isAssertionVariableName(obj); 89 | } 90 | } 91 | 92 | function isAssertionFunction (callee) { 93 | return isAssertionVariableName(callee); 94 | } 95 | 96 | function isConsoleAssert (callee) { 97 | if (!isMemberExpression(callee)) { 98 | return false; 99 | } 100 | const { object: obj, property: prop } = callee; 101 | return isIdentifier(obj) && obj.name === 'console' && 102 | isIdentifier(prop) && prop.name === 'assert'; 103 | } 104 | 105 | function registerIdentifierAsAssertionVariable (id) { 106 | if (isIdentifier(id)) { 107 | targetVariables.add(id.name); 108 | } 109 | } 110 | 111 | function handleDestructuredAssertionAssignment (objectPattern) { 112 | for (const { value } of objectPattern.properties) { 113 | registerIdentifierAsAssertionVariable(value); 114 | } 115 | } 116 | 117 | function handleImportSpecifiers (importDeclaration) { 118 | for (const { local } of importDeclaration.specifiers) { 119 | registerIdentifierAsAssertionVariable(local); 120 | } 121 | } 122 | 123 | function registerAssertionVariables (node) { 124 | if (isIdentifier(node)) { 125 | registerIdentifierAsAssertionVariable(node); 126 | } else if (isObjectPattern(node)) { 127 | handleDestructuredAssertionAssignment(node); 128 | } else if (isImportDeclaration(node)) { 129 | handleImportSpecifiers(node); 130 | } 131 | } 132 | 133 | function isRequireAssert (id, init) { 134 | if (!isCallExpression(init)) { 135 | return false; 136 | } 137 | const callee = init.callee; 138 | if (!isIdentifier(callee) || callee.name !== 'require') { 139 | return false; 140 | } 141 | const arg = init.arguments[0]; 142 | if (!isLiteral(arg) || !isAssertionModuleName(arg)) { 143 | return false; 144 | } 145 | return isIdentifier(id) || isObjectPattern(id); 146 | } 147 | 148 | function isRequireAssertDotStrict (id, init) { 149 | if (!isMemberExpression(init)) { 150 | return false; 151 | } 152 | if (!isRequireAssert(id, init.object)) { 153 | return false; 154 | } 155 | const prop = init.property; 156 | if (!isIdentifier(prop)) { 157 | return false; 158 | } 159 | return prop.name === 'strict'; 160 | } 161 | 162 | function isRemovalTargetRequire (id, init) { 163 | return isRequireAssert(id, init) || isRequireAssertDotStrict(id, init); 164 | } 165 | 166 | function isRemovalTargetAssertion (callee) { 167 | return isAssertionFunction(callee) || isAssertionMethod(callee) || isConsoleAssert(callee); 168 | } 169 | 170 | const nodeToRemove = new WeakSet(); 171 | 172 | return { 173 | enter: function (currentNode, parentNode) { 174 | switch (currentNode.type) { 175 | case 'ImportDeclaration': { 176 | const source = currentNode.source; 177 | if (!(isAssertionModuleName(source))) { 178 | return; 179 | } 180 | // remove current ImportDeclaration 181 | nodeToRemove.add(currentNode); 182 | this.skip(); 183 | // register local identifier(s) as assertion variable 184 | registerAssertionVariables(currentNode); 185 | break; 186 | } 187 | case 'VariableDeclarator': { 188 | if (isRemovalTargetRequire(currentNode.id, currentNode.init)) { 189 | if (parentNode.declarations.length === 1) { 190 | // remove parent VariableDeclaration 191 | nodeToRemove.add(parentNode); 192 | } else { 193 | // single var pattern 194 | // remove current VariableDeclarator 195 | nodeToRemove.add(currentNode); 196 | } 197 | this.skip(); 198 | // register local identifier(s) as assertion variable 199 | registerAssertionVariables(currentNode.id); 200 | } 201 | break; 202 | } 203 | case 'AssignmentExpression': { 204 | if (currentNode.operator !== '=') { 205 | return; 206 | } 207 | if (!isExpressionStatement(parentNode)) { 208 | return; 209 | } 210 | if (isRemovalTargetRequire(currentNode.left, currentNode.right)) { 211 | // remove parent ExpressionStatement 212 | nodeToRemove.add(parentNode); 213 | this.skip(); 214 | // register local identifier(s) as assertion variable 215 | registerAssertionVariables(currentNode.left); 216 | } 217 | break; 218 | } 219 | case 'CallExpression': { 220 | if (!isExpressionStatement(parentNode)) { 221 | return; 222 | } 223 | const callee = currentNode.callee; 224 | if (isRemovalTargetAssertion(callee)) { 225 | // remove parent ExpressionStatement 226 | nodeToRemove.add(parentNode); 227 | this.skip(); 228 | } 229 | break; 230 | } 231 | case 'AwaitExpression': { 232 | const childNode = currentNode.argument; 233 | if (isExpressionStatement(parentNode) && isCallExpression(childNode)) { 234 | const callee = childNode.callee; 235 | if (isRemovalTargetAssertion(callee)) { 236 | // remove parent ExpressionStatement 237 | nodeToRemove.add(parentNode); 238 | this.skip(); 239 | } 240 | } 241 | break; 242 | } 243 | } 244 | }, 245 | leave: function (currentNode, parentNode) { 246 | switch (currentNode.type) { 247 | case 'ImportDeclaration': 248 | case 'VariableDeclarator': 249 | case 'VariableDeclaration': 250 | case 'ExpressionStatement': 251 | break; 252 | default: 253 | return undefined; 254 | } 255 | if (nodeToRemove.has(currentNode)) { 256 | if (isExpressionStatement(currentNode)) { 257 | const path = this.path(); 258 | const key = path[path.length - 1]; 259 | if (isNonBlockChildOfParentNode(currentNode, parentNode, key)) { 260 | return { 261 | type: 'BlockStatement', 262 | body: [] 263 | }; 264 | } 265 | } 266 | this.remove(); 267 | } 268 | return undefined; 269 | } 270 | }; 271 | } 272 | 273 | function unassertAst (ast, options) { 274 | return replace(ast, createVisitor(options)); 275 | } 276 | 277 | function defaultOptions () { 278 | return { 279 | modules: [ 280 | 'assert', 281 | 'assert/strict', 282 | 'node:assert', 283 | 'node:assert/strict' 284 | ] 285 | }; 286 | } 287 | 288 | export { 289 | unassertAst, 290 | defaultOptions, 291 | createVisitor 292 | }; 293 | -------------------------------------------------------------------------------- /test/fixtures/assignment/expected.js: -------------------------------------------------------------------------------- 1 | var assert; 2 | function add(a, b) { 3 | return a + b; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/assignment/fixture.js: -------------------------------------------------------------------------------- 1 | var assert; 2 | assert = require('assert/strict'); 3 | function add (a, b) { 4 | console.assert(typeof a === 'number'); 5 | assert(!isNaN(a)); 6 | assert.equal(typeof b, 'number'); 7 | assert.ok(!isNaN(b)); 8 | return a + b; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/assignment_singlevar/expected.js: -------------------------------------------------------------------------------- 1 | var add, assert; 2 | add = function (a, b) { 3 | return a + b; 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/assignment_singlevar/fixture.js: -------------------------------------------------------------------------------- 1 | var add, assert; 2 | assert = require('assert').strict; 3 | add = function (a, b) { 4 | console.assert(typeof a === 'number'); 5 | assert(!isNaN(a)); 6 | assert.equal(typeof b, 'number'); 7 | assert.ok(!isNaN(b)); 8 | return a + b; 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/cjs/expected.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function add(a, b) { 3 | return a + b; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/cjs/postlude.cjs: -------------------------------------------------------------------------------- 1 | function add (a, b) { 2 | assert(!isNaN(a)); 3 | assert.equal(typeof b, 'number'); 4 | assert.ok(!isNaN(b)); 5 | return a + b; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/cjs/prelude.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /test/fixtures/custom_modules_cjs/expected.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | try { 3 | } catch (err) { 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/custom_modules_cjs/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const htassert = require('http-assert'); 4 | const { ok, equal: eq, deepEqual: deq } = require('node:assert'); 5 | 6 | try { 7 | htassert(username == 'foo', 401, 'authentication failed'); 8 | } catch (err) { 9 | eq(err.status, 401); 10 | deq(err.message, 'authentication failed'); 11 | ok(err.expose); 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/custom_modules_mjs/expected.js: -------------------------------------------------------------------------------- 1 | async function add(a, b) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/custom_modules_mjs/fixture.js: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | import nassert from 'nanoassert'; 3 | import * as uvuassert from 'uvu/assert'; 4 | import { strict as powerAssert } from 'power-assert'; 5 | import { default as looseAssert } from 'node:assert'; 6 | import strictAssert, { ok, equal as eq } from 'node:assert/strict'; 7 | 8 | async function add (a, b) { 9 | strictAssert(!isNaN(a)); 10 | looseAssert(typeof a === 'number'); 11 | eq(typeof b, 'number'); 12 | ok(!isNaN(b)); 13 | powerAssert(typeof a === typeof b); 14 | 15 | nassert(!isNaN(a)); 16 | 17 | uvuassert.is(Math.sqrt(4), 2); 18 | uvuassert.is(Math.sqrt(144), 12); 19 | uvuassert.is(Math.sqrt(2), Math.SQRT2); 20 | 21 | invariant(someTruthyVal, 'This will not throw'); 22 | invariant(someFalseyVal, 'This will throw an error with this message'); 23 | 24 | await looseAssert.rejects(prms); 25 | await strictAssert.doesNotReject(prms2); 26 | 27 | return a + b; 28 | } 29 | -------------------------------------------------------------------------------- /test/fixtures/mjs/expected.mjs: -------------------------------------------------------------------------------- 1 | function add(a, b) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/mjs/postlude.mjs: -------------------------------------------------------------------------------- 1 | function add (a, b) { 2 | assert(!isNaN(a)); 3 | assert.equal(typeof b, 'number'); 4 | assert.ok(!isNaN(b)); 5 | return a + b; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/non_block_statement/expected.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function add(a, b) { 3 | if (!isNaN(a)) { 4 | } 5 | if (typeof b === 'number') { 6 | } 7 | if (typeof a === 'number') { 8 | } else if (typeof b === 'number') { 9 | } else { 10 | } 11 | ensure: { 12 | } 13 | for (const i of [ 14 | a, 15 | b 16 | ]) { 17 | } 18 | return a + b; 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/non_block_statement/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | function add (a, b) { 6 | if (!isNaN(a)) assert(0 < a); 7 | if (typeof b === 'number') { 8 | assert(0 < b); 9 | } 10 | 11 | if (typeof a === 'number') 12 | assert(0 < a); 13 | else if (typeof b === 'number') 14 | assert(0 < b); 15 | else 16 | assert(false); 17 | 18 | ensure: 19 | assert(0 < a); 20 | 21 | for (const i of [a, b]) 22 | assert (0 < i); 23 | 24 | return a + b; 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/not_an_expression_statement/expected.js: -------------------------------------------------------------------------------- 1 | function add(a, b) { 2 | if (isTrue(a)) { 3 | return null; 4 | } 5 | if (!isTrue(b)) { 6 | return null; 7 | } 8 | return a + b; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/not_an_expression_statement/fixture.js: -------------------------------------------------------------------------------- 1 | function add(a, b) { 2 | isTrue(!isNaN(a), 'message'); 3 | if (isTrue(a)) { 4 | return null; 5 | } 6 | console.assert(a); 7 | if (!isTrue(b)) { 8 | return null; 9 | } 10 | return a + b; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/variable_declarator_singlevar/expected.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var foo = 'FOO', bar = 'BAR'; 3 | function add(a, b) { 4 | return a + b; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/variable_declarator_singlevar/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var foo = 'FOO', 4 | assert = require('assert'), 5 | bar = 'BAR'; 6 | 7 | function add (a, b) { 8 | assert(!isNaN(a)); 9 | assert.equal(typeof b, 'number'); 10 | assert.ok(!isNaN(b)); 11 | return a + b; 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/various_assertion_methods/expected.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | async function add(a, b) { 3 | return a + b; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/various_assertion_methods/fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('node:assert'); 4 | 5 | async function add (a, b) { 6 | console.assert(typeof a === 'number'); 7 | 8 | assert(!isNaN(a)); 9 | assert(!isNaN(a), 'assertion message'); 10 | 11 | assert.ok(!isNaN(b)); 12 | assert.ok(!isNaN(b), 'assertion message'); 13 | 14 | assert.equal(typeof b, 'number'); 15 | assert.equal(typeof b, 'number', 'assertion message'); 16 | 17 | assert.strictEqual(typeof b, 'number'); 18 | assert.strictEqual(typeof b, 'number', 'assertion message'); 19 | 20 | assert.deepEqual(typeof b, 'number'); 21 | assert.deepEqual(typeof b, 'number', 'assertion message'); 22 | 23 | assert.deepStrictEqual(typeof b, 'number'); 24 | assert.deepStrictEqual(typeof b, 'number', 'assertion message'); 25 | 26 | assert.notEqual(typeof a, 'object'); 27 | assert.notEqual(typeof a, 'object', 'assertion message'); 28 | 29 | assert.notStrictEqual(typeof a, 'object'); 30 | assert.notStrictEqual(typeof a, 'object', 'assertion message'); 31 | 32 | assert.notDeepEqual(typeof a, 'object'); 33 | assert.notDeepEqual(typeof a, 'object', 'assertion message'); 34 | 35 | assert.notDeepStrictEqual(typeof a, 'object'); 36 | assert.notDeepStrictEqual(typeof a, 'object', 'assertion message'); 37 | 38 | assert.throws(function () { 39 | validate(a); 40 | }); 41 | assert.throws(function () { 42 | validate(a); 43 | }, 'assertion message'); 44 | assert.throws(function () { 45 | validate(a); 46 | }, Error, 'assertion message'); 47 | 48 | assert.doesNotThrow(function () { 49 | validate(b); 50 | }, 'assertion message'); 51 | assert.doesNotThrow(function () { 52 | validate(b); 53 | }); 54 | 55 | assert.ifError(a); 56 | assert.fail(a, b, 'assertion message', '=='); 57 | 58 | await assert.rejects(prms); 59 | await assert.doesNotReject(prms2); 60 | 61 | return a + b; 62 | } 63 | -------------------------------------------------------------------------------- /test/test.mjs: -------------------------------------------------------------------------------- 1 | import { unassertAst, createVisitor } from '../src/index.mjs'; 2 | import { strict as assert } from 'assert'; 3 | import { resolve, dirname } from 'path'; 4 | import { readFileSync, existsSync } from 'fs'; 5 | import { parse } from 'acorn'; 6 | import { generate } from 'escodegen'; 7 | import { replace } from 'estraverse'; 8 | import { fileURLToPath } from 'url'; 9 | const __dirname = dirname(fileURLToPath(import.meta.url)); 10 | 11 | function parseFixture (filepath) { 12 | return parse(readFileSync(filepath), { sourceType: 'module', ecmaVersion: '2022' }); 13 | } 14 | 15 | function createFixture ({ code, postlude, prelude }) { 16 | return parse(prelude + '\n' + code + '\n' + postlude, { sourceType: 'module', ecmaVersion: '2022' }); 17 | } 18 | 19 | function testWithGeneratedFixture (ext, code) { 20 | const preludeFilepath = resolve(__dirname, 'fixtures', ext, `prelude.${ext}`); 21 | const prelude = existsSync(preludeFilepath) ? readFileSync(preludeFilepath).toString() : ''; 22 | const postludeFilepath = resolve(__dirname, 'fixtures', ext, `postlude.${ext}`); 23 | const postlude = readFileSync(postludeFilepath).toString(); 24 | const expectedFilepath = resolve(__dirname, 'fixtures', ext, `expected.${ext}`); 25 | const expected = readFileSync(expectedFilepath).toString(); 26 | 27 | function deftest (name, fun) { 28 | it(`${code} : ${name}`, () => { 29 | const ast = createFixture({ code, postlude, prelude }); 30 | const modifiedAst = fun(ast); 31 | const actual = generate(modifiedAst); 32 | assert.equal(actual + '\n', expected); 33 | }); 34 | } 35 | deftest('unassertAst', (ast) => unassertAst(ast)); 36 | deftest('createVisitor', (ast) => replace(ast, createVisitor())); 37 | } 38 | 39 | function testESM (code) { 40 | testWithGeneratedFixture('mjs', code); 41 | } 42 | 43 | function testCJS (code) { 44 | testWithGeneratedFixture('cjs', code); 45 | } 46 | 47 | function testWithFixture (fixtureName, options) { 48 | const fixtureFilepath = resolve(__dirname, 'fixtures', fixtureName, 'fixture.js'); 49 | const expectedFilepath = resolve(__dirname, 'fixtures', fixtureName, 'expected.js'); 50 | const expected = readFileSync(expectedFilepath).toString(); 51 | 52 | function deftest (name, fun) { 53 | it(`${fixtureName} : ${name}`, () => { 54 | const ast = parseFixture(fixtureFilepath); 55 | const modifiedAst = fun(ast); 56 | const actual = generate(modifiedAst); 57 | assert.equal(actual + '\n', expected); 58 | }); 59 | } 60 | deftest('unassertAst', (ast) => unassertAst(ast, options)); 61 | deftest('createVisitor', (ast) => replace(ast, createVisitor(options))); 62 | } 63 | 64 | describe('with default options', () => { 65 | testWithFixture('various_assertion_methods'); 66 | testWithFixture('variable_declarator_singlevar'); 67 | testWithFixture('assignment'); 68 | testWithFixture('assignment_singlevar'); 69 | testWithFixture('non_block_statement'); 70 | 71 | describe('removal of ESM imports', () => { 72 | testESM("import assert from 'assert';"); 73 | testESM("import assert from 'node:assert';"); 74 | testESM("import assert from 'node:assert/strict';"); 75 | testESM("import assert from 'assert/strict';"); 76 | testESM("import * as assert from 'assert';"); 77 | testESM("import * as assert from 'node:assert';"); 78 | testESM("import * as assert from 'node:assert/strict';"); 79 | testESM("import * as assert from 'assert/strict';"); 80 | testESM("import { strict as assert } from 'assert';"); 81 | testESM("import { strict as assert } from 'node:assert';"); 82 | testESM("import { default as assert } from 'assert';"); 83 | testESM("import { default as assert } from 'node:assert';"); 84 | }); 85 | 86 | describe('removal of CJS requires', () => { 87 | testCJS("const assert = require('assert');"); 88 | testCJS("const assert = require('assert').strict;"); 89 | testCJS("const assert = require('assert/strict');"); 90 | testCJS("const assert = require('node:assert');"); 91 | testCJS("const assert = require('node:assert').strict;"); 92 | testCJS("const assert = require('node:assert/strict');"); 93 | testCJS("const { strict: assert } = require('assert');"); 94 | testCJS("const { strict: assert } = require('node:assert');"); 95 | }); 96 | }); 97 | 98 | describe('with custom options', () => { 99 | testWithFixture('custom_modules_cjs', { 100 | modules: [ 101 | 'http-assert', 102 | 'node:assert' 103 | ] 104 | }); 105 | 106 | testWithFixture('custom_modules_mjs', { 107 | modules: [ 108 | 'node:assert', 109 | 'node:assert/strict', 110 | 'power-assert', 111 | 'invariant', 112 | 'nanoassert', 113 | 'uvu/assert' 114 | ] 115 | }); 116 | 117 | testWithFixture('not_an_expression_statement', { 118 | variables: [ 119 | 'isTrue' 120 | ] 121 | }); 122 | }); 123 | --------------------------------------------------------------------------------