From 45aa6a3ba3486f1b116c5daab6432d144e5ea574 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 10 Nov 2019 17:36:05 +0100 Subject: [PATCH] New: Add no-setter-return rule (fixes #12285) (#12346) --- docs/rules/no-setter-return.md | 101 ++++++ lib/rules/index.js | 1 + lib/rules/no-setter-return.js | 227 +++++++++++++ tests/lib/rules/no-setter-return.js | 510 ++++++++++++++++++++++++++++ tools/rule-types.json | 1 + 5 files changed, 840 insertions(+) create mode 100644 docs/rules/no-setter-return.md create mode 100644 lib/rules/no-setter-return.js create mode 100644 tests/lib/rules/no-setter-return.js diff --git a/docs/rules/no-setter-return.md b/docs/rules/no-setter-return.md new file mode 100644 index 00000000000..53089746edc --- /dev/null +++ b/docs/rules/no-setter-return.md @@ -0,0 +1,101 @@ +# Disallow returning values from setters (no-setter-return) + +Setters cannot return values. + +While returning a value from a setter does not produce an error, the returned value is being ignored. Therefore, returning a value from a setter is either unnecessary or a possible error, since the returned value cannot be used. + +## Rule Details + +This rule disallows returning values from setters and reports `return` statements in setter functions. + +Only `return` without a value is allowed, as it's a control flow statement. + +This rule checks setters in: + +* Object literals. +* Class declarations and class expressions. +* Property descriptors in `Object.create`, `Object.defineProperty`, `Object.defineProperties`, and `Reflect.defineProperty` methods of the global objects. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-setter-return: "error"*/ + +var foo = { + set a(value) { + this.val = value; + return value; + } +}; + +class Foo { + set a(value) { + this.val = value * 2; + return this.val; + } +} + +const Bar = class { + static set a(value) { + if (value < 0) { + this.val = 0; + return 0; + } + this.val = value; + } +}; + +Object.defineProperty(foo, "bar", { + set(value) { + if (value < 0) { + return false; + } + this.val = value; + } +}); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-setter-return: "error"*/ + +var foo = { + set a(value) { + this.val = value; + } +}; + +class Foo { + set a(value) { + this.val = value * 2; + } +} + +const Bar = class { + static set a(value) { + if (value < 0) { + this.val = 0; + return; + } + this.val = value; + } +}; + +Object.defineProperty(foo, "bar", { + set(value) { + if (value < 0) { + throw new Error("Negative value."); + } + this.val = value; + } +}); +``` + +## Further Reading + +* [MDN setter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + +## Related Rules + +* [getter-return](getter-return.md) diff --git a/lib/rules/index.js b/lib/rules/index.js index dbda93fb325..e014e67cba7 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -186,6 +186,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-self-assign": () => require("./no-self-assign"), "no-self-compare": () => require("./no-self-compare"), "no-sequences": () => require("./no-sequences"), + "no-setter-return": () => require("./no-setter-return"), "no-shadow": () => require("./no-shadow"), "no-shadow-restricted-names": () => require("./no-shadow-restricted-names"), "no-spaced-func": () => require("./no-spaced-func"), diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js new file mode 100644 index 00000000000..e0948696c34 --- /dev/null +++ b/lib/rules/no-setter-return.js @@ -0,0 +1,227 @@ +/** + * @fileoverview Rule to disallow returning values from setters + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); +const { findVariable } = require("eslint-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the given identifier node is a reference to a global variable. + * @param {ASTNode} node `Identifier` node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} True if the identifier is a reference to a global variable. + */ +function isGlobalReference(node, scope) { + const variable = findVariable(scope, node); + + return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; +} + +/** + * Determines whether the given node is an argument of the specified global method call, at the given `index` position. + * E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @param {string} objectName Name of the global object. + * @param {string} methodName Name of the method. + * @param {number} index The given position. + * @returns {boolean} `true` if the node is argument at the given position. + */ +function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) { + const parent = node.parent; + + return parent.type === "CallExpression" && + parent.arguments[index] === node && + parent.callee.type === "MemberExpression" && + astUtils.getStaticPropertyName(parent.callee) === methodName && + parent.callee.object.type === "Identifier" && + parent.callee.object.name === objectName && + isGlobalReference(parent.callee.object, scope); +} + +/** + * Determines whether the given node is used as a property descriptor. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} `true` if the node is a property descriptor. + */ +function isPropertyDescriptor(node, scope) { + if ( + isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) || + isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2) + ) { + return true; + } + + const parent = node.parent; + + if ( + parent.type === "Property" && + parent.value === node + ) { + const grandparent = parent.parent; + + if ( + grandparent.type === "ObjectExpression" && + ( + isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) || + isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1) + ) + ) { + return true; + } + } + + return false; +} + +/** + * Determines whether the given function node is used as a setter function. + * @param {ASTNode} node The node to check. + * @param {Scope} scope Scope to which the node belongs. + * @returns {boolean} `true` if the node is a setter. + */ +function isSetter(node, scope) { + const parent = node.parent; + + if ( + parent.kind === "set" && + parent.value === node + ) { + + // Setter in an object literal or in a class + return true; + } + + if ( + parent.type === "Property" && + parent.value === node && + astUtils.getStaticPropertyName(parent) === "set" && + parent.parent.type === "ObjectExpression" && + isPropertyDescriptor(parent.parent, scope) + ) { + + // Setter in a property descriptor + return true; + } + + return false; +} + +/** + * Finds function's outer scope. + * @param {Scope} scope Function's own scope. + * @returns {Scope} Function's outer scope. + */ +function getOuterScope(scope) { + const upper = scope.upper; + + if (upper.type === "function-expression-name") { + return upper.upper; + } + + return upper; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow returning values from setters", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-setter-return" + }, + + schema: [], + + messages: { + returnsValue: "Setter cannot return a value." + } + }, + + create(context) { + let funcInfo = null; + + /** + * Creates and pushes to the stack a function info object for the given function node. + * @param {ASTNode} node The function node. + * @returns {void} + */ + function enterFunction(node) { + const outerScope = getOuterScope(context.getScope()); + + funcInfo = { + upper: funcInfo, + isSetter: isSetter(node, outerScope) + }; + } + + /** + * Pops the current function info object from the stack. + * @returns {void} + */ + function exitFunction() { + funcInfo = funcInfo.upper; + } + + /** + * Reports the given node. + * @param {ASTNode} node Node to report. + * @returns {void} + */ + function report(node) { + context.report({ node, messageId: "returnsValue" }); + } + + return { + + /* + * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid + * false positives, because a ReturnStatement node can belong to a function declaration inside a setter. + * + * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor, + * but that's out of scope for this rule. + */ + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression(node) { + enterFunction(node); + + if (funcInfo.isSetter && node.expression) { + + // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement. + report(node.body); + } + }, + + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + "ArrowFunctionExpression:exit": exitFunction, + + ReturnStatement(node) { + + // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`. + if (funcInfo && funcInfo.isSetter && node.argument) { + report(node); + } + } + }; + } +}; diff --git a/tests/lib/rules/no-setter-return.js b/tests/lib/rules/no-setter-return.js new file mode 100644 index 00000000000..8ba7daa777a --- /dev/null +++ b/tests/lib/rules/no-setter-return.js @@ -0,0 +1,510 @@ +/** + * @fileoverview Tests for the no-setter-return rule + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-setter-return"); +const { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Creates an error object. + * @param {number} [column] Reported column. + * @param {string} [type= "ReturnStatement"] Reported node type. + * @returns {Object} The error object. + */ +function error(column, type = "ReturnStatement") { + const errorObject = { + messageId: "returnsValue", + type + }; + + if (column) { + errorObject.column = column; + } + + return errorObject; +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); + +ruleTester.run("no-setter-return", rule, { + valid: [ + + //------------------------------------------------------------------------------ + // General + //------------------------------------------------------------------------------ + + // not a setter + "function foo() { return 1; }", + "function set(val) { return 1; }", + "var foo = function() { return 1; };", + "var foo = function set() { return 1; };", + "var set = function() { return 1; };", + "var set = function set(val) { return 1; };", + "var set = val => { return 1; };", + "var set = val => 1;", + + // setters do not have effect on other functions (test function info tracking) + "({ set a(val) { }}); function foo() { return 1; }", + "({ set a(val) { }}); (function () { return 1; });", + "({ set a(val) { }}); (() => { return 1; });", + "({ set a(val) { }}); (() => 1);", + + // does not report global return + { + code: "return 1;", + env: { node: true } + }, + { + code: "return 1;", + parserOptions: { ecmaFeatures: { globalReturn: true } } + }, + { + code: "return 1; function foo(){ return 1; } return 1;", + env: { node: true } + }, + { + code: "function foo(){} return 1; var bar = function*(){ return 1; }; return 1; var baz = () => {}; return 1;", + env: { node: true } + }, + + //------------------------------------------------------------------------------ + // Object literals and classes + //------------------------------------------------------------------------------ + + // return without a value is allowed + "({ set foo(val) { return; } })", + "({ set foo(val) { if (val) { return; } } })", + "class A { set foo(val) { return; } }", + "(class { set foo(val) { if (val) { return; } else { return; } return; } })", + "class A { set foo(val) { try {} catch(e) { return; } } }", + + // not a setter + "({ get foo() { return 1; } })", + "({ get set() { return 1; } })", + "({ set(val) { return 1; } })", + "({ set: function(val) { return 1; } })", + "({ foo: function set(val) { return 1; } })", + "({ set: function set(val) { return 1; } })", + "({ set: (val) => { return 1; } })", + "({ set: (val) => 1 })", + "set = { foo(val) { return 1; } };", + "class A { constructor(val) { return 1; } }", + "class set { constructor(val) { return 1; } }", + "class set { foo(val) { return 1; } }", + "var set = class { foo(val) { return 1; } }", + "(class set { foo(val) { return 1; } })", + "class A { get foo() { return val; } }", + "class A { get set() { return val; } }", + "class A { set(val) { return 1; } }", + "class A { static set(val) { return 1; } }", + "({ set: set = function set(val) { return 1; } } = {})", + "({ set: set = (val) => 1 } = {})", + + // not returning from the setter + "({ set foo(val) { function foo(val) { return 1; } } })", + "({ set foo(val) { var foo = function(val) { return 1; } } })", + "({ set foo(val) { var foo = (val) => { return 1; } } })", + "({ set foo(val) { var foo = (val) => 1; } })", + "({ set [function() { return 1; }](val) {} })", + "({ set [() => { return 1; }](val) {} })", + "({ set [() => 1](val) {} })", + "({ set foo(val = function() { return 1; }) {} })", + "({ set foo(val = v => 1) {} })", + "(class { set foo(val) { function foo(val) { return 1; } } })", + "(class { set foo(val) { var foo = function(val) { return 1; } } })", + "(class { set foo(val) { var foo = (val) => { return 1; } } })", + "(class { set foo(val) { var foo = (val) => 1; } })", + "(class { set [function() { return 1; }](val) {} })", + "(class { set [() => { return 1; }](val) {} })", + "(class { set [() => 1](val) {} })", + "(class { set foo(val = function() { return 1; }) {} })", + "(class { set foo(val = (v) => 1) {} })", + + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ + + // return without a value is allowed + "Object.defineProperty(foo, 'bar', { set(val) { return; } })", + { + code: "Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return; } } })", + env: { es6: true } + }, + "Object.defineProperties(foo, { bar: { set(val) { try { return; } catch(e){} } } })", + "Object.create(foo, { bar: { set: function(val) { return; } } })", + + // not a setter + "x = { set(val) { return 1; } }", + "x = { foo: { set(val) { return 1; } } }", + "Object.defineProperty(foo, 'bar', { value(val) { return 1; } })", + { + code: "Reflect.defineProperty(foo, 'bar', { value: function set(val) { return 1; } })", + env: { es6: true } + }, + "Object.defineProperties(foo, { bar: { [set](val) { return 1; } } })", + "Object.create(foo, { bar: { 'set ': function(val) { return 1; } } })", + "Object.defineProperty(foo, 'bar', { [`set `]: (val) => { return 1; } })", + { + code: "Reflect.defineProperty(foo, 'bar', { Set(val) { return 1; } })", + env: { es6: true } + }, + "Object.defineProperties(foo, { bar: { value: (val) => 1 } })", + "Object.create(foo, { set: { value: function(val) { return 1; } } })", + "Object.defineProperty(foo, 'bar', { baz(val) { return 1; } })", + { + code: "Reflect.defineProperty(foo, 'bar', { get(val) { return 1; } })", + env: { es6: true } + }, + "Object.create(foo, { set: function(val) { return 1; } })", + "Object.defineProperty(foo, { set: (val) => 1 })", + + // not returning from the setter + "Object.defineProperty(foo, 'bar', { set(val) { function foo() { return 1; } } })", + { + code: "Reflect.defineProperty(foo, 'bar', { set(val) { var foo = function() { return 1; } } })", + env: { es6: true } + }, + "Object.defineProperties(foo, { bar: { set(val) { () => { return 1 }; } } })", + "Object.create(foo, { bar: { set: (val) => { (val) => 1; } } })", + + // invalid index + "Object.defineProperty(foo, 'bar', 'baz', { set(val) { return 1; } })", + "Object.defineProperty(foo, { set(val) { return 1; } }, 'bar')", + "Object.defineProperty({ set(val) { return 1; } }, foo, 'bar')", + { + code: "Reflect.defineProperty(foo, 'bar', 'baz', { set(val) { return 1; } })", + env: { es6: true } + }, + { + code: "Reflect.defineProperty(foo, { set(val) { return 1; } }, 'bar')", + env: { es6: true } + }, + { + code: "Reflect.defineProperty({ set(val) { return 1; } }, foo, 'bar')", + env: { es6: true } + }, + "Object.defineProperties(foo, bar, { baz: { set(val) { return 1; } } })", + "Object.defineProperties({ bar: { set(val) { return 1; } } }, foo)", + "Object.create(foo, bar, { baz: { set(val) { return 1; } } })", + "Object.create({ bar: { set(val) { return 1; } } }, foo)", + + // not targeted method name + "Object.DefineProperty(foo, 'bar', { set(val) { return 1; } })", + { + code: "Reflect.DefineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } })", + env: { es6: true } + }, + "Object.DefineProperties(foo, { bar: { set(val) { try { return 1; } catch(e){} } } })", + "Object.Create(foo, { bar: { set: function(val) { return 1; } } })", + + // not targeted object name + "object.defineProperty(foo, 'bar', { set(val) { return 1; } })", + { + code: "reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } })", + env: { es6: true } + }, + { + code: "Reflect.defineProperties(foo, { bar: { set(val) { try { return 1; } catch(e){} } } })", + env: { es6: true } + }, + "object.create(foo, { bar: { set: function(val) { return 1; } } })", + + // global object doesn't exist + "Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } })", + "/* globals Object:off */ Object.defineProperty(foo, 'bar', { set(val) { return 1; } })", + { + code: "Object.defineProperties(foo, { bar: { set(val) { try { return 1; } catch(e){} } } })", + globals: { Object: "off" } + }, + + // global object is shadowed + "let Object; Object.defineProperty(foo, 'bar', { set(val) { return 1; } })", + { + code: "function f() { Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return 1; } } }); var Reflect;}", + env: { es6: true } + }, + "function f(Object) { Object.defineProperties(foo, { bar: { set(val) { try { return 1; } catch(e){} } } }) }", + "if (x) { const Object = getObject(); Object.create(foo, { bar: { set: function(val) { return 1; } } }) }", + "x = function Object() { Object.defineProperty(foo, 'bar', { set(val) { return 1; } }) }" + ], + + invalid: [ + + //------------------------------------------------------------------------------ + // Object literals and classes + //------------------------------------------------------------------------------ + + // full error test + { + code: "({ set a(val){ return val + 1; } })", + errors: [{ message: "Setter cannot return a value.", type: "ReturnStatement", column: 16, endColumn: 31 }] + }, + + // basic tests + { + code: "({ set a(val) { return 1; } })", + errors: [error()] + }, + { + code: "class A { set a(val) { return 1; } }", + errors: [error()] + }, + { + code: "class A { static set a(val) { return 1; } }", + errors: [error()] + }, + { + code: "(class { set a(val) { return 1; } })", + errors: [error()] + }, + + // any value + { + code: "({ set a(val) { return val; } })", + errors: [error()] + }, + { + code: "class A { set a(val) { return undefined; } }", + errors: [error()] + }, + { + code: "(class { set a(val) { return null; } })", + errors: [error()] + }, + { + code: "({ set a(val) { return x + y; } })", + errors: [error()] + }, + { + code: "class A { set a(val) { return foo(); } }", + errors: [error()] + }, + { + code: "(class { set a(val) { return this._a; } })", + errors: [error()] + }, + { + code: "({ set a(val) { return this.a; } })", + errors: [error()] + }, + + // any location + { + code: "({ set a(val) { if (foo) { return 1; }; } })", + errors: [error()] + }, + { + code: "class A { set a(val) { try { return 1; } catch(e) {} } }", + errors: [error()] + }, + { + code: "(class { set a(val) { while (foo){ if (bar) break; else return 1; } } })", + errors: [error()] + }, + + // multiple invalid in same object literal/class + { + code: "({ set a(val) { return 1; }, set b(val) { return 1; } })", + errors: [error(17), error(43)] + }, + { + code: "class A { set a(val) { return 1; } set b(val) { return 1; } }", + errors: [error(24), error(49)] + }, + { + code: "(class { set a(val) { return 1; } static set b(val) { return 1; } })", + errors: [error(23), error(55)] + }, + + // multiple invalid in the same setter + { + code: "({ set a(val) { if(val) { return 1; } else { return 2 }; } })", + errors: [error(27), error(46)] + }, + { + code: "class A { set a(val) { switch(val) { case 1: return x; case 2: return y; default: return z } } }", + errors: [error(46), error(64), error(83)] + }, + { + code: "(class { static set a(val) { if (val > 0) { this._val = val; return val; } return false; } })", + errors: [error(62), error(76)] + }, + + // valid and invalid in the same setter + { + code: "({ set a(val) { if(val) { return 1; } else { return; }; } })", + errors: [error(27)] + }, + { + code: "class A { set a(val) { switch(val) { case 1: return x; case 2: return; default: return z } } }", + errors: [error(46), error(81)] + }, + { + code: "(class { static set a(val) { if (val > 0) { this._val = val; return; } return false; } })", + errors: [error(72)] + }, + + // inner functions do not have effect + { + code: "({ set a(val) { function b(){} return b(); } })", + errors: [error(32)] + }, + { + code: "class A { set a(val) { return () => {}; } }", + errors: [error(24)] + }, + { + code: "(class { set a(val) { function b(){ return 1; } return 2; } })", + errors: [error(49)] + }, + { + code: "({ set a(val) { function b(){ return; } return 1; } })", + errors: [error(41)] + }, + { + code: "class A { set a(val) { var x = function() { return 1; }; return 2; } }", + errors: [error(58)] + }, + { + code: "(class { set a(val) { var x = () => { return; }; return 2; } })", + errors: [error(50)] + }, + + // other functions and global returns do not have effect (test function info tracking) + { + code: "function f(){}; ({ set a(val) { return 1; } });", + errors: [error()] + }, + { + code: "x = function f(){}; class A { set a(val) { return 1; } };", + errors: [error()] + }, + { + code: "x = () => {}; A = class { set a(val) { return 1; } };", + errors: [error()] + }, + { + code: "return; ({ set a(val) { return 1; } }); return 2;", + env: { node: true }, + errors: [error(25)] + }, + + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ + + // basic tests + { + code: "Object.defineProperty(foo, 'bar', { set(val) { return 1; } })", + errors: [error()] + }, + { + code: "Reflect.defineProperty(foo, 'bar', { set(val) { return 1; } })", + env: { es6: true }, + errors: [error()] + }, + { + code: "Object.defineProperties(foo, { baz: { set(val) { return 1; } } })", + errors: [error()] + }, + { + code: "Object.create(null, { baz: { set(val) { return 1; } } })", + errors: [error()] + }, + + // arrow implicit return// basic tests + { + code: "Object.defineProperty(foo, 'bar', { set: val => val })", + errors: [error(49, "Identifier")] + }, + { + code: "Reflect.defineProperty(foo, 'bar', { set: val => f(val) })", + env: { es6: true }, + errors: [error(50, "CallExpression")] + }, + { + code: "Object.defineProperties(foo, { baz: { set: val => a + b } })", + errors: [error(51, "BinaryExpression")] + }, + { + code: "Object.create({}, { baz: { set: val => this._val } })", + errors: [error(40, "MemberExpression")] + }, + + // various locations, value types and multiple invalid/valid in same setter. + { + code: "Object.defineProperty(foo, 'bar', { set(val) { if (val) { return; } return false; }, get(val) { return 1; } })", + errors: [error(69)] + }, + { + code: "Reflect.defineProperty(foo, 'bar', { set(val) { try { return f(val) } catch (e) { return e }; } })", + env: { es6: true }, + errors: [error(55), error(83)] + }, + { + code: "Object.defineProperties(foo, { bar: { get(){ return null; }, set(val) { return null; } } })", + errors: [error(73)] + }, + { + code: "Object.create(null, { baz: { set(val) { return this._val; return; return undefined; } } })", + errors: [error(41), error(67)] + }, + + // multiple invalid in the same descriptors object + { + code: "Object.defineProperties(foo, { baz: { set(val) { return 1; } }, bar: { set(val) { return 1; } } })", + errors: [error(50), error(83)] + }, + { + code: "Object.create({}, { baz: { set(val) { return 1; } }, bar: { set: (val) => 1 } })", + errors: [ + error(39), + error(75, "Literal") + ] + }, + + // various syntax for properties + { + code: "Object['defineProperty'](foo, 'bar', { set: function bar(val) { return 1; } })", + errors: [error()] + }, + { + code: "Reflect.defineProperty(foo, 'bar', { 'set'(val) { return 1; } })", + env: { es6: true }, + errors: [error()] + }, + { + code: "Object[`defineProperties`](foo, { baz: { ['set'](val) { return 1; } } })", + errors: [error()] + }, + { + code: "Object.create({}, { baz: { [`set`]: (val) => { return 1; } } })", + errors: [error()] + }, + + // edge cases for global objects + { + code: "Object.defineProperty(foo, 'bar', { set: function Object(val) { return 1; } })", + errors: [error()] + }, + { + code: "Object.defineProperty(foo, 'bar', { set: function(Object) { return 1; } })", + errors: [error()] + } + ] +}); diff --git a/tools/rule-types.json b/tools/rule-types.json index 4bc07d4e43e..f8741b83c9d 100644 --- a/tools/rule-types.json +++ b/tools/rule-types.json @@ -173,6 +173,7 @@ "no-self-assign": "problem", "no-self-compare": "problem", "no-sequences": "suggestion", + "no-setter-return": "problem", "no-shadow": "suggestion", "no-shadow-restricted-names": "suggestion", "no-spaced-func": "layout",