diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js index 149abac56..9ef2d8b7c 100644 --- a/lib/rules/no-side-effects-in-computed-properties.js +++ b/lib/rules/no-side-effects-in-computed-properties.js @@ -41,7 +41,7 @@ module.exports = { }, // this.xxx.func() 'CallExpression' (node) { - const code = context.getSourceCode().getText(node) + const code = utils.parseMemberOrCallExpression(node) const MUTATION_REGEX = /(this.)((?!(concat|slice|map|filter)\().)[^\)]*((push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)\()/g if (MUTATION_REGEX.test(code)) { diff --git a/lib/utils/index.js b/lib/utils/index.js index 677fff6b5..7494178c4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -617,5 +617,42 @@ module.exports = { return } return body.errors.some(error => typeof error.code === 'string' && error.code.startsWith('eof-')) + }, + + /** + * Parse CallExpression or MemberExpression to get simplified version without arguments + * + * @param {Object} node The node to parse (MemberExpression | CallExpression) + * @return {String} eg. 'this.asd.qwe().map().filter().test.reduce()' + */ + parseMemberOrCallExpression (node) { + const parsedCallee = [] + let n = node + let isFunc + + while (n.type === 'MemberExpression' || n.type === 'CallExpression') { + if (n.type === 'CallExpression') { + n = n.callee + isFunc = true + } else { + if (n.computed) { + parsedCallee.push('[]') + } else if (n.property.type === 'Identifier') { + parsedCallee.push(n.property.name + (isFunc ? '()' : '')) + } + isFunc = false + n = n.object + } + } + + if (n.type === 'Identifier') { + parsedCallee.push(n.name) + } + + if (n.type === 'ThisExpression') { + parsedCallee.push('this') + } + + return parsedCallee.reverse().join('.').replace(/\.\[/g, '[') } } diff --git a/tests/lib/rules/no-side-effects-in-computed-properties.js b/tests/lib/rules/no-side-effects-in-computed-properties.js index cfd71c7d1..3eadc45c0 100644 --- a/tests/lib/rules/no-side-effects-in-computed-properties.js +++ b/tests/lib/rules/no-side-effects-in-computed-properties.js @@ -81,6 +81,25 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, { get() { return Object.keys(this.a).sort() } + }, + test11() { + const categories = {} + + this.types.forEach(c => { + categories[c.category] = categories[c.category] || [] + categories[c.category].push(c) + }) + + return categories + }, + test12() { + return this.types.map(t => { + // [].push('xxx') + return t + }) + }, + test13 () { + this.someArray.forEach(arr => console.log(arr)) } } })`, diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js index 2189f8960..c222142d8 100644 --- a/tests/lib/utils/index.js +++ b/tests/lib/utils/index.js @@ -166,3 +166,23 @@ describe('getStaticPropertyName', () => { assert.ok(parsed === 'computed') }) }) + +describe('parseMemberOrCallExpression', () => { + let node + + const parse = function (code) { + return babelEslint.parse(code).body[0].declarations[0].init + } + + it('should parse CallExpression', () => { + node = parse(`const test = this.lorem['ipsum'].map(d => d.id).filter((a, b) => a > b).reduce((acc, d) => acc + d, 0)`) + const parsed = utils.parseMemberOrCallExpression(node) + assert.equal(parsed, 'this.lorem[].map().filter().reduce()') + }) + + it('should parse MemberExpression', () => { + node = parse(`const test = this.lorem['ipsum'][0].map(d => d.id).dolor.reduce((acc, d) => acc + d, 0).sit`) + const parsed = utils.parseMemberOrCallExpression(node) + assert.equal(parsed, 'this.lorem[][].map().dolor.reduce().sit') + }) +})