diff --git a/docs/rules/accessor-pairs.md b/docs/rules/accessor-pairs.md index 1cbf7c9ef18..207eedeac21 100644 --- a/docs/rules/accessor-pairs.md +++ b/docs/rules/accessor-pairs.md @@ -143,6 +143,27 @@ Object.defineProperty(o, 'c', { ``` +## Known Limitations + +Due to the limits of static analysis, this rule does not account for possible side effects and in certain cases +might not report a missing pair for a getter/setter that has a computed key, like in the following example: + +```js +/*eslint accessor-pairs: "error"*/ + +var a = 1; + +// no warnings +var o = { + get [a++]() { + return this.val; + }, + set [a++](value) { + this.val = value; + } +}; +``` + ## When Not To Use It You can turn this rule off if you are not concerned with the simultaneous presence of setters and getters on objects. diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index aca23184863..9c78bdc70e0 100644 --- a/lib/rules/accessor-pairs.js +++ b/lib/rules/accessor-pairs.js @@ -5,10 +5,87 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * Property name if it can be computed statically, otherwise the list of the tokens of the key node. + * @typedef {string|Token[]} Key + */ + +/** + * Accessor nodes with the same key. + * @typedef {Object} AccessorData + * @property {Key} key Accessor's key + * @property {ASTNode[]} getters List of getter nodes. + * @property {ASTNode[]} setters List of setter nodes. + */ + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ +/** + * Checks whether or not the given lists represent the equal tokens in the same order. + * Tokens are compared by their properties, not by instance. + * @param {Token[]} left First list of tokens. + * @param {Token[]} right Second list of tokens. + * @returns {boolean} `true` if the lists have same tokens. + */ +function areEqualTokenLists(left, right) { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + const leftToken = left[i], + rightToken = right[i]; + + if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { + return false; + } + } + + return true; +} + +/** + * Checks whether or not the given keys are equal. + * @param {Key} left First key. + * @param {Key} right Second key. + * @returns {boolean} `true` if the keys are equal. + */ +function areEqualKeys(left, right) { + if (typeof left === "string" && typeof right === "string") { + + // Statically computed names. + return left === right; + } + if (Array.isArray(left) && Array.isArray(right)) { + + // Token lists. + return areEqualTokenLists(left, right); + } + + return false; +} + +/** + * Checks whether or not a given node is of an accessor kind ('get' or 'set'). + * @param {ASTNode} node - A node to check. + * @returns {boolean} `true` if the node is of an accessor kind. + */ +function isAccessorKind(node) { + return node.kind === "get" || node.kind === "set"; +} + /** * Checks whether or not a given node is an `Identifier` node which was named a given name. * @param {ASTNode} node - A node to check. @@ -97,69 +174,152 @@ module.exports = { }], messages: { - getter: "Getter is not present.", - setter: "Setter is not present." + missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.", + missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.", + missingGetterInObjectLiteral: "Getter is not present for {{ name }}.", + missingSetterInObjectLiteral: "Setter is not present for {{ name }}." } }, create(context) { const config = context.options[0] || {}; const checkGetWithoutSet = config.getWithoutSet === true; const checkSetWithoutGet = config.setWithoutGet !== false; + const sourceCode = context.getSourceCode(); /** - * Checks a object expression to see if it has setter and getter both present or none. - * @param {ASTNode} node The node to check. + * Reports the given node. + * @param {ASTNode} node The node to report. + * @param {string} messageKind "missingGetter" or "missingSetter". * @returns {void} * @private */ - function checkLonelySetGet(node) { - let isSetPresent = false; - let isGetPresent = false; - const isDescriptor = isPropertyDescriptor(node); + function report(node, messageKind) { + if (node.type === "Property") { + context.report({ + node, + messageId: `${messageKind}InObjectLiteral`, + loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node.value) } + }); + } else { + context.report({ + node, + messageId: `${messageKind}InPropertyDescriptor` + }); + } + } - for (let i = 0, end = node.properties.length; i < end; i++) { - const property = node.properties[i]; + /** + * Reports each of the nodes in the given list using the same messageId. + * @param {ASTNode[]} nodes Nodes to report. + * @param {string} messageKind "missingGetter" or "missingSetter". + * @returns {void} + * @private + */ + function reportList(nodes, messageKind) { + for (const node of nodes) { + report(node, messageKind); + } + } - let propToCheck = ""; + /** + * Creates a new `AccessorData` object for the given getter or setter node. + * @param {ASTNode} node A getter or setter node. + * @returns {AccessorData} New `AccessorData` object that contains the given node. + * @private + */ + function createAccessorData(node) { + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); - if (property.kind === "init") { - if (isDescriptor && !property.computed) { - propToCheck = property.key.name; - } - } else { - propToCheck = property.kind; - } + return { + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }; + } - switch (propToCheck) { - case "set": - isSetPresent = true; - break; + /** + * Merges the given `AccessorData` object into the given accessors list. + * @param {AccessorData[]} accessors The list to merge into. + * @param {AccessorData} accessorData The object to merge. + * @returns {AccessorData[]} The same instance with the merged object. + * @private + */ + function mergeAccessorData(accessors, accessorData) { + const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); - case "get": - isGetPresent = true; - break; + if (equalKeyElement) { + equalKeyElement.getters.push(...accessorData.getters); + equalKeyElement.setters.push(...accessorData.setters); + } else { + accessors.push(accessorData); + } - default: + return accessors; + } - // Do nothing - } + /** + * Checks accessor pairs in the given list of nodes. + * @param {ASTNode[]} nodes The list to check. + * @returns {void} + * @private + */ + function checkList(nodes) { + const accessors = nodes + .filter(isAccessorKind) + .map(createAccessorData) + .reduce(mergeAccessorData, []); - if (isSetPresent && isGetPresent) { - break; + for (const { getters, setters } of accessors) { + if (checkSetWithoutGet && setters.length && !getters.length) { + reportList(setters, "missingGetter"); + } + if (checkGetWithoutSet && getters.length && !setters.length) { + reportList(getters, "missingSetter"); } } + } - if (checkSetWithoutGet && isSetPresent && !isGetPresent) { - context.report({ node, messageId: "getter" }); - } else if (checkGetWithoutSet && isGetPresent && !isSetPresent) { - context.report({ node, messageId: "setter" }); + /** + * Checks accessor pairs in an object literal. + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkObjectLiteral(node) { + checkList(node.properties.filter(p => p.type === "Property")); + } + + /** + * Checks accessor pairs in a property descriptor. + * @param {ASTNode} node Property descriptor `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkPropertyDescriptor(node) { + const namesToCheck = node.properties + .filter(p => p.type === "Property" && p.kind === "init" && !p.computed) + .map(({ key }) => key.name); + + const hasGetter = namesToCheck.includes("get"); + const hasSetter = namesToCheck.includes("set"); + + if (checkSetWithoutGet && hasSetter && !hasGetter) { + report(node, "missingGetter"); + } + if (checkGetWithoutSet && hasGetter && !hasSetter) { + report(node, "missingSetter"); } } return { ObjectExpression(node) { if (checkSetWithoutGet || checkGetWithoutSet) { - checkLonelySetGet(node); + checkObjectLiteral(node); + if (isPropertyDescriptor(node)) { + checkPropertyDescriptor(node); + } } } }; diff --git a/tests/lib/rules/accessor-pairs.js b/tests/lib/rules/accessor-pairs.js index 42ca8461de3..4f55cd10eb7 100644 --- a/tests/lib/rules/accessor-pairs.js +++ b/tests/lib/rules/accessor-pairs.js @@ -18,26 +18,301 @@ const rule = require("../../../lib/rules/accessor-pairs"), const ruleTester = new RuleTester(); -const getterError = { messageId: "getter" }; -const setterError = { messageId: "setter" }; - ruleTester.run("accessor-pairs", rule, { valid: [ - "var o = { a: 1 };", - "var o = {\n get a() {\n return val; \n} \n};", - "var o = {\n set a(value) {\n val = value; \n},\n get a() {\n return val; \n} \n};", - "var o = {a: 1};\n Object.defineProperty(o, 'b', \n{set: function(value) {\n val = value; \n},\n get: function() {\n return val; \n} \n});", + + //------------------------------------------------------------------------------ + // General + //------------------------------------------------------------------------------ + + // Does not check object patterns { - code: "var expr = 'foo'; var o = { set [expr](value) { val = value; }, get [expr]() { return val; } };", + code: "var { get: foo } = bar; ({ set: foo } = bar);", + options: [{ setWithoutGet: true, getWithoutSet: true }], parserOptions: { ecmaVersion: 6 } }, { - code: "var o = {\n set a(value) {\n val = value; \n} \n};", - options: [{ - setWithoutGet: false - }] + code: "var { set } = foo; ({ get } = foo);", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + //------------------------------------------------------------------------------ + // Object literals + //------------------------------------------------------------------------------ + + // Test default settings, this would be an error if `getWithoutSet` was set to `true` + "var o = { get a() {} }", + + // No accessors + { + code: "var o = {};", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { a: 1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { a: get };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { a: set };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get: function(){} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { set: function(foo){} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { set };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { [get]: function() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { [set]: function(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { set(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + // Disabled options + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: false }] + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }] + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: false }] + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }] + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false }] + }, + + // Valid pairs with identifiers + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }] + }, + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }] + }, + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { set a(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] }, + // Valid pairs with statically computed names + { + code: "var o = { get 'a'() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get a() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get ['abc']() {}, set ['abc'](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [1e2]() {}, set 100(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get abc() {}, set [`abc`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get ['123']() {}, set 123(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + // Valid pairs with expressions + { + code: "var o = { get [a]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [a]() {}, set [(a)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [(a)]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [a]() {}, set [ a ](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [/*comment*/a/*comment*/]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [f()]() {}, set [f()](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [f(a)]() {}, set [f(a)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [a + b]() {}, set [a + b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get [`${a}`]() {}, set [`${a}`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + // Multiple valid pairs in the same literal + { + code: "var o = { get a() {}, set a(foo) {}, get b() {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get a() {}, set c(foo) {}, set a(bar) {}, get b() {}, get c() {}, set b(baz) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + + // Valid pairs with other elements + { + code: "var o = { get a() {}, set a(foo) {}, b: bar };", + options: [{ setWithoutGet: true, getWithoutSet: true }] + }, + { + code: "var o = { get a() {}, b, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get a() {}, ...b, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 } + }, + { + code: "var o = { get a() {}, set a(foo) {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 } + }, + + // Duplicate keys. This is the responsibility of no-dupe-keys, but this rule still checks is there the other accessor kind. + { + code: "var o = { get a() {}, get a() {}, set a(foo) {}, };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get a() {}, set a(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get a() {}, set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { set a(bar) {}, get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get a() {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { get a() {}, set a(foo) {}, a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var o = { a, get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + /* + * This should be actually invalid by this rule! + * This code creates a property with the setter only, the getter will be ignored. + * It's treated as 3 attempts to define the same key, and the last wins. + * However, this edge case is not covered, it should be reported by no-dupe-keys anyway. + */ + { + code: "var o = { get a() {}, a:1, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 } + }, + + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ + + "var o = {a: 1};\n Object.defineProperty(o, 'b', \n{set: function(value) {\n val = value; \n},\n get: function() {\n return val; \n} \n});", + // https://github.com/eslint/eslint/issues/3262 "var o = {set: function() {}}", "Object.defineProperties(obj, {set: {value: function() {}}});", @@ -46,38 +321,494 @@ ruleTester.run("accessor-pairs", rule, { { code: "var o = {[set]: function() {}}", parserOptions: { ecmaVersion: 6 } }, { code: "var set = 'value'; Object.defineProperty(obj, 'foo', {[set]: function(value) {}});", parserOptions: { ecmaVersion: 6 } } ], + invalid: [ + + //------------------------------------------------------------------------------ + // Object literals + //------------------------------------------------------------------------------ + + // Test default settings + { + code: "var o = { set a(value) {} };", + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] + }, + + // Test that the options do not affect each other + { + code: "var o = { set a(value) {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] + }, + { + code: "var o = { set a(value) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] + }, + { + code: "var o = { get a() {} };", + options: [{ getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] + }, + + // Various kinds of the getter's key + { + code: "var o = { get abc() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] + }, + { + code: "var o = { get 'abc'() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] + }, + { + code: "var o = { get 123() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter '123'.", type: "Property" }] + }, + { + code: "var o = { get 1e2() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter '100'.", type: "Property" }] + }, + { + code: "var o = { get ['abc']() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] + }, + { + code: "var o = { get [`abc`]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] + }, + { + code: "var o = { get [123]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter '123'.", type: "Property" }] + }, + { + code: "var o = { get [abc]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter.", type: "Property" }] + }, + { + code: "var o = { get [f(abc)]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter.", type: "Property" }] + }, + { + code: "var o = { get [a + b]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter.", type: "Property" }] + }, + + // Various kinds of the setter's key + { + code: "var o = { set abc(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] + }, + { + code: "var o = { set 'abc'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] + }, + { + code: "var o = { set 123(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter '123'.", type: "Property" }] + }, { - code: "var o = {\n set a(value) {\n val = value; \n} \n};", - errors: [getterError] + code: "var o = { set 1e2(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter '100'.", type: "Property" }] }, { - code: "var o = {\n get a() {\n return val; \n} \n};", - options: [{ - getWithoutSet: true - }], - errors: [setterError] + code: "var o = { set ['abc'](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] + }, + { + code: "var o = { set [`abc`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] + }, + { + code: "var o = { set [123](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter '123'.", type: "Property" }] + }, + { + code: "var o = { set [abc](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter.", type: "Property" }] + }, + { + code: "var o = { set [f(abc)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter.", type: "Property" }] + }, + { + code: "var o = { set [a + b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter.", type: "Property" }] + }, + + // Different keys + { + code: "var o = { get a() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { set a(foo) {}, get b() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, + { message: "Setter is not present for getter 'b'.", type: "Property", column: 26 } + ] }, + { + code: "var o = { get 1() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter '1'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { get a() {}, set 1(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter '1'.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { get a() {}, set 'a '(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'a '.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { get ' a'() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter ' a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'a'.", type: "Property", column: 26 } + ] + }, + { + code: "var o = { get ''() {}, set ' '(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { messageId: "missingSetterInObjectLiteral", type: "Property", column: 11 }, // TODO: Change to message when getFunctionNameWithKind gets fixed + { message: "Getter is not present for setter ' '.", type: "Property", column: 24 } + ] + }, + { + code: "var o = { get ''() {}, set null(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { messageId: "missingSetterInObjectLiteral", type: "Property", column: 11 }, // TODO: Change to message when getFunctionNameWithKind gets fixed + { message: "Getter is not present for setter 'null'.", type: "Property", column: 24 } + ] + }, + { + code: "var o = { get [`a`]() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'b'.", type: "Property", column: 27 } + ] + }, + { + code: "var o = { get [a]() {}, set [b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter.", type: "Property", column: 11 }, + { message: "Getter is not present for setter.", type: "Property", column: 25 } + ] + }, + { + code: "var o = { get [a]() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'a'.", type: "Property", column: 25 } + ] + }, + { + code: "var o = { get a() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { get [a + b]() {}, set [a - b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter.", type: "Property", column: 11 }, + { message: "Getter is not present for setter.", type: "Property", column: 29 } + ] + }, + { + code: "var o = { get [`${0} `]() {}, set [`${0}`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter.", type: "Property", column: 11 }, + { message: "Getter is not present for setter.", type: "Property", column: 31 } + ] + }, + + // Multiple invalid of same and different kinds + { + code: "var o = { get a() {}, get b() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Setter is not present for getter 'b'.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { set a(foo) {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'b'.", type: "Property", column: 26 } + ] + }, + { + code: "var o = { get a() {}, set b(foo) {}, set c(foo) {}, get d() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 }, + { message: "Getter is not present for setter 'c'.", type: "Property", column: 38 }, + { message: "Setter is not present for getter 'd'.", type: "Property", column: 53 } + ] + }, + + // Checks per object literal + { + code: "var o1 = { get a() {} }, o2 = { set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 12 }, + { message: "Getter is not present for setter 'a'.", type: "Property", column: 33 } + ] + }, + { + code: "var o1 = { set a(foo) {} }, o2 = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Getter is not present for setter 'a'.", type: "Property", column: 12 }, + { message: "Setter is not present for getter 'a'.", type: "Property", column: 36 } + ] + }, + + // Combinations or valid and invalid + { + code: "var o = { get a() {}, get b() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { get b() {}, get a() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 23 }] + }, + { + code: "var o = { get b() {}, set b(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 38 }] + }, + { + code: "var o = { set a(foo) {}, get b() {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { get b() {}, set a(foo) {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 23 }] + }, + { + code: "var o = { get b() {}, set b(bar) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 38 }] + }, + { + code: "var o = { get v1() {}, set i1(foo) {}, get v2() {}, set v2(bar) {}, get i2() {}, set v1(baz) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { message: "Getter is not present for setter 'i1'.", type: "Property", column: 24 }, + { message: "Setter is not present for getter 'i2'.", type: "Property", column: 69 } + ] + }, + + // In the case of duplicates which don't have the other kind, all nodes are reported + { + code: "var o = { get a() {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, + { message: "Setter is not present for getter 'a'.", type: "Property", column: 23 } + ] + }, + { + code: "var o = { set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, + { message: "Getter is not present for setter 'a'.", type: "Property", column: 26 } + ] + }, + + // Other elements or even value property duplicates in the same literal do not affect this rule + { + code: "var o = { a, get b() {}, c };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter 'b'.", type: "Property", column: 14 }] + }, + { + code: "var o = { a, get b() {}, c, set d(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { message: "Setter is not present for getter 'b'.", type: "Property", column: 14 }, + { message: "Getter is not present for setter 'd'.", type: "Property", column: 29 } + ] + }, + { + code: "var o = { get a() {}, a:1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { a, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 14 }] + }, + { + code: "var o = { set a(foo) {}, a:1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { a, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 14 }] + }, + { + code: "var o = { get a() {}, ...b };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { get a() {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] + }, + { + code: "var o = { set a(foo) {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] + }, + + // Full location tests + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [{ + message: "Setter is not present for getter 'a'.", + type: "Property", + line: 1, + column: 11, + endLine: 1, + endColumn: 16 + }] + }, + { + code: "var o = {\n set [\n a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [{ + message: "Getter is not present for setter.", + type: "Property", + line: 2, + column: 3, + endLine: 3, + endColumn: 4 + }] + }, + + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ + { code: "var o = {d: 1};\n Object.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", - errors: [getterError] + errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] }, { code: "Reflect.defineProperty(obj, 'foo', {set: function(value) {}});", - errors: [getterError] + errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] }, { code: "Object.defineProperties(obj, {foo: {set: function(value) {}}});", - errors: [getterError] + errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] }, { code: "Object.create(null, {foo: {set: function(value) {}}});", - errors: [getterError] - }, - { - code: "var expr = 'foo'; var o = { set [expr](value) { val = value; } };", - parserOptions: { ecmaVersion: 6 }, - errors: [getterError] + errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] } ] });