├── .npmignore ├── index.js ├── README.md ├── .github └── workflows │ └── ci.yml ├── package.json ├── .gitignore ├── LICENSE ├── rules └── no-useless-assign.js └── tests └── no-useless-assign.js /.npmignore: -------------------------------------------------------------------------------- 1 | # Everything 2 | * 3 | 4 | !index.js 5 | !rules/* 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent useless assignment. 3 | * @author Erik Desjardins 4 | * @copyright 2016 Erik Desjardins. All rights reserved. 5 | * See LICENSE file in root directory for full license. 6 | */ 7 | 'use strict'; 8 | 9 | module.exports = { 10 | rules: { 11 | 'no-useless-assign': require('./rules/no-useless-assign') 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-no-useless-assign 2 | 3 | Prevent useless assignment of the form: 4 | 5 | ```js 6 | var foo = bar; 7 | return foo; 8 | ``` 9 | 10 | ```js 11 | var foo; 12 | // ... 13 | foo = bar; 14 | return foo; 15 | ``` 16 | 17 | ## Usage 18 | 19 | `npm i --save-dev eslint-plugin-no-useless-assign` 20 | 21 | ```json 22 | { 23 | "plugins": [ 24 | "no-useless-assign" 25 | ], 26 | "rules": { 27 | "no-useless-assign/no-useless-assign": 2 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: '12.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | - run: npm install 21 | - run: npm test 22 | - run: npm publish 23 | if: startsWith(github.ref, 'refs/tags/') 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-no-useless-assign", 3 | "version": "1.0.3", 4 | "description": "Prevent useless assignment.", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin", 8 | "eslint-plugin" 9 | ], 10 | "author": "Erik Desjardins", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/erikdesjardins/eslint-plugin-no-useless-assign.git" 14 | }, 15 | "main": "index.js", 16 | "scripts": { 17 | "test": "mocha ./tests/*.js" 18 | }, 19 | "peerDependencies": { 20 | "eslint": ">=1.0.0" 21 | }, 22 | "devDependencies": { 23 | "eslint": "^6.6.0", 24 | "mocha": "^6.2.2" 25 | }, 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erik Desjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rules/no-useless-assign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent useless assignment. 3 | * @author Erik Desjardins 4 | * @copyright 2016 Erik Desjardins. All rights reserved. 5 | * See LICENSE file in root directory for full license. 6 | */ 7 | 'use strict'; 8 | 9 | module.exports = function(context) { 10 | var scopes = []; 11 | 12 | function enterScope(node) { 13 | scopes.push({}); 14 | // for params 15 | addDeclaredVariables(node); 16 | } 17 | 18 | function exitScope() { 19 | scopes.pop(); 20 | } 21 | 22 | function addDeclaredVariables(node) { 23 | var thisScope = scopes[scopes.length - 1]; 24 | context.getDeclaredVariables(node).forEach(function(varDecl) { 25 | thisScope[varDecl.name] = true; 26 | }); 27 | } 28 | 29 | function nodeBefore(node) { 30 | var parent = node.parent; 31 | var siblings; 32 | 33 | if (parent.type === 'SwitchCase') { 34 | siblings = parent.consequent; 35 | } else { // BlockStatement 36 | siblings = parent.body; 37 | } 38 | 39 | if (!siblings) { 40 | return null; 41 | } 42 | 43 | for (var i = 0; i < siblings.length - 1; ++i) { 44 | if (siblings[i + 1] === node) { 45 | return siblings[i]; 46 | } 47 | } 48 | return null; 49 | } 50 | 51 | function checkForRedundantVar(declar, name) { 52 | var lastVar = declar.declarations[declar.declarations.length - 1]; 53 | 54 | if (lastVar && lastVar.id.name === name) { 55 | context.report({ 56 | node: lastVar, 57 | message: 'Redundant variable.' 58 | }); 59 | } 60 | } 61 | 62 | function checkForUselessAssignment(assignment, name) { 63 | var left = assignment.expression.left; 64 | 65 | if (left.type !== 'Identifier') { 66 | return; 67 | } 68 | 69 | if (left.name !== name) { 70 | return; 71 | } 72 | 73 | if (!scopes[scopes.length - 1][name]) { 74 | // variable not declared in scope 75 | return; 76 | } 77 | 78 | context.report({ 79 | node: left, 80 | message: 'Useless assignment.' 81 | }); 82 | } 83 | 84 | function checkReturnStatement(node) { 85 | if (!node.argument) { 86 | return; 87 | } 88 | 89 | if (node.argument.type !== 'Identifier') { 90 | return; 91 | } 92 | 93 | var name = node.argument.name; 94 | var prevNode = nodeBefore(node); 95 | 96 | if (!prevNode) { 97 | return; 98 | } 99 | 100 | if (prevNode.type === 'VariableDeclaration') { 101 | checkForRedundantVar(prevNode, name); 102 | } else if ( 103 | prevNode.type === 'ExpressionStatement' && 104 | prevNode.expression.type === 'AssignmentExpression' && 105 | prevNode.expression.operator === '=' 106 | ) { 107 | checkForUselessAssignment(prevNode, name); 108 | } 109 | } 110 | 111 | return { 112 | Program: enterScope, 113 | FunctionDeclaration: enterScope, 114 | FunctionExpression: enterScope, 115 | ArrowFunctionExpression: enterScope, 116 | 117 | 'Program:exit': exitScope, 118 | 'FunctionDeclaration:exit': exitScope, 119 | 'FunctionExpression:exit': exitScope, 120 | 'ArrowFunctionExpression:exit': exitScope, 121 | 122 | VariableDeclaration: addDeclaredVariables, 123 | 124 | ReturnStatement: checkReturnStatement 125 | }; 126 | }; 127 | -------------------------------------------------------------------------------- /tests/no-useless-assign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prevent useless assignment. 3 | * @author Erik Desjardins 4 | * @copyright 2016 Erik Desjardins. All rights reserved. 5 | * See LICENSE file in root directory for full license. 6 | */ 7 | 'use strict'; 8 | 9 | var rule = require('../rules/no-useless-assign'); 10 | var RuleTester = require("eslint").RuleTester; 11 | 12 | var varMessage = 'Redundant variable.'; 13 | var assignMessage = 'Useless assignment.'; 14 | 15 | var ruleTester = new RuleTester(); 16 | ruleTester.run('no-useless-assign', rule, { 17 | valid: [ 18 | // return for control flow 19 | '(function() { return; });', 20 | // return for control flow within shorthand if 21 | '(function() { if (foo) return; else return; });', 22 | // constant 23 | '(function() { return 5; });', 24 | // variable 25 | '(function() { return foo; });', 26 | // variable within shorthand if 27 | '(function() { if (foo) return foo; else return foo; });', 28 | // declared variable within shorthand if 29 | '(function() { var foo; if (foo) return foo; });', 30 | // function invocation 31 | '(function() { return foo.bar(); });', 32 | // property of preceding variable 33 | '(function() { var foo; return foo.bar; });', 34 | // function invocation of preceding variable 35 | '(function() { var foo; return foo(); });', 36 | // function invocation on preceding variable 37 | '(function() { var foo; return foo.bar() });', 38 | // expression involving preceding variable 39 | '(function() { var foo; return foo + bar; });', 40 | // function invocation with preceding variable as argument 41 | '(function() { var foo; return bar(foo); });', 42 | // switch case 43 | '(function() { switch (foo) { case bar: return baz; } });', 44 | // assignment to global 45 | '(function() { foo = bar; return foo; });', 46 | // assignment to global, function declaration 47 | 'function a() { foo = bar; return foo; }', 48 | // assignment to global, arrow function 49 | { code: '(() => { foo = bar; return foo; });', parserOptions: { ecmaVersion: 2018 } }, 50 | // assignment of out-of-scope var 51 | '(function() { var foo; (function() { foo = bar; return foo; }); });', 52 | // assignment of out-of-scope var, function declaration 53 | 'function a() { var foo; function b() { foo = bar; return foo; } }', 54 | // assignment of out-of-scope var, arrow function 55 | { code: '(function() { var foo; (() => { foo = bar; return foo; }); });', parserOptions: { ecmaVersion: 2018 } }, 56 | // assignment of out-of-scope let 57 | { code: '(function() { let foo; (function() { foo = bar; return foo; }); });', parserOptions: { ecmaVersion: 2018 } }, 58 | // assignment of out-of-scope let, function declaration 59 | { code: 'function a() { let foo; function b() { foo = bar; return foo; } }', parserOptions: { ecmaVersion: 2018 } }, 60 | // assignment of out-of-scope let, arrow function 61 | { code: '(function() { let foo; (() => { foo = bar; return foo; }); });', parserOptions: { ecmaVersion: 2018 } }, 62 | // not the last var before return 63 | '(function() { var foo, bar; return foo; });', 64 | // not the last var before return, switch case 65 | '(function() { switch (foo) { case baz: var foo, bar; return foo; } });', 66 | // not the last let before return 67 | { code: '(function() { let foo, bar; return foo; });', parserOptions: { ecmaVersion: 2018 } }, 68 | // not the last const before return 69 | { code: '(function() { const foo = 1, bar = 2; return foo; });', parserOptions: { ecmaVersion: 2018 } }, 70 | // multiple var declarations 71 | '(function() { var foo; var bar; return foo; });', 72 | // mixed var declarations 73 | { code: '(function() { const foo = 1; let bar; return foo; });', parserOptions: { ecmaVersion: 2018 } }, 74 | // global, many statements deep 75 | '(function() { if (foo) {} else { if (foo) with (foo) { for (foo; foo; foo) { while (foo) { do { foo = bar; return foo; } while (foo); } } } } });', 76 | // global, within try-catch 77 | '(function() { try { foo = bar; return foo; } catch (e) { foo = bar; return foo; } });', 78 | // shorthand plus-equals, etc. 79 | '(function() { var foo; foo += 1; return foo; });', 80 | // _just_ out-of-scope 81 | '(function() { var foo; function a() { var bar; function b() { bar = baz; return bar; } foo = baz; return foo; } });', 82 | // params out-of-scope 83 | '(function(foo) { (function() { foo = bar; return foo; }); });', 84 | // arrow function params out-of-scope 85 | { code: '(foo => { (() => { foo = bar; return foo; }); });', parserOptions: { ecmaVersion: 2018 } }, 86 | // generator params out-of-scope 87 | { code: '(function*(foo) { (function*() { foo = bar; return foo; }); });', parserOptions: { ecmaVersion: 2018 } }, 88 | // class method params out-of-scope 89 | { code: '(class { foo(bar) { (function() { bar = baz; return bar; }); } });', parserOptions: { ecmaVersion: 2018 } }, 90 | // object method params out-of-scope 91 | { code: '({ foo(bar) { (function() { bar = baz; return bar; }); } });', parserOptions: { ecmaVersion: 2018 } }, 92 | ], 93 | invalid: [ 94 | // useless var 95 | { 96 | code: '(function() { var foo; return foo; });', 97 | errors: [{ 98 | message: varMessage, 99 | type: 'VariableDeclarator', 100 | line: 1, 101 | column: 19 102 | }] 103 | }, 104 | // useless var, last in list 105 | { 106 | code: '(function() { var bar, foo; return foo; });', 107 | errors: [{ 108 | message: varMessage, 109 | type: 'VariableDeclarator', 110 | line: 1, 111 | column: 24 112 | }] 113 | }, 114 | // useless initialized var, last in list 115 | { 116 | code: '(function() { var bar, foo = baz; return foo; });', 117 | errors: [{ 118 | message: varMessage, 119 | type: 'VariableDeclarator', 120 | line: 1, 121 | column: 24 122 | }] 123 | }, 124 | // useless var, within block 125 | { 126 | code: '(function() { if (bar) { var foo; return foo; } });', 127 | errors: [{ 128 | message: varMessage, 129 | type: 'VariableDeclarator', 130 | line: 1, 131 | column: 30 132 | }] 133 | }, 134 | // useless var, within switch case 135 | { 136 | code: '(function() { switch (foo) { case baz: var foo; return foo; } });', 137 | errors: [{ 138 | message: varMessage, 139 | type: 'VariableDeclarator', 140 | line: 1, 141 | column: 44 142 | }] 143 | }, 144 | // useless let 145 | { 146 | code: '(function() { let foo; return foo; });', 147 | parserOptions: { ecmaVersion: 2018 }, 148 | errors: [{ 149 | message: varMessage, 150 | type: 'VariableDeclarator', 151 | line: 1, 152 | column: 19 153 | }] 154 | }, 155 | // useless let, within block 156 | { 157 | code: '(function() { if (bar) { let foo; return foo; } });', 158 | parserOptions: { ecmaVersion: 2018 }, 159 | errors: [{ 160 | message: varMessage, 161 | type: 'VariableDeclarator', 162 | line: 1, 163 | column: 30 164 | }] 165 | }, 166 | // useless const 167 | { 168 | code: '(function() { const foo = bar; return foo; });', 169 | parserOptions: { ecmaVersion: 2018 }, 170 | errors: [{ 171 | message: varMessage, 172 | type: 'VariableDeclarator', 173 | line: 1, 174 | column: 21 175 | }] 176 | }, 177 | // useless const, within block 178 | { 179 | code: '(function() { if (bar) { const foo = bar; return foo; } });', 180 | parserOptions: { ecmaVersion: 2018 }, 181 | errors: [{ 182 | message: varMessage, 183 | type: 'VariableDeclarator', 184 | line: 1, 185 | column: 32 186 | }] 187 | }, 188 | // reassign to var in scope 189 | { 190 | code: '(function() { var foo; bar(); foo = baz; return foo; });', 191 | errors: [{ 192 | message: assignMessage, 193 | type: 'Identifier', 194 | line: 1, 195 | column: 31 196 | }] 197 | }, 198 | // reassign to var in scope, within block 199 | { 200 | code: '(function() { var foo; bar(); if (baz) { foo = baz; return foo; } });', 201 | errors: [{ 202 | message: assignMessage, 203 | type: 'Identifier', 204 | line: 1, 205 | column: 42 206 | }] 207 | }, 208 | // reassign to var in scope, within switch case 209 | { 210 | code: '(function() { var foo; switch (foo) { case baz: foo = bar; return foo; } });', 211 | errors: [{ 212 | message: assignMessage, 213 | type: 'Identifier', 214 | line: 1, 215 | column: 49 216 | }] 217 | }, 218 | // reassign to var in scope, many statements deep 219 | { 220 | code: '(function() { var foo; if (foo) {} else { if (foo) with (foo) { for (foo; foo; foo) { while (foo) { do { foo = bar; return foo; } while (foo); } } } } });', 221 | errors: [{ 222 | message: assignMessage, 223 | type: 'Identifier', 224 | line: 1, 225 | column: 106 226 | }] 227 | }, 228 | // reassign to var in scope, many statements deep (var halfway up) 229 | { 230 | code: '(function() { if (foo) {} else { if (foo) with (foo) { var foo; for (foo; foo; foo) { while (foo) { do { foo = bar; return foo; } while (foo); } } } } });', 231 | errors: [{ 232 | message: assignMessage, 233 | type: 'Identifier', 234 | line: 1, 235 | column: 106 236 | }] 237 | }, 238 | // reassign to var in scope, within try-catch 239 | { 240 | code: '(function() { var foo; try { foo = bar; return foo; } catch (e) { foo = bar; return foo; } });', 241 | errors: [{ 242 | message: assignMessage, 243 | type: 'Identifier', 244 | line: 1, 245 | column: 30 246 | }, { 247 | message: assignMessage, 248 | type: 'Identifier', 249 | line: 1, 250 | column: 67 251 | }] 252 | }, 253 | // reassign to var in scope, split across intermediate scope 254 | { 255 | code: '(function() { var foo; function a() { var bar; bar = baz; return bar; } foo = baz; return foo; });', 256 | errors: [{ 257 | message: assignMessage, 258 | type: 'Identifier', 259 | line: 1, 260 | column: 48 261 | }, { 262 | message: assignMessage, 263 | type: 'Identifier', 264 | line: 1, 265 | column: 73 266 | }] 267 | }, 268 | // reassign to let in scope 269 | { 270 | code: '(function() { let foo; bar(); foo = baz; return foo; });', 271 | parserOptions: { ecmaVersion: 2018 }, 272 | errors: [{ 273 | message: assignMessage, 274 | type: 'Identifier', 275 | line: 1, 276 | column: 31 277 | }] 278 | }, 279 | // reassign to let in scope, within block 280 | { 281 | code: '(function() { let foo; bar(); if (baz) { foo = baz; return foo; } });', 282 | parserOptions: { ecmaVersion: 2018 }, 283 | errors: [{ 284 | message: assignMessage, 285 | type: 'Identifier', 286 | line: 1, 287 | column: 42 288 | }] 289 | }, 290 | // shadowed var, let 291 | { 292 | code: '(function() { var foo; let bar; (function() { var foo; foo = 5; return foo; }); (function() { let bar; bar = 5; return bar; }); });', 293 | parserOptions: { ecmaVersion: 2018 }, 294 | errors: [{ 295 | message: assignMessage, 296 | type: 'Identifier', 297 | line: 1, 298 | column: 56 299 | }, { 300 | message: assignMessage, 301 | type: 'Identifier', 302 | line: 1, 303 | column: 104 304 | }] 305 | }, 306 | // reassign to params 307 | { 308 | code: '(function(foo) { foo = bar; return foo; });', 309 | parserOptions: { ecmaVersion: 2018 }, 310 | errors: [{ 311 | message: assignMessage, 312 | type: 'Identifier', 313 | line: 1, 314 | column: 18 315 | }] 316 | }, 317 | // reassign to params, shadowing var out-of-scope 318 | { 319 | code: '(function() { var foo; (function(foo) { foo = bar; return foo; }); });', 320 | errors: [{ 321 | message: assignMessage, 322 | type: 'Identifier', 323 | line: 1, 324 | column: 41 325 | }] 326 | }, 327 | // reassign to var, shadowing params out-of-scope 328 | { 329 | code: '(function(foo) { (function() { var foo; foo = bar; return foo; }); });', 330 | errors: [{ 331 | message: assignMessage, 332 | type: 'Identifier', 333 | line: 1, 334 | column: 41 335 | }] 336 | }, 337 | // reassign to arrow function params 338 | { 339 | code: '(foo => { foo = bar; return foo; });', 340 | parserOptions: { ecmaVersion: 2018 }, 341 | errors: [{ 342 | message: assignMessage, 343 | type: 'Identifier', 344 | line: 1, 345 | column: 11 346 | }] 347 | }, 348 | // reassign to generator params 349 | { 350 | code: '(function*(foo) { foo = bar; return foo; });', 351 | parserOptions: { ecmaVersion: 2018 }, 352 | errors: [{ 353 | message: assignMessage, 354 | type: 'Identifier', 355 | line: 1, 356 | column: 19 357 | }] 358 | }, 359 | // reassign to class method params 360 | { 361 | code: '(class { foo(bar) { bar = baz; return bar; } });', 362 | parserOptions: { ecmaVersion: 2018 }, 363 | errors: [{ 364 | message: assignMessage, 365 | type: 'Identifier', 366 | line: 1, 367 | column: 21 368 | }] 369 | }, 370 | // reassign to object method params 371 | { 372 | code: '({ foo(bar) { bar = baz; return bar; } });', 373 | parserOptions: { ecmaVersion: 2018 }, 374 | errors: [{ 375 | message: assignMessage, 376 | type: 'Identifier', 377 | line: 1, 378 | column: 15 379 | }] 380 | }, 381 | ] 382 | }); 383 | --------------------------------------------------------------------------------