d6ec43bf1d6aac1ccfb557c0ee8c815f77057bb6
[platform/framework/web/crosswalk-tizen.git] /
1 /**
2  * @fileoverview Rule to flag declared but unused variables
3  * @author Ilya Volodin
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = function(context) {
13
14     var MESSAGE = "{{name}} is defined but never used";
15
16     var config = {
17         vars: "all",
18         args: "after-used"
19     };
20
21     if (context.options[0]) {
22         if (typeof context.options[0] === "string") {
23             config.vars = context.options[0];
24         } else {
25             config.vars = context.options[0].vars || config.vars;
26             config.args = context.options[0].args || config.args;
27         }
28     }
29
30     /**
31      * Determines if a given variable is being exported from a module.
32      * @param {Variable} variable EScope variable object.
33      * @returns {boolean} True if the variable is exported, false if not.
34      * @private
35      */
36     function isExported(variable) {
37
38         var definition = variable.defs[0];
39
40         if (definition) {
41
42             definition = definition.node;
43             if (definition.type === "VariableDeclarator") {
44                 definition = definition.parent;
45             }
46
47             return definition.parent.type.indexOf("Export") === 0;
48         } else {
49             return false;
50         }
51     }
52
53     /**
54      * Determines if a reference is a read operation.
55      * @param {Reference} ref - an escope Reference
56      * @returns {Boolean} whether the given reference represents a read operation
57      * @private
58      */
59     function isReadRef(ref) {
60         return ref.isRead();
61     }
62
63     /**
64      * Determine if an identifier is referencing the enclosing function name.
65      * @param {Reference} ref The reference to check.
66      * @returns {boolean} True if it's a self-reference, false if not.
67      * @private
68      */
69     function isSelfReference(ref) {
70
71         if (ref.from.type === "function" && ref.from.block.id) {
72             return ref.identifier.name === ref.from.block.id.name;
73         }
74
75         return false;
76     }
77
78     /**
79      * Determines if a reference should be counted as a read. A reference should
80      * be counted only if it's a read and it's not a reference to the containing
81      * function declaration name.
82      * @param {Reference} ref The reference to check.
83      * @returns {boolean} True if it's a value read reference, false if not.
84      * @private
85      */
86     function isValidReadRef(ref) {
87         return isReadRef(ref) && !isSelfReference(ref);
88     }
89
90     /**
91      * Gets an array of local variables without read references.
92      * @param {Scope} scope - an escope Scope object
93      * @returns {Variable[]} most of the local variables with no read references
94      * @private
95      */
96     function getUnusedLocals(scope) {
97         var unused = [];
98         var variables = scope.variables;
99
100         if (scope.type !== "global" && scope.type !== "TDZ") {
101             for (var i = 0, l = variables.length; i < l; ++i) {
102
103                 // skip function expression names
104                 if (scope.functionExpressionScope || variables[i].eslintUsed) {
105                     continue;
106                 }
107                 // skip implicit "arguments" variable
108                 if (scope.type === "function" && variables[i].name === "arguments" && variables[i].identifiers.length === 0) {
109                     continue;
110                 }
111
112                 var def = variables[i].defs[0],
113                     type = def.type;
114
115                 // skip catch variables
116                 if (type === "CatchClause") {
117                     continue;
118                 }
119
120                 // skip any setter argument
121                 if (type === "Parameter" && def.node.parent.type === "Property" && def.node.parent.kind === "set") {
122                     continue;
123                 }
124
125                 // if "args" option is "none", skip any parameter
126                 if (config.args === "none" && type === "Parameter") {
127                     continue;
128                 }
129
130                 // if "args" option is "after-used", skip all but the last parameter
131                 if (config.args === "after-used" && type === "Parameter" && variables[i].defs[0].index < variables[i].defs[0].node.params.length - 1) {
132                     continue;
133                 }
134
135                 if (variables[i].references.filter(isValidReadRef).length === 0 && !isExported(variables[i])) {
136                     unused.push(variables[i]);
137                 }
138             }
139         }
140
141         return [].concat.apply(unused, scope.childScopes.map(getUnusedLocals));
142     }
143
144     return {
145         "Program:exit": function(programNode) {
146             var globalScope = context.getScope();
147             var unused = getUnusedLocals(globalScope);
148             var i, l;
149
150             // determine unused globals
151             if (config.vars === "all") {
152                 var unresolvedRefs = globalScope.through.filter(isValidReadRef).map(function(ref) {
153                     return ref.identifier.name;
154                 });
155
156                 for (i = 0, l = globalScope.variables.length; i < l; ++i) {
157                     if (unresolvedRefs.indexOf(globalScope.variables[i].name) < 0 &&
158                             !globalScope.variables[i].eslintUsed && !isExported(globalScope.variables[i])) {
159                         unused.push(globalScope.variables[i]);
160                     }
161                 }
162             }
163
164             for (i = 0, l = unused.length; i < l; ++i) {
165                 if (unused[i].eslintExplicitGlobal) {
166                     context.report(programNode, MESSAGE, unused[i]);
167                 } else if (unused[i].defs.length > 0) {
168
169                     // TODO: Remove when https://github.com/estools/escope/issues/49 is resolved
170                     if (unused[i].defs[0].type === "ClassName") {
171                         continue;
172                     }
173
174                     context.report(unused[i].identifiers[0], MESSAGE, unused[i]);
175                 }
176             }
177         }
178     };
179
180 };