1 | 'use strict';
|
2 | const {isCommaToken} = require('eslint-utils');
|
3 | const getDocumentationUrl = require('./utils/get-documentation-url');
|
4 |
|
5 | const messageId = 'no-useless-undefined';
|
6 |
|
7 | const getSelector = (parent, property) =>
|
8 | `${parent} > Identifier.${property}[name="undefined"]`;
|
9 |
|
10 |
|
11 | const returnSelector = getSelector('ReturnStatement', 'argument');
|
12 |
|
13 |
|
14 | const yieldSelector = getSelector('YieldExpression[delegate=false]', 'argument');
|
15 |
|
16 |
|
17 | const arrowFunctionSelector = getSelector('ArrowFunctionExpression', 'body');
|
18 |
|
19 |
|
20 | const variableInitSelector = getSelector(
|
21 | [
|
22 | 'VariableDeclaration',
|
23 | '[kind!="const"]',
|
24 | '>',
|
25 | 'VariableDeclarator'
|
26 | ].join(''),
|
27 | 'init'
|
28 | );
|
29 |
|
30 |
|
31 | const assignmentPatternSelector = getSelector('AssignmentPattern', 'right');
|
32 |
|
33 | const isUndefined = node => node && node.type === 'Identifier' && node.name === 'undefined';
|
34 |
|
35 | const 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 | ]);
|
55 | const 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 |
|
72 | const 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 |
|
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 |
|
155 | module.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 | };
|