├── .gitignore ├── .prettierignore ├── .travis.yml ├── README.md ├── __testfixtures__ ├── .eslintrc.yml ├── extract-calls-fake.input.js ├── extract-calls-fake.output.js ├── migrate-to-v5.input.js └── migrate-to-v5.output.js ├── __tests__ ├── extract-calls-fake-test.js └── migrate-to-v5-test.js ├── extract-calls-fake.js ├── migrate-to-v5.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .vscode/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | __testfixtures__ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## sinon-codemod 2 | 3 | [![Build Status](https://img.shields.io/travis/hurrymaplelad/sinon-codemod.svg?style=flat-square)](https://travis-ci.org/hurrymaplelad/sinon-codemod) [![Code Style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 4 | 5 | This repository contains a collection of codemod scripts based for use with 6 | [JSCodeshift](https://github.com/facebook/jscodeshift) that help update Sinon APIs. 7 | 8 | ### Setup & Run 9 | 10 | * `npm install -g jscodeshift` 11 | * `git clone https://github.com/hurrymaplelad/sinon-codemod.git` or download a zip file 12 | from `https://github.com/hurrymaplelad/sinon-codemod/archive/master.zip` 13 | * Run `npm install` in the sinon-codemod directory 14 | * Alternatively, run [`yarn`](https://yarnpkg.com/) to install in the 15 | sinon-codemod directory for a reliable dependency resolution 16 | * `jscodeshift -t ` 17 | * Use the `-d` option for a dry-run and use `-p` to print the output 18 | for comparison 19 | 20 | ### Included Scripts 21 | 22 | #### `extract-calls-fake` 23 | 24 | Converts 3-argument calls to `sinon.stub(x,y,z)` into `sinon.stub(x,y).callsFake(z)`. 25 | 26 | ```sh 27 | jscodeshift -t sinon-codemod/extract-calls-fake.js 28 | ``` 29 | 30 | #### `migrate-to-v5` 31 | 32 | Removes `sandbox` variable declaration 33 | 34 | Removes `sinon.sandbox.create();` 35 | 36 | Replaces `sandbox.restore()` with `sinon.restore()` 37 | 38 | Replaces `sandbox.stub()` with `sinon.stub()` 39 | 40 | Replaces `sandbox.spy()` with `sinon.spy()` 41 | 42 | Replaces `sandbox.mock()` with `sinon.mock()` 43 | 44 | ```sh 45 | jscodeshift -t sinon-codemod/migrate-to-v5.js 46 | ``` -------------------------------------------------------------------------------- /__testfixtures__/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | # Skip autoformatting to allow testing a range of import formats 3 | "prettier/prettier": "off" 4 | "no-unused-vars": "off" 5 | -------------------------------------------------------------------------------- /__testfixtures__/extract-calls-fake.input.js: -------------------------------------------------------------------------------- 1 | const sinon = require("sinon"); 2 | 3 | const obj = {foo: () => {}}; 4 | 5 | // function 6 | sinon.stub(obj, 'foo', function () { 7 | return 'boom'; 8 | }).toString(); 9 | sinon.stub(obj, 'bar'); 10 | 11 | // Arrow Function 12 | sinon.stub(obj, 'foo', () => {}); 13 | 14 | // getter 15 | const fakeGetter = function () { 16 | return false; 17 | }; 18 | sinon.stub(obj, "prop", {get: fakeGetter}); 19 | sinon.stub(obj, 'prop', { 20 | get() { 21 | return false; 22 | } 23 | }); 24 | 25 | // setter 26 | function setterFn(val) { 27 | obj.example = val; 28 | } 29 | 30 | sinon.stub(obj, 'prop', {set: setterFn}); 31 | sinon.stub(obj, 'prop', { 32 | set(val) { 33 | obj.example = val; 34 | } 35 | }); 36 | 37 | function myFunc() {} 38 | sinon.stub(obj, 'someMethod', myFunc); 39 | 40 | // sandboxed variations 41 | this._sandbox.stub(obj, 'foo', () => {}); 42 | 43 | const someobj = {foo: {query: () => {}}}; 44 | let arg1, aFunction; 45 | const query = this._sandbox.stub(someobj.foo, 'query', aFunction.bind(null, 'then', arg1)); 46 | -------------------------------------------------------------------------------- /__testfixtures__/extract-calls-fake.output.js: -------------------------------------------------------------------------------- 1 | const sinon = require("sinon"); 2 | 3 | const obj = {foo: () => {}}; 4 | 5 | // function 6 | sinon.stub(obj, 'foo').callsFake(function () { 7 | return 'boom'; 8 | }).toString(); 9 | sinon.stub(obj, 'bar'); 10 | 11 | // Arrow Function 12 | sinon.stub(obj, 'foo').callsFake(() => {}); 13 | 14 | // getter 15 | const fakeGetter = function () { 16 | return false; 17 | }; 18 | sinon.stub(obj, "prop").get(fakeGetter); 19 | sinon.stub(obj, 'prop').get(function() { 20 | return false; 21 | }); 22 | 23 | // setter 24 | function setterFn(val) { 25 | obj.example = val; 26 | } 27 | 28 | sinon.stub(obj, 'prop').set(setterFn); 29 | sinon.stub(obj, 'prop').set(function(val) { 30 | obj.example = val; 31 | }); 32 | 33 | function myFunc() {} 34 | sinon.stub(obj, 'someMethod').callsFake(myFunc); 35 | 36 | // sandboxed variations 37 | this._sandbox.stub(obj, 'foo').callsFake(() => {}); 38 | 39 | const someobj = {foo: {query: () => {}}}; 40 | let arg1, aFunction; 41 | const query = this._sandbox.stub(someobj.foo, 'query').callsFake(aFunction.bind(null, 'then', arg1)); 42 | -------------------------------------------------------------------------------- /__testfixtures__/migrate-to-v5.input.js: -------------------------------------------------------------------------------- 1 | const sinon = require("sinon"); 2 | 3 | describe('dummy suite', () => { 4 | let sandbox = null; 5 | 6 | beforeEach(() => { 7 | sandbox = sinon.sandbox.create(); 8 | }); 9 | 10 | afterEach(() => { 11 | sandbox.restore(); 12 | }); 13 | 14 | it('dummy test', () => { 15 | sandbox.stub({}, 'dummyFunction').returns({}); 16 | sandbox.spy({}, 'dummyFunction'); 17 | sandbox.mock({}); 18 | }) 19 | }); -------------------------------------------------------------------------------- /__testfixtures__/migrate-to-v5.output.js: -------------------------------------------------------------------------------- 1 | const sinon = require("sinon"); 2 | 3 | describe('dummy suite', () => { 4 | beforeEach(() => {}); 5 | 6 | afterEach(() => { 7 | sinon.restore(); 8 | }); 9 | 10 | it('dummy test', () => { 11 | sinon.stub({}, 'dummyFunction').returns({}); 12 | sinon.spy({}, 'dummyFunction'); 13 | sinon.mock({}); 14 | }) 15 | }); -------------------------------------------------------------------------------- /__tests__/extract-calls-fake-test.js: -------------------------------------------------------------------------------- 1 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 2 | describe('extract-calls-fake', () => { 3 | defineTest(__dirname, 'extract-calls-fake'); 4 | }); 5 | -------------------------------------------------------------------------------- /__tests__/migrate-to-v5-test.js: -------------------------------------------------------------------------------- 1 | const defineTest = require('jscodeshift/dist/testUtils').defineTest; 2 | describe('migrate-to-v5', () => { 3 | defineTest(__dirname, 'migrate-to-v5'); 4 | }); 5 | -------------------------------------------------------------------------------- /extract-calls-fake.js: -------------------------------------------------------------------------------- 1 | function transformer(file, api) { 2 | const j = api.jscodeshift; 3 | const source = j(file.source); 4 | const replacer = path => { 5 | let callNode = path.node; 6 | // console.log('EXP', path.node); 7 | let fakeImplementationNode = callNode.arguments.pop(); 8 | // sinon.stub(obj, 'foo', function () { return 'boom'; }) 9 | // sinon.stub(obj, 'foo', () => {}) 10 | // sinon.stub(obj, 'foo', someFunc) 11 | if ( 12 | [ 13 | 'FunctionExpression', 14 | 'ArrowFunctionExpression', 15 | 'Identifier', 16 | 'CallExpression' 17 | ].includes(fakeImplementationNode.type) 18 | ) { 19 | return j.memberExpression( 20 | callNode, 21 | j.callExpression(j.identifier('callsFake'), [fakeImplementationNode]) 22 | ); 23 | } else if (fakeImplementationNode.type === 'ObjectExpression') { 24 | // getter/setter 25 | const properties = fakeImplementationNode.properties; 26 | if (!properties) { 27 | return; 28 | } 29 | // { get: fake, set: fake } pattern is not supported yet. 30 | if (properties.length > 1) { 31 | throw new Error('NOT support'); 32 | } 33 | const property = properties[0]; 34 | if (property.kind !== 'init' || !property.key) { 35 | return; 36 | } 37 | if (property.key.name !== 'get' && property.key.name !== 'set') { 38 | return; // this is not getter or setter 39 | } 40 | const isGetter = property.key.name === 'get'; 41 | // => stub(obj, "prop").get(fn) 42 | if (isGetter) { 43 | return j.memberExpression( 44 | callNode, 45 | j.callExpression(j.identifier('get'), [property.value]) 46 | ); 47 | } else { 48 | // => stub(obj, "prop").set(fn) 49 | return j.memberExpression( 50 | callNode, 51 | j.callExpression(j.identifier('set'), [property.value]) 52 | ); 53 | } 54 | } 55 | }; 56 | 57 | source 58 | .find(j.CallExpression, { 59 | callee: { 60 | object: { 61 | name: 'sinon' 62 | }, 63 | property: { 64 | name: 'stub' 65 | } 66 | }, 67 | arguments: { 68 | length: 3 69 | } 70 | }) 71 | .replaceWith(replacer); 72 | 73 | return source 74 | .find(j.CallExpression, { 75 | callee: { 76 | type: 'MemberExpression', 77 | object: { 78 | type: 'MemberExpression', 79 | object: { 80 | type: 'ThisExpression' 81 | }, 82 | property: { 83 | type: 'Identifier', 84 | name: '_sandbox' 85 | } 86 | }, 87 | property: { 88 | name: 'stub' 89 | } 90 | }, 91 | arguments: { 92 | length: 3 93 | } 94 | }) 95 | .replaceWith(replacer) 96 | .toSource(); 97 | } 98 | 99 | module.exports = transformer; 100 | -------------------------------------------------------------------------------- /migrate-to-v5.js: -------------------------------------------------------------------------------- 1 | function transformer(file, api) { 2 | const j = api.jscodeshift; 3 | const source = j(file.source); 4 | 5 | const replacer = path => { 6 | path.value.callee.object.name = 'sinon'; 7 | return path.value; 8 | }; 9 | 10 | /* 11 | let sandbox = null; 12 | 13 | -> 14 | 15 | */ 16 | source 17 | .find(j.VariableDeclaration, {declarations: [{id: {name: 'sandbox'}}]}) 18 | .remove(); 19 | 20 | /* 21 | beforeEach(() => { 22 | sandbox = sinon.sandbox.create(); 23 | }); 24 | 25 | -> 26 | 27 | beforeEach(() => {}); 28 | */ 29 | source 30 | .find(j.AssignmentExpression, { 31 | right: { 32 | type: 'CallExpression', 33 | callee: { 34 | type: 'MemberExpression', 35 | object: { 36 | type: 'MemberExpression', 37 | object: { 38 | name: 'sinon' 39 | }, 40 | property: { 41 | name: 'sandbox' 42 | } 43 | } 44 | } 45 | } 46 | }) 47 | .remove(); 48 | 49 | /* 50 | sandbox.stub({}, 'dummyFunction').returns({}); 51 | 52 | -> 53 | 54 | sinon.stub({}, 'dummyFunction').returns({}); 55 | */ 56 | source 57 | .find(j.CallExpression, { 58 | callee: { 59 | type: 'MemberExpression', 60 | object: { 61 | name: 'sandbox' 62 | }, 63 | property: { 64 | name: 'stub' 65 | } 66 | } 67 | }) 68 | .replaceWith(replacer); 69 | 70 | /* 71 | sandbox.spy({}, 'dummyFunction'); 72 | 73 | -> 74 | 75 | sinon.spy({}, 'dummyFunction'); 76 | */ 77 | source 78 | .find(j.CallExpression, { 79 | callee: { 80 | type: 'MemberExpression', 81 | object: { 82 | name: 'sandbox' 83 | }, 84 | property: { 85 | name: 'spy' 86 | } 87 | } 88 | }) 89 | .replaceWith(replacer); 90 | 91 | /* 92 | sandbox.mock({})); 93 | 94 | -> 95 | 96 | sinon.mock({}); 97 | */ 98 | source 99 | .find(j.CallExpression, { 100 | callee: { 101 | type: 'MemberExpression', 102 | object: { 103 | name: 'sandbox' 104 | }, 105 | property: { 106 | name: 'mock' 107 | } 108 | } 109 | }) 110 | .replaceWith(replacer); 111 | 112 | /* 113 | sandbox.restore(); 114 | 115 | -> 116 | 117 | sinon.restore(); 118 | */ 119 | source 120 | .find(j.CallExpression, { 121 | callee: { 122 | type: 'MemberExpression', 123 | object: { 124 | name: 'sandbox' 125 | }, 126 | property: { 127 | name: 'restore' 128 | } 129 | } 130 | }) 131 | .replaceWith(replacer); 132 | 133 | return source.toSource(); 134 | } 135 | 136 | module.exports = transformer; 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sinon-codemod", 3 | "version": "2.0.0", 4 | "description": "Sinon codemod scripts for JSCodeshift", 5 | "repository": { 6 | "url": "git@github.com:hurrymaplelad/sinon-codemod.git", 7 | "type": "git" 8 | }, 9 | "author": "Adam Hull ", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "eslint": "^4.18.2", 13 | "eslint-config-prettier": "^2.9.0", 14 | "eslint-plugin-prettier": "^2.6.0", 15 | "jest": "^17.0.3", 16 | "prettier": "^1.11.1" 17 | }, 18 | "scripts": { 19 | "test": "yarn run lint && jest", 20 | "fix": "yarn run eslint --fix & yarn run prettier --write & wait", 21 | "lint": "yarn run eslint & yarn run prettier -l & wait", 22 | "prettier": "prettier README.md package.json", 23 | "eslint": "eslint ." 24 | }, 25 | "prettier": { 26 | "singleQuote": true, 27 | "bracketSpacing": false 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "eslint:recommended", 32 | "plugin:prettier/recommended" 33 | ], 34 | "env": { 35 | "node": true, 36 | "es6": true, 37 | "mocha": true 38 | }, 39 | "parserOptions": { 40 | "ecmaVersion": 2017 41 | } 42 | }, 43 | "dependencies": { 44 | "jscodeshift": "^0.3.30" 45 | } 46 | } 47 | --------------------------------------------------------------------------------