diff --git a/rules/prefer-node-append.js b/rules/prefer-node-append.js index 73231b2281..efb841f9cb 100644 --- a/rules/prefer-node-append.js +++ b/rules/prefer-node-append.js @@ -2,17 +2,25 @@ const getDocumentationUrl = require('./utils/get-documentation-url'); const isValueNotUsable = require('./utils/is-value-not-usable'); const methodSelector = require('./utils/method-selector'); +const {notDomNodeSelector} = require('./utils/not-dom-node'); const message = 'Prefer `Node#append()` over `Node#appendChild()`.'; -const selector = methodSelector({ - name: 'appendChild', - length: 1 -}); +const selector = [ + methodSelector({ + name: 'appendChild', + length: 1 + }), + notDomNodeSelector({ + node: 'callee.object' + }), + notDomNodeSelector({ + node: 'arguments.0' + }) +].join(''); const create = context => { return { [selector](node) { - // TODO: exclude those cases parent/child impossible to be `Node` const fix = isValueNotUsable(node) ? fixer => fixer.replaceText(node.callee.property, 'append') : undefined; diff --git a/rules/utils/not-dom-node.js b/rules/utils/not-dom-node.js new file mode 100644 index 0000000000..cf146b68e1 --- /dev/null +++ b/rules/utils/not-dom-node.js @@ -0,0 +1,35 @@ +'use strict'; + +// AST Types: +// https://github.com/eslint/espree/blob/master/lib/ast-node-types.js#L18 +// Only types possible to be `callee` or `argument` listed +const impossibleTypes = [ + 'ArrayExpression', + 'ArrowFunctionExpression', + 'ClassExpression', + 'FunctionExpression', + 'Literal', + 'ObjectExpression', + 'TemplateLiteral' +]; + +// We might need this later +/* istanbul ignore next */ +const isNotDomNode = node => { + return impossibleTypes.includes(node.type) || + (node.type === 'Identifier' && node.name === 'undefined'); +}; + +const notDomNodeSelector = options => { + const {node} = options; + + return [ + ...impossibleTypes.map(type => `[${node}.type!="${type}"]`), + `:not([${node}.type="Identifier"][${node}.name="undefined"])` + ].join(''); +}; + +module.exports = { + isNotDomNode, + notDomNodeSelector +}; diff --git a/test/prefer-node-append.js b/test/prefer-node-append.js index 8301c5cbad..758dba23e3 100644 --- a/test/prefer-node-append.js +++ b/test/prefer-node-append.js @@ -1,11 +1,12 @@ import test from 'ava'; import avaRuleTester from 'eslint-ava-rule-tester'; import {outdent} from 'outdent'; +import notDomNodeTypes from './utils/not-dom-node-types'; import rule from '../rules/prefer-node-append'; const ruleTester = avaRuleTester(test, { - env: { - es6: true + parserOptions: { + ecmaVersion: 2020 } }); @@ -30,7 +31,11 @@ ruleTester.run('prefer-node-append', rule, { // More or less argument(s) 'parent.appendChild(one, two);', 'parent.appendChild();', - 'parent.appendChild(...argumentsArray)' + 'parent.appendChild(...argumentsArray)', + // `callee.object` is not a DOM Node, + ...notDomNodeTypes.map(data => `(${data}).appendChild(foo)`), + // First argument is not a DOM Node, + ...notDomNodeTypes.map(data => `foo.appendChild(${data})`) ], invalid: [ { @@ -44,19 +49,19 @@ ruleTester.run('prefer-node-append', rule, { errors: [error] }, { - code: 'node.appendChild(null)', - output: 'node.append(null)', + code: 'node.appendChild(foo)', + output: 'node.append(foo)', errors: [error] }, { code: outdent` function foo() { - node.appendChild(null); + node.appendChild(bar); } `, output: outdent` function foo() { - node.append(null); + node.append(bar); } `, errors: [error] diff --git a/test/utils/not-dom-node-types.js b/test/utils/not-dom-node-types.js new file mode 100644 index 0000000000..75df47bb2a --- /dev/null +++ b/test/utils/not-dom-node-types.js @@ -0,0 +1,32 @@ +'use strict'; + +module.exports = [ + // ArrayExpression + '[]', + '[1]', + '[...elements]', + // ArrowFunctionExpression + '() => {}', + // ClassExpression + 'class Node {}', + // FunctionExpression + 'function() {}', + // Literal + '0', + '1', + '""', + '"string"', + '/regex/', + 'null', + '0n', + '1n', + 'true', + 'false', + // ObjectExpression + '{}', + // TemplateLiteral + '`templateLiteral`', + // Undefined + 'undefined' +]; +