UNPKG

3.94 kBJavaScriptView Raw
1'use strict';
2const {isCommaToken} = require('eslint-utils');
3const getDocumentationUrl = require('./utils/get-documentation-url');
4
5const messageId = 'no-useless-undefined';
6
7const getSelector = (parent, property) =>
8 `${parent} > Identifier.${property}[name="undefined"]`;
9
10// `return undefined`
11const returnSelector = getSelector('ReturnStatement', 'argument');
12
13// `yield undefined`
14const yieldSelector = getSelector('YieldExpression[delegate=false]', 'argument');
15
16// `() => undefined`
17const arrowFunctionSelector = getSelector('ArrowFunctionExpression', 'body');
18
19// `let foo = undefined` / `var foo = undefined`
20const variableInitSelector = getSelector(
21 [
22 'VariableDeclaration',
23 '[kind!="const"]',
24 '>',
25 'VariableDeclarator'
26 ].join(''),
27 'init'
28);
29
30// `const {foo = undefined} = {}`
31const assignmentPatternSelector = getSelector('AssignmentPattern', 'right');
32
33const isUndefined = node => node && node.type === 'Identifier' && node.name === 'undefined';
34
35const compareFunctionNames = new Set([
36 'is',
37 'equal',
38 'notEqual',
39 'strictEqual',
40 'notStrictEqual',
41 'propertyVal',
42 'notPropertyVal',
43 'not',
44 'include',
45 'property',
46 'toBe',
47 'toContain',
48 'toContainEqual',
49 'toEqual',
50 'same',
51 'notSame',
52 'strictSame',
53 'strictNotSame'
54]);
55const isCompareFunction = node => {
56 let name;
57
58 if (node.type === 'Identifier') {
59 name = node.name;
60 } else if (
61 node.type === 'MemberExpression' &&
62 node.computed === false &&
63 node.property &&
64 node.property.type === 'Identifier'
65 ) {
66 name = node.property.name;
67 }
68
69 return compareFunctionNames.has(name);
70};
71
72const create = context => {
73 const listener = fix => node => {
74 context.report({
75 node,
76 messageId,
77 fix: fixer => fix(node, fixer)
78 });
79 };
80
81 const code = context.getSourceCode().text;
82
83 const removeNodeAndLeadingSpace = (node, fixer) => {
84 const textBefore = code.slice(0, node.range[0]);
85 return fixer.removeRange([
86 node.range[0] - (textBefore.length - textBefore.trim().length),
87 node.range[1]
88 ]);
89 };
90
91 return {
92 [returnSelector]: listener(removeNodeAndLeadingSpace),
93 [yieldSelector]: listener(removeNodeAndLeadingSpace),
94 [arrowFunctionSelector]: listener(
95 (node, fixer) => fixer.replaceText(node, '{}')
96 ),
97 [variableInitSelector]: listener(
98 (node, fixer) => fixer.removeRange([node.parent.id.range[1], node.range[1]])
99 ),
100 [assignmentPatternSelector]: listener(
101 (node, fixer) => fixer.removeRange([node.parent.left.range[1], node.range[1]])
102 ),
103 CallExpression: node => {
104 if (isCompareFunction(node.callee)) {
105 return;
106 }
107
108 const argumentNodes = node.arguments;
109 const undefinedArguments = [];
110 for (let index = argumentNodes.length - 1; index >= 0; index--) {
111 const node = argumentNodes[index];
112 if (isUndefined(node)) {
113 undefinedArguments.unshift(node);
114 } else {
115 break;
116 }
117 }
118
119 if (undefinedArguments.length === 0) {
120 return;
121 }
122
123 const firstUndefined = undefinedArguments[0];
124 const lastUndefined = undefinedArguments[undefinedArguments.length - 1];
125
126 context.report({
127 messageId,
128 loc: {
129 start: firstUndefined.loc.start,
130 end: lastUndefined.loc.end
131 },
132 fix: fixer => {
133 let start = firstUndefined.range[0];
134 let end = lastUndefined.range[1];
135
136 const previousArgument = argumentNodes[argumentNodes.length - undefinedArguments.length - 1];
137
138 if (previousArgument) {
139 start = previousArgument.range[1];
140 } else {
141 // If all arguments removed, and there is trailing comma, we need remove it.
142 const tokenAfter = context.getTokenAfter(lastUndefined);
143 if (isCommaToken(tokenAfter)) {
144 end = tokenAfter.range[1];
145 }
146 }
147
148 return fixer.removeRange([start, end]);
149 }
150 });
151 }
152 };
153};
154
155module.exports = {
156 create,
157 meta: {
158 type: 'suggestion',
159 docs: {
160 url: getDocumentationUrl(__filename)
161 },
162 messages: {
163 [messageId]: 'Do not use useless `undefined`.'
164 },
165 fixable: 'code'
166 }
167};