UNPKG

5.25 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3
4const getDeclaratorOrPropertyValue = declaratorOrProperty => {
5 return declaratorOrProperty.init || declaratorOrProperty.value;
6};
7
8const isMemberExpressionCall = memberExpression => {
9 return (
10 memberExpression.parent &&
11 memberExpression.parent.type === 'CallExpression' &&
12 memberExpression.parent.callee === memberExpression
13 );
14};
15
16const isMemberExpressionAssignment = memberExpression => {
17 return (
18 memberExpression.parent &&
19 memberExpression.parent.type === 'AssignmentExpression'
20 );
21};
22
23const isMemberExpressionComputedBeyondPrediction = memberExpression => {
24 return (
25 memberExpression.computed && memberExpression.property.type !== 'Literal'
26 );
27};
28
29const specialProtoPropertyKey = {
30 type: 'Identifier',
31 name: '__proto__'
32};
33
34const propertyKeysEqual = (keyA, keyB) => {
35 if (keyA.type === 'Identifier') {
36 if (keyB.type === 'Identifier') {
37 return keyA.name === keyB.name;
38 }
39
40 if (keyB.type === 'Literal') {
41 return keyA.name === keyB.value;
42 }
43 }
44
45 if (keyA.type === 'Literal') {
46 if (keyB.type === 'Identifier') {
47 return keyA.value === keyB.name;
48 }
49
50 if (keyB.type === 'Literal') {
51 return keyA.value === keyB.value;
52 }
53 }
54
55 return false;
56};
57
58const objectPatternMatchesObjectExprPropertyKey = (pattern, key) => {
59 return pattern.properties.some(property => {
60 if (property.type === 'ExperimentalRestProperty' || property.type === 'RestElement') {
61 return true;
62 }
63
64 return propertyKeysEqual(property.key, key);
65 });
66};
67
68const isLeafDeclaratorOrProperty = declaratorOrProperty => {
69 const value = getDeclaratorOrPropertyValue(declaratorOrProperty);
70
71 if (!value) {
72 return true;
73 }
74
75 if (value.type !== 'ObjectExpression') {
76 return true;
77 }
78
79 return false;
80};
81
82const isUnusedVariable = variable => {
83 const hasReadReference = variable.references.some(reference => reference.isRead());
84 return !hasReadReference;
85};
86
87const create = context => {
88 const getPropertyDisplayName = property => {
89 if (property.key.type === 'Identifier') {
90 return property.key.name;
91 }
92
93 if (property.key.type === 'Literal') {
94 return property.key.value;
95 }
96
97 return context.getSource(property.key);
98 };
99
100 const checkProperty = (property, references, path) => {
101 if (references.length === 0) {
102 context.report({
103 node: property,
104 message: 'Property `{{name}}` is defined but never used.',
105 data: {
106 name: getPropertyDisplayName(property)
107 }
108 });
109 return;
110 }
111
112 checkObject(property, references, path);
113 };
114
115 const checkProperties = (objectExpression, references, path = []) => {
116 objectExpression.properties.forEach(property => {
117 const {key} = property;
118
119 if (!key) {
120 return;
121 }
122
123 if (propertyKeysEqual(key, specialProtoPropertyKey)) {
124 return;
125 }
126
127 const nextPath = path.concat(key);
128
129 const nextReferences = references
130 .map(reference => {
131 const {parent} = reference.identifier;
132
133 if (reference.init) {
134 if (
135 parent.type === 'VariableDeclarator' &&
136 parent.parent.type === 'VariableDeclaration' &&
137 parent.parent.parent.type === 'ExportNamedDeclaration'
138 ) {
139 return {identifier: parent};
140 }
141
142 return;
143 }
144
145 if (parent.type === 'MemberExpression') {
146 if (
147 isMemberExpressionAssignment(parent) ||
148 isMemberExpressionCall(parent) ||
149 isMemberExpressionComputedBeyondPrediction(parent) ||
150 propertyKeysEqual(parent.property, key)
151 ) {
152 return {identifier: parent};
153 }
154
155 return;
156 }
157
158 if (
159 parent.type === 'VariableDeclarator' &&
160 parent.id.type === 'ObjectPattern'
161 ) {
162 if (objectPatternMatchesObjectExprPropertyKey(parent.id, key)) {
163 return {identifier: parent};
164 }
165
166 return;
167 }
168
169 if (
170 parent.type === 'AssignmentExpression' &&
171 parent.left.type === 'ObjectPattern'
172 ) {
173 if (objectPatternMatchesObjectExprPropertyKey(parent.left, key)) {
174 return {identifier: parent};
175 }
176
177 return;
178 }
179
180 return reference;
181 })
182 .filter(Boolean);
183
184 checkProperty(property, nextReferences, nextPath);
185 });
186 };
187
188 const checkObject = (declaratorOrProperty, references, path) => {
189 if (isLeafDeclaratorOrProperty(declaratorOrProperty)) {
190 return;
191 }
192
193 const value = getDeclaratorOrPropertyValue(declaratorOrProperty);
194
195 checkProperties(value, references, path);
196 };
197
198 const checkVariable = variable => {
199 if (variable.defs.length !== 1) {
200 return;
201 }
202
203 if (isUnusedVariable(variable)) {
204 return;
205 }
206
207 const [definition] = variable.defs;
208
209 checkObject(definition.node, variable.references);
210 };
211
212 const checkVariables = scope => {
213 scope.variables.forEach(variable => checkVariable(variable));
214 };
215
216 const checkChildScopes = scope => {
217 scope.childScopes.forEach(scope => checkScope(scope));
218 };
219
220 const checkScope = scope => {
221 if (scope.type === 'global') {
222 return checkChildScopes(scope);
223 }
224
225 checkVariables(scope);
226
227 return checkChildScopes(scope);
228 };
229
230 return {
231 'Program:exit'() {
232 checkScope(context.getScope());
233 }
234 };
235};
236
237module.exports = {
238 create,
239 meta: {
240 type: 'suggestion',
241 docs: {
242 url: getDocumentationUrl(__filename)
243 }
244 }
245};