eslint 0.22.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +111 -95
  3. package/bin/eslint.js +41 -41
  4. package/conf/environments.js +87 -81
  5. package/conf/eslint.json +186 -179
  6. package/lib/api.js +13 -12
  7. package/lib/cli-engine.js +441 -451
  8. package/lib/cli.js +196 -196
  9. package/lib/config-initializer.js +145 -145
  10. package/lib/config-validator.js +110 -110
  11. package/lib/config.js +428 -416
  12. package/lib/eslint.js +1072 -1073
  13. package/lib/file-finder.js +167 -167
  14. package/lib/formatters/checkstyle.js +68 -68
  15. package/lib/formatters/compact.js +53 -53
  16. package/lib/formatters/jslint-xml.js +40 -40
  17. package/lib/formatters/junit.js +63 -63
  18. package/lib/formatters/stylish.js +90 -90
  19. package/lib/formatters/tap.js +86 -86
  20. package/lib/ignored-paths.js +137 -137
  21. package/lib/load-rules.js +39 -39
  22. package/lib/options.js +132 -126
  23. package/lib/rule-context.js +107 -107
  24. package/lib/rules/accessor-pairs.js +65 -65
  25. package/lib/rules/array-bracket-spacing.js +180 -0
  26. package/lib/rules/block-scoped-var.js +339 -320
  27. package/lib/rules/brace-style.js +228 -228
  28. package/lib/rules/camelcase.js +111 -111
  29. package/lib/rules/comma-dangle.js +67 -64
  30. package/lib/rules/comma-spacing.js +191 -191
  31. package/lib/rules/comma-style.js +195 -195
  32. package/lib/rules/complexity.js +94 -94
  33. package/lib/rules/computed-property-spacing.js +144 -0
  34. package/lib/rules/consistent-return.js +75 -75
  35. package/lib/rules/consistent-this.js +119 -119
  36. package/lib/rules/constructor-super.js +108 -0
  37. package/lib/rules/curly.js +109 -109
  38. package/lib/rules/default-case.js +66 -66
  39. package/lib/rules/dot-location.js +63 -63
  40. package/lib/rules/dot-notation.js +119 -119
  41. package/lib/rules/eol-last.js +38 -38
  42. package/lib/rules/eqeqeq.js +96 -96
  43. package/lib/rules/func-names.js +45 -45
  44. package/lib/rules/func-style.js +49 -49
  45. package/lib/rules/generator-star-spacing.js +104 -87
  46. package/lib/rules/generator-star.js +76 -76
  47. package/lib/rules/global-strict.js +49 -49
  48. package/lib/rules/guard-for-in.js +32 -32
  49. package/lib/rules/handle-callback-err.js +81 -124
  50. package/lib/rules/indent.js +486 -486
  51. package/lib/rules/key-spacing.js +325 -325
  52. package/lib/rules/linebreak-style.js +44 -44
  53. package/lib/rules/lines-around-comment.js +228 -160
  54. package/lib/rules/max-depth.js +89 -89
  55. package/lib/rules/max-len.js +76 -76
  56. package/lib/rules/max-nested-callbacks.js +73 -73
  57. package/lib/rules/max-params.js +45 -45
  58. package/lib/rules/max-statements.js +61 -61
  59. package/lib/rules/new-cap.js +224 -224
  60. package/lib/rules/new-parens.js +29 -29
  61. package/lib/rules/newline-after-var.js +127 -127
  62. package/lib/rules/no-alert.js +153 -153
  63. package/lib/rules/no-array-constructor.js +31 -31
  64. package/lib/rules/no-bitwise.js +57 -57
  65. package/lib/rules/no-caller.js +29 -29
  66. package/lib/rules/no-catch-shadow.js +52 -52
  67. package/lib/rules/no-comma-dangle.js +45 -45
  68. package/lib/rules/no-cond-assign.js +123 -123
  69. package/lib/rules/no-console.js +27 -27
  70. package/lib/rules/no-constant-condition.js +73 -73
  71. package/lib/rules/no-continue.js +23 -23
  72. package/lib/rules/no-control-regex.js +58 -58
  73. package/lib/rules/no-debugger.js +22 -22
  74. package/lib/rules/no-delete-var.js +25 -25
  75. package/lib/rules/no-div-regex.js +27 -27
  76. package/lib/rules/no-dupe-args.js +89 -85
  77. package/lib/rules/no-dupe-keys.js +43 -43
  78. package/lib/rules/no-duplicate-case.js +67 -67
  79. package/lib/rules/no-else-return.js +125 -125
  80. package/lib/rules/no-empty-character-class.js +43 -43
  81. package/lib/rules/no-empty-class.js +45 -45
  82. package/lib/rules/no-empty-label.js +27 -27
  83. package/lib/rules/no-empty.js +49 -49
  84. package/lib/rules/no-eq-null.js +29 -29
  85. package/lib/rules/no-eval.js +26 -26
  86. package/lib/rules/no-ex-assign.js +42 -42
  87. package/lib/rules/no-extend-native.js +103 -103
  88. package/lib/rules/no-extra-bind.js +81 -81
  89. package/lib/rules/no-extra-boolean-cast.js +71 -71
  90. package/lib/rules/no-extra-parens.js +368 -355
  91. package/lib/rules/no-extra-semi.js +70 -23
  92. package/lib/rules/no-extra-strict.js +86 -86
  93. package/lib/rules/no-fallthrough.js +97 -97
  94. package/lib/rules/no-floating-decimal.js +30 -30
  95. package/lib/rules/no-func-assign.js +83 -83
  96. package/lib/rules/no-implied-eval.js +76 -76
  97. package/lib/rules/no-inline-comments.js +49 -49
  98. package/lib/rules/no-inner-declarations.js +78 -78
  99. package/lib/rules/no-invalid-regexp.js +53 -53
  100. package/lib/rules/no-irregular-whitespace.js +135 -135
  101. package/lib/rules/no-iterator.js +28 -28
  102. package/lib/rules/no-label-var.js +64 -64
  103. package/lib/rules/no-labels.js +44 -44
  104. package/lib/rules/no-lone-blocks.js +106 -27
  105. package/lib/rules/no-lonely-if.js +30 -30
  106. package/lib/rules/no-loop-func.js +58 -58
  107. package/lib/rules/no-mixed-requires.js +165 -165
  108. package/lib/rules/no-mixed-spaces-and-tabs.js +74 -74
  109. package/lib/rules/no-multi-spaces.js +119 -119
  110. package/lib/rules/no-multi-str.js +43 -43
  111. package/lib/rules/no-multiple-empty-lines.js +98 -98
  112. package/lib/rules/no-native-reassign.js +62 -62
  113. package/lib/rules/no-negated-in-lhs.js +25 -25
  114. package/lib/rules/no-nested-ternary.js +24 -24
  115. package/lib/rules/no-new-func.js +25 -25
  116. package/lib/rules/no-new-object.js +25 -25
  117. package/lib/rules/no-new-require.js +25 -25
  118. package/lib/rules/no-new-wrappers.js +26 -26
  119. package/lib/rules/no-new.js +27 -27
  120. package/lib/rules/no-obj-calls.js +28 -28
  121. package/lib/rules/no-octal-escape.js +39 -39
  122. package/lib/rules/no-octal.js +25 -25
  123. package/lib/rules/no-param-reassign.js +87 -87
  124. package/lib/rules/no-path-concat.js +39 -39
  125. package/lib/rules/no-plusplus.js +24 -24
  126. package/lib/rules/no-process-env.js +30 -30
  127. package/lib/rules/no-process-exit.js +33 -33
  128. package/lib/rules/no-proto.js +28 -28
  129. package/lib/rules/no-redeclare.js +68 -68
  130. package/lib/rules/no-regex-spaces.js +35 -35
  131. package/lib/rules/no-reserved-keys.js +56 -56
  132. package/lib/rules/no-restricted-modules.js +85 -85
  133. package/lib/rules/no-return-assign.js +53 -24
  134. package/lib/rules/no-script-url.js +34 -34
  135. package/lib/rules/no-self-compare.js +29 -29
  136. package/lib/rules/no-sequences.js +94 -94
  137. package/lib/rules/no-shadow-restricted-names.js +51 -51
  138. package/lib/rules/no-shadow.js +181 -136
  139. package/lib/rules/no-space-before-semi.js +98 -98
  140. package/lib/rules/no-spaced-func.js +37 -37
  141. package/lib/rules/no-sparse-arrays.js +33 -33
  142. package/lib/rules/no-sync.js +30 -30
  143. package/lib/rules/no-ternary.js +24 -24
  144. package/lib/rules/no-this-before-super.js +144 -0
  145. package/lib/rules/no-throw-literal.js +33 -33
  146. package/lib/rules/no-trailing-spaces.js +74 -63
  147. package/lib/rules/no-undef-init.js +28 -28
  148. package/lib/rules/no-undef.js +92 -92
  149. package/lib/rules/no-undefined.js +27 -27
  150. package/lib/rules/no-underscore-dangle.js +73 -73
  151. package/lib/rules/no-unexpected-multiline.js +58 -0
  152. package/lib/rules/no-unneeded-ternary.js +48 -48
  153. package/lib/rules/no-unreachable.js +98 -98
  154. package/lib/rules/no-unused-expressions.js +76 -76
  155. package/lib/rules/no-unused-vars.js +252 -250
  156. package/lib/rules/no-use-before-define.js +105 -105
  157. package/lib/rules/no-var.js +26 -26
  158. package/lib/rules/no-void.js +28 -28
  159. package/lib/rules/no-warning-comments.js +102 -102
  160. package/lib/rules/no-with.js +22 -22
  161. package/lib/rules/no-wrap-func.js +65 -65
  162. package/lib/rules/object-curly-spacing.js +231 -206
  163. package/lib/rules/object-shorthand.js +74 -73
  164. package/lib/rules/one-var.js +311 -304
  165. package/lib/rules/operator-assignment.js +118 -118
  166. package/lib/rules/operator-linebreak.js +114 -114
  167. package/lib/rules/padded-blocks.js +98 -98
  168. package/lib/rules/prefer-const.js +91 -0
  169. package/lib/rules/quote-props.js +72 -72
  170. package/lib/rules/quotes.js +92 -92
  171. package/lib/rules/radix.js +41 -41
  172. package/lib/rules/semi-spacing.js +167 -167
  173. package/lib/rules/semi.js +136 -136
  174. package/lib/rules/sort-vars.js +49 -49
  175. package/lib/rules/space-after-function-name.js +49 -49
  176. package/lib/rules/space-after-keywords.js +82 -82
  177. package/lib/rules/space-before-blocks.js +91 -91
  178. package/lib/rules/space-before-function-paren.js +139 -139
  179. package/lib/rules/space-before-function-parentheses.js +139 -139
  180. package/lib/rules/space-in-brackets.js +305 -305
  181. package/lib/rules/space-in-parens.js +281 -281
  182. package/lib/rules/space-infix-ops.js +106 -106
  183. package/lib/rules/space-return-throw-case.js +38 -38
  184. package/lib/rules/space-unary-ops.js +124 -133
  185. package/lib/rules/spaced-comment.js +143 -0
  186. package/lib/rules/spaced-line-comment.js +89 -89
  187. package/lib/rules/strict.js +242 -242
  188. package/lib/rules/use-isnan.js +26 -26
  189. package/lib/rules/valid-jsdoc.js +215 -215
  190. package/lib/rules/valid-typeof.js +42 -42
  191. package/lib/rules/vars-on-top.js +115 -115
  192. package/lib/rules/wrap-iife.js +48 -48
  193. package/lib/rules/wrap-regex.js +38 -38
  194. package/lib/rules/yoda.js +242 -225
  195. package/lib/rules.js +88 -88
  196. package/lib/timing.js +109 -109
  197. package/lib/token-store.js +201 -201
  198. package/lib/util/traverse.js +105 -105
  199. package/lib/util.js +125 -85
  200. package/package.json +6 -6
  201. package/CHANGELOG.md +0 -1638
package/lib/eslint.js CHANGED
@@ -1,1073 +1,1072 @@
1
- /**
2
- * @fileoverview Main ESLint object.
3
- * @author Nicholas C. Zakas
4
- */
5
- "use strict";
6
-
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- var estraverse = require("estraverse-fb"),
12
- escope = require("escope"),
13
- environments = require("../conf/environments"),
14
- assign = require("object-assign"),
15
- rules = require("./rules"),
16
- util = require("./util"),
17
- RuleContext = require("./rule-context"),
18
- timing = require("./timing"),
19
- createTokenStore = require("./token-store.js"),
20
- EventEmitter = require("events").EventEmitter,
21
- escapeRegExp = require("escape-string-regexp");
22
-
23
- //------------------------------------------------------------------------------
24
- // Helpers
25
- //------------------------------------------------------------------------------
26
-
27
- // TODO: Remove when estraverse is updated
28
- estraverse.Syntax.Super = "Super";
29
- estraverse.VisitorKeys.Super = [];
30
-
31
- /**
32
- * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
33
- * whitespace.
34
- * @param {string} string The string to parse.
35
- * @returns {Object} Result map object of names and boolean values
36
- */
37
- function parseBooleanConfig(string) {
38
- var items = {};
39
- // Collapse whitespace around : to make parsing easier
40
- string = string.replace(/\s*:\s*/g, ":");
41
- // Collapse whitespace around ,
42
- string = string.replace(/\s*,\s*/g, ",");
43
- string.split(/\s|,+/).forEach(function(name) {
44
- if (!name) {
45
- return;
46
- }
47
- var pos = name.indexOf(":"),
48
- value;
49
- if (pos !== -1) {
50
- value = name.substring(pos + 1, name.length);
51
- name = name.substring(0, pos);
52
- }
53
-
54
- items[name] = (value === "true");
55
-
56
- });
57
- return items;
58
- }
59
-
60
- /**
61
- * Parses a JSON-like config.
62
- * @param {string} string The string to parse.
63
- * @param {Object} location Start line and column of comments for potential error message.
64
- * @param {Object[]} messages The messages queue for potential error message.
65
- * @returns {Object} Result map object
66
- */
67
- function parseJsonConfig(string, location, messages) {
68
- var items = {};
69
- string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
70
- try {
71
- items = JSON.parse("{" + string + "}");
72
- } catch(ex) {
73
-
74
- messages.push({
75
- fatal: true,
76
- severity: 2,
77
- message: "Failed to parse JSON from '" + string + "': " + ex.message,
78
- line: location.start.line,
79
- column: location.start.column
80
- });
81
-
82
- }
83
-
84
- return items;
85
- }
86
-
87
- /**
88
- * Parses a config of values separated by comma.
89
- * @param {string} string The string to parse.
90
- * @returns {Object} Result map of values and true values
91
- */
92
- function parseListConfig(string) {
93
- var items = {};
94
- // Collapse whitespace around ,
95
- string = string.replace(/\s*,\s*/g, ",");
96
- string.split(/,+/).forEach(function(name) {
97
- name = name.trim();
98
- if (!name) {
99
- return;
100
- }
101
- items[name] = true;
102
- });
103
- return items;
104
- }
105
-
106
- /**
107
- * @param {Scope} scope The scope object to check.
108
- * @param {string} name The name of the variable to look up.
109
- * @returns {Variable} The variable object if found or null if not.
110
- */
111
- function getVariable(scope, name) {
112
- var variable = null;
113
- scope.variables.some(function(v) {
114
- if (v.name === name) {
115
- variable = v;
116
- return true;
117
- } else {
118
- return false;
119
- }
120
-
121
- });
122
- return variable;
123
- }
124
-
125
- /**
126
- * Ensures that variables representing built-in properties of the Global Object,
127
- * and any globals declared by special block comments, are present in the global
128
- * scope.
129
- * @param {ASTNode} program The top node of the AST.
130
- * @param {Scope} globalScope The global scope.
131
- * @param {Object} config The existing configuration data.
132
- * @returns {void}
133
- */
134
- function addDeclaredGlobals(program, globalScope, config) {
135
- var declaredGlobals = {},
136
- explicitGlobals = {},
137
- builtin = environments.builtin;
138
-
139
- assign(declaredGlobals, builtin);
140
-
141
- Object.keys(config.env).forEach(function (name) {
142
- if (config.env[name]) {
143
- var environmentGlobals = environments[name] && environments[name].globals;
144
- if (environmentGlobals) {
145
- assign(declaredGlobals, environmentGlobals);
146
- }
147
- }
148
- });
149
-
150
- assign(declaredGlobals, config.globals);
151
- assign(explicitGlobals, config.astGlobals);
152
-
153
- Object.keys(declaredGlobals).forEach(function(name) {
154
- var variable = getVariable(globalScope, name);
155
- if (!variable) {
156
- variable = new escope.Variable(name, globalScope);
157
- variable.eslintExplicitGlobal = false;
158
- globalScope.variables.push(variable);
159
- }
160
- variable.writeable = declaredGlobals[name];
161
- });
162
-
163
- Object.keys(explicitGlobals).forEach(function(name) {
164
- var variable = getVariable(globalScope, name);
165
- if (!variable) {
166
- variable = new escope.Variable(name, globalScope);
167
- variable.eslintExplicitGlobal = true;
168
- globalScope.variables.push(variable);
169
- }
170
- variable.writeable = explicitGlobals[name];
171
- });
172
- }
173
-
174
- /**
175
- * Add data to reporting configuration to disable reporting for list of rules
176
- * starting from start location
177
- * @param {Object[]} reportingConfig Current reporting configuration
178
- * @param {Object} start Position to start
179
- * @param {string[]} rulesToDisable List of rules
180
- * @returns {void}
181
- */
182
- function disableReporting(reportingConfig, start, rulesToDisable) {
183
-
184
- if (rulesToDisable.length) {
185
- rulesToDisable.forEach(function(rule) {
186
- reportingConfig.push({
187
- start: start,
188
- end: null,
189
- rule: rule
190
- });
191
- });
192
- } else {
193
- reportingConfig.push({
194
- start: start,
195
- end: null,
196
- rule: null
197
- });
198
- }
199
- }
200
-
201
- /**
202
- * Add data to reporting configuration to enable reporting for list of rules
203
- * starting from start location
204
- * @param {Object[]} reportingConfig Current reporting configuration
205
- * @param {Object} start Position to start
206
- * @param {string[]} rulesToEnable List of rules
207
- * @returns {void}
208
- */
209
- function enableReporting(reportingConfig, start, rulesToEnable) {
210
- var i;
211
-
212
- if (rulesToEnable.length) {
213
- rulesToEnable.forEach(function(rule) {
214
- for (i = reportingConfig.length - 1; i >= 0; i--) {
215
- if (!reportingConfig[i].end && reportingConfig[i].rule === rule ) {
216
- reportingConfig[i].end = start;
217
- break;
218
- }
219
- }
220
- });
221
- } else {
222
- // find all previous disabled locations if they was started as list of rules
223
- var prevStart;
224
- for (i = reportingConfig.length - 1; i >= 0; i--) {
225
- if (prevStart && prevStart !== reportingConfig[i].start) {
226
- break;
227
- }
228
-
229
- if (!reportingConfig[i].end) {
230
- reportingConfig[i].end = start;
231
- prevStart = reportingConfig[i].start;
232
- }
233
- }
234
- }
235
- }
236
-
237
- /**
238
- * Parses comments in file to extract file-specific config of rules, globals
239
- * and environments and merges them with global config; also code blocks
240
- * where reporting is disabled or enabled and merges them with reporting config.
241
- * @param {ASTNode} ast The top node of the AST.
242
- * @param {Object} config The existing configuration data.
243
- * @param {Object[]} reportingConfig The existing reporting configuration data.
244
- * @param {Object[]} messages The messages queue.
245
- * @returns {void}
246
- */
247
- function modifyConfigsFromComments(ast, config, reportingConfig, messages) {
248
-
249
- var commentConfig = {
250
- astGlobals: {},
251
- rules: {},
252
- env: {}
253
- };
254
- var commentRules = {};
255
-
256
- ast.comments.forEach(function(comment) {
257
-
258
- var value = comment.value.trim();
259
- var match = /^(eslint-\w+|eslint-\w+-\w+|eslint|globals?)(\s|$)/.exec(value);
260
-
261
- if (match) {
262
- value = value.substring(match.index + match[1].length);
263
-
264
- if (comment.type === "Block") {
265
- switch (match[1]) {
266
- case "globals":
267
- case "global":
268
- assign(commentConfig.astGlobals, parseBooleanConfig(value));
269
- break;
270
-
271
- case "eslint-env":
272
- assign(commentConfig.env, parseListConfig(value));
273
- break;
274
-
275
- case "eslint-disable":
276
- disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
277
- break;
278
-
279
- case "eslint-enable":
280
- enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
281
- break;
282
-
283
- case "eslint":
284
- var items = parseJsonConfig(value, comment.loc, messages);
285
- Object.keys(items).forEach(function(name) {
286
- var ruleValue = items[name];
287
- if (typeof ruleValue === "number" || (Array.isArray(ruleValue) && typeof ruleValue[0] === "number")) {
288
- commentRules[name] = ruleValue;
289
- }
290
- });
291
- break;
292
-
293
- // no default
294
- }
295
- } else {
296
- // comment.type === "Line"
297
- if (match[1] === "eslint-disable-line") {
298
- disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value)));
299
- enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
300
- }
301
- }
302
- }
303
- });
304
-
305
- // apply environment configs
306
- Object.keys(commentConfig.env).forEach(function (name) {
307
- if (environments[name]) {
308
- util.mergeConfigs(commentConfig, environments[name]);
309
- }
310
- });
311
- assign(commentConfig.rules, commentRules);
312
-
313
- util.mergeConfigs(config, commentConfig);
314
- }
315
-
316
- /**
317
- * Check if message of rule with ruleId should be ignored in location
318
- * @param {Object[]} reportingConfig Collection of ignore records
319
- * @param {string} ruleId Id of rule
320
- * @param {Object} location Location of message
321
- * @returns {boolean} True if message should be ignored, false otherwise
322
- */
323
- function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
324
-
325
- for (var i = 0, c = reportingConfig.length; i < c; i++) {
326
-
327
- var ignore = reportingConfig[i];
328
- if ((!ignore.rule || ignore.rule === ruleId) &&
329
- (location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
330
- (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
331
- return true;
332
- }
333
- }
334
-
335
- return false;
336
- }
337
-
338
- /**
339
- * Process initial config to make it safe to extend by file comment config
340
- * @param {Object} config Initial config
341
- * @returns {Object} Processed config
342
- */
343
- function prepareConfig(config) {
344
-
345
- config.globals = config.globals || config.global || {};
346
- delete config.global;
347
-
348
- var copiedRules = {},
349
- ecmaFeatures = {},
350
- preparedConfig;
351
-
352
- if (typeof config.rules === "object") {
353
- Object.keys(config.rules).forEach(function(k) {
354
- var rule = config.rules[k];
355
- if (rule === null) {
356
- throw new Error("Invalid config for rule '" + k + "'\.");
357
- }
358
- if (Array.isArray(rule)) {
359
- copiedRules[k] = rule.slice();
360
- } else {
361
- copiedRules[k] = rule;
362
- }
363
- });
364
- }
365
-
366
- // merge in environment ecmaFeatures
367
- if (typeof config.env === "object") {
368
- Object.keys(config.env).forEach(function(env) {
369
- if (config.env[env] && environments[env].ecmaFeatures) {
370
- assign(ecmaFeatures, environments[env].ecmaFeatures);
371
- }
372
- });
373
- }
374
-
375
- preparedConfig = {
376
- rules: copiedRules,
377
- parser: config.parser || "espree",
378
- globals: util.mergeConfigs({}, config.globals),
379
- env: util.mergeConfigs({}, config.env || {}),
380
- settings: util.mergeConfigs({}, config.settings || {}),
381
- ecmaFeatures: util.mergeConfigs(ecmaFeatures, config.ecmaFeatures || {})
382
- };
383
-
384
- // can't have global return inside of modules
385
- if (preparedConfig.ecmaFeatures.modules) {
386
- preparedConfig.ecmaFeatures.globalReturn = false;
387
- }
388
-
389
- return preparedConfig;
390
- }
391
-
392
- //------------------------------------------------------------------------------
393
- // Public Interface
394
- //------------------------------------------------------------------------------
395
-
396
- /**
397
- * Object that is responsible for verifying JavaScript text
398
- * @name eslint
399
- */
400
- module.exports = (function() {
401
-
402
- var api = Object.create(new EventEmitter()),
403
- messages = [],
404
- currentText = null,
405
- currentTextLines = [],
406
- currentConfig = null,
407
- currentTokens = null,
408
- currentScopes = null,
409
- scopeMap = null,
410
- scopeManager = null,
411
- currentFilename = null,
412
- controller = null,
413
- reportingConfig = [],
414
- commentLocsEnter = [],
415
- commentLocsExit = [],
416
- currentAST = null;
417
-
418
- /**
419
- * Parses text into an AST. Moved out here because the try-catch prevents
420
- * optimization of functions, so it's best to keep the try-catch as isolated
421
- * as possible
422
- * @param {string} text The text to parse.
423
- * @param {Object} config The ESLint configuration object.
424
- * @returns {ASTNode} The AST if successful or null if not.
425
- * @private
426
- */
427
- function parse(text, config) {
428
-
429
- var parser;
430
-
431
- try {
432
- parser = require(config.parser);
433
- } catch (ex) {
434
- messages.push({
435
- fatal: true,
436
- severity: 2,
437
- message: ex.message,
438
- line: 0,
439
- column: 0
440
- });
441
-
442
- return null;
443
- }
444
-
445
- /*
446
- * Check for parsing errors first. If there's a parsing error, nothing
447
- * else can happen. However, a parsing error does not throw an error
448
- * from this method - it's just considered a fatal error message, a
449
- * problem that ESLint identified just like any other.
450
- */
451
- try {
452
- return parser.parse(text, {
453
- loc: true,
454
- range: true,
455
- raw: true,
456
- tokens: true,
457
- comment: true,
458
- attachComment: true,
459
- ecmaFeatures: config.ecmaFeatures
460
- });
461
- } catch (ex) {
462
-
463
- messages.push({
464
- fatal: true,
465
- severity: 2,
466
-
467
- // messages come as "Line X: Unexpected token foo", so strip off leading part
468
- message: ex.message.substring(ex.message.indexOf(":") + 1).trim(),
469
-
470
- line: ex.lineNumber,
471
- column: ex.column
472
- });
473
-
474
- return null;
475
- }
476
- }
477
-
478
- /**
479
- * Check collection of comments to prevent double event for comment as
480
- * leading and trailing, then emit event if passing
481
- * @param {ASTNode[]} comments Collection of comment nodes
482
- * @param {Object[]} locs List of locations of previous comment nodes
483
- * @param {string} eventName Event name postfix
484
- * @returns {void}
485
- */
486
- function emitComments(comments, locs, eventName) {
487
-
488
- if (comments.length) {
489
- comments.forEach(function(node) {
490
- if (locs.indexOf(node.loc) >= 0) {
491
- locs.splice(locs.indexOf(node.loc), 1);
492
- } else {
493
- locs.push(node.loc);
494
- api.emit(node.type + eventName, node);
495
- }
496
- });
497
- }
498
- }
499
-
500
- /**
501
- * Shortcut to check and emit enter of comment nodes
502
- * @param {ASTNode[]} comments Collection of comment nodes
503
- * @returns {void}
504
- */
505
- function emitCommentsEnter(comments) {
506
- emitComments(comments, commentLocsEnter, "Comment");
507
- }
508
-
509
- /**
510
- * Shortcut to check and emit exit of comment nodes
511
- * @param {ASTNode[]} comments Collection of comment nodes
512
- * @returns {void}
513
- */
514
- function emitCommentsExit(comments) {
515
- emitComments(comments, commentLocsExit, "Comment:exit");
516
- }
517
-
518
- /**
519
- * Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
520
- * Returns 0 if the rule config is not valid (an Array or a number)
521
- * @param {Array|number} ruleConfig rule configuration
522
- * @returns {number} 0, 1, or 2, indicating rule severity
523
- */
524
- function getRuleSeverity(ruleConfig) {
525
- if (typeof ruleConfig === "number") {
526
- return ruleConfig;
527
- } else if (Array.isArray(ruleConfig)) {
528
- return ruleConfig[0];
529
- } else {
530
- return 0;
531
- }
532
- }
533
-
534
- /**
535
- * Get the options for a rule (not including severity), if any
536
- * @param {Array|number} ruleConfig rule configuration
537
- * @returns {Array} of rule options, empty Array if none
538
- */
539
- function getRuleOptions(ruleConfig) {
540
- if (Array.isArray(ruleConfig)) {
541
- return ruleConfig.slice(1);
542
- } else {
543
- return [];
544
- }
545
- }
546
-
547
- // set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
548
- api.setMaxListeners(0);
549
-
550
- /**
551
- * Resets the internal state of the object.
552
- * @returns {void}
553
- */
554
- api.reset = function() {
555
- this.removeAllListeners();
556
- messages = [];
557
- currentAST = null;
558
- currentConfig = null;
559
- currentText = null;
560
- currentTextLines = [];
561
- currentTokens = null;
562
- currentScopes = null;
563
- scopeMap = null;
564
- scopeManager = null;
565
- controller = null;
566
- reportingConfig = [];
567
- commentLocsEnter = [];
568
- commentLocsExit = [];
569
- };
570
-
571
- /**
572
- * Verifies the text against the rules specified by the second argument.
573
- * @param {string} text The JavaScript text to verify.
574
- * @param {Object} config An object whose keys specify the rules to use.
575
- * @param {string=} filename The optional filename of the file being checked.
576
- * If this is not set, the filename will default to '<input>' in the rule context.
577
- * @param {boolean=} saveState Indicates if the state from the last run should be saved.
578
- * Mostly useful for testing purposes.
579
- * @returns {Object[]} The results as an array of messages or null if no messages.
580
- */
581
- api.verify = function(text, config, filename, saveState) {
582
-
583
- var ast,
584
- shebang,
585
- ecmaFeatures,
586
- ecmaVersion;
587
-
588
- // set the current parsed filename
589
- currentFilename = filename;
590
-
591
- if (!saveState) {
592
- this.reset();
593
- }
594
-
595
- // there's no input, just exit here
596
- if (text.trim().length === 0) {
597
- currentText = text;
598
- return messages;
599
- }
600
-
601
- // process initial config to make it safe to extend
602
- config = prepareConfig(config || {});
603
-
604
- ast = parse(text.replace(/^#!([^\r\n]+)/, function(match, captured) {
605
- shebang = captured;
606
- return "//" + captured;
607
- }), config);
608
-
609
- // if espree failed to parse the file, there's no sense in setting up rules
610
- if (ast) {
611
-
612
- currentAST = ast;
613
-
614
- // parse global comments and modify config
615
- modifyConfigsFromComments(ast, config, reportingConfig, messages);
616
-
617
- // enable appropriate rules
618
- Object.keys(config.rules).filter(function(key) {
619
- return getRuleSeverity(config.rules[key]) > 0;
620
- }).forEach(function(key) {
621
-
622
- var ruleCreator = rules.get(key),
623
- severity = getRuleSeverity(config.rules[key]),
624
- options = getRuleOptions(config.rules[key]),
625
- rule;
626
-
627
- if (ruleCreator) {
628
- try {
629
- rule = ruleCreator(new RuleContext(
630
- key, api, severity, options,
631
- config.settings, config.ecmaFeatures
632
- ));
633
-
634
- // add all the node types as listeners
635
- Object.keys(rule).forEach(function(nodeType) {
636
- api.on(nodeType, timing.enabled
637
- ? timing.time(key, rule[nodeType])
638
- : rule[nodeType]
639
- );
640
- });
641
- } catch(ex) {
642
- ex.message = "Error while loading rule '" + key + "': " + ex.message;
643
- throw ex;
644
- }
645
-
646
- } else {
647
- throw new Error("Definition for rule '" + key + "' was not found.");
648
- }
649
- });
650
-
651
- // save config so rules can access as necessary
652
- currentConfig = config;
653
- currentText = text;
654
- controller = new estraverse.Controller();
655
-
656
- ecmaFeatures = currentConfig.ecmaFeatures;
657
- ecmaVersion = (ecmaFeatures.blockBindings || ecmaFeatures.classes ||
658
- ecmaFeatures.modules || ecmaFeatures.defaultParams ||
659
- ecmaFeatures.destructuring) ? 6 : 5;
660
-
661
-
662
- // gather data that may be needed by the rules
663
- scopeManager = escope.analyze(ast, {
664
- ignoreEval: true,
665
- nodejsScope: ecmaFeatures.globalReturn,
666
- ecmaVersion: ecmaVersion,
667
- sourceType: ecmaFeatures.modules ? "module" : "script"
668
- });
669
- currentScopes = scopeManager.scopes;
670
-
671
- /*
672
- * Index the scopes by the start range of their block for efficient
673
- * lookup in getScope.
674
- */
675
- scopeMap = [];
676
- currentScopes.forEach(function (scope, index) {
677
- var range = scope.block.range[0];
678
-
679
- // Sometimes two scopes are returned for a given node. This is
680
- // handled later in a known way, so just don't overwrite here.
681
- if (!scopeMap[range]) {
682
- scopeMap[range] = index;
683
- }
684
- });
685
-
686
- /*
687
- * Split text here into array of lines so
688
- * it's not being done repeatedly
689
- * by individual rules.
690
- */
691
- currentTextLines = currentText.split(/\r\n|\r|\n|\u2028|\u2029/g);
692
-
693
- // Freezing so array isn't accidentally changed by a rule.
694
- Object.freeze(currentTextLines);
695
-
696
- currentTokens = createTokenStore(ast.tokens);
697
- Object.keys(currentTokens).forEach(function(method) {
698
- api[method] = currentTokens[method];
699
- });
700
-
701
- // augment global scope with declared global variables
702
- addDeclaredGlobals(ast, currentScopes[0], currentConfig);
703
-
704
- // remove shebang comments
705
- if (shebang && ast.comments.length && ast.comments[0].value === shebang) {
706
- ast.comments.splice(0, 1);
707
-
708
- if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) {
709
- ast.body[0].leadingComments.splice(0, 1);
710
- }
711
- }
712
-
713
- /*
714
- * Each node has a type property. Whenever a particular type of node is found,
715
- * an event is fired. This allows any listeners to automatically be informed
716
- * that this type of node has been found and react accordingly.
717
- */
718
- controller.traverse(ast, {
719
- enter: function(node, parent) {
720
-
721
- var comments = api.getComments(node);
722
-
723
- emitCommentsEnter(comments.leading);
724
- node.parent = parent;
725
- api.emit(node.type, node);
726
- emitCommentsEnter(comments.trailing);
727
- },
728
- leave: function(node) {
729
-
730
- var comments = api.getComments(node);
731
-
732
- emitCommentsExit(comments.trailing);
733
- api.emit(node.type + ":exit", node);
734
- emitCommentsExit(comments.leading);
735
- }
736
- });
737
-
738
- }
739
-
740
- // sort by line and column
741
- messages.sort(function(a, b) {
742
- var lineDiff = a.line - b.line;
743
-
744
- if (lineDiff === 0) {
745
- return a.column - b.column;
746
- } else {
747
- return lineDiff;
748
- }
749
- });
750
-
751
- return messages;
752
- };
753
-
754
- /**
755
- * Reports a message from one of the rules.
756
- * @param {string} ruleId The ID of the rule causing the message.
757
- * @param {number} severity The severity level of the rule as configured.
758
- * @param {ASTNode} node The AST node that the message relates to.
759
- * @param {Object=} location An object containing the error line and column
760
- * numbers. If location is not provided the node's start location will
761
- * be used.
762
- * @param {string} message The actual message.
763
- * @param {Object} opts Optional template data which produces a formatted message
764
- * with symbols being replaced by this object's values.
765
- * @returns {void}
766
- */
767
- api.report = function(ruleId, severity, node, location, message, opts) {
768
-
769
- if (typeof location === "string") {
770
- opts = message;
771
- message = location;
772
- location = node.loc.start;
773
- }
774
-
775
- Object.keys(opts || {}).forEach(function (key) {
776
- var rx = new RegExp("{{" + escapeRegExp(key) + "}}", "g");
777
- message = message.replace(rx, opts[key]);
778
- });
779
-
780
- if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
781
- return;
782
- }
783
-
784
- messages.push({
785
- ruleId: ruleId,
786
- severity: severity,
787
- message: message,
788
- line: location.line,
789
- column: location.column,
790
- nodeType: node.type,
791
- source: currentTextLines[location.line - 1] || ""
792
- });
793
- };
794
-
795
- /**
796
- * Gets the source code for the given node.
797
- * @param {ASTNode=} node The AST node to get the text for.
798
- * @param {int=} beforeCount The number of characters before the node to retrieve.
799
- * @param {int=} afterCount The number of characters after the node to retrieve.
800
- * @returns {string} The text representing the AST node.
801
- */
802
- api.getSource = function(node, beforeCount, afterCount) {
803
- if (node) {
804
- return (currentText !== null) ? currentText.slice(Math.max(node.range[0] - (beforeCount || 0), 0),
805
- node.range[1] + (afterCount || 0)) : null;
806
- } else {
807
- return currentText;
808
- }
809
-
810
- };
811
-
812
- /**
813
- * Gets the entire source text split into an array of lines.
814
- * @returns {Array} The source text as an array of lines.
815
- */
816
- api.getSourceLines = function() {
817
- return currentTextLines;
818
- };
819
-
820
- /**
821
- * Retrieves an array containing all comments in the source code.
822
- * @returns {ASTNode[]} An array of comment nodes.
823
- */
824
- api.getAllComments = function() {
825
- return currentAST.comments;
826
- };
827
-
828
- /**
829
- * Gets all comments for the given node.
830
- * @param {ASTNode} node The AST node to get the comments for.
831
- * @returns {Object} The list of comments indexed by their position.
832
- */
833
- api.getComments = function(node) {
834
-
835
- var leadingComments = node.leadingComments || [],
836
- trailingComments = node.trailingComments || [];
837
-
838
- /*
839
- * espree adds a "comments" array on Program nodes rather than
840
- * leadingComments/trailingComments. Comments are only left in the
841
- * Program node comments array if there is no executable code.
842
- */
843
- if (node.type === "Program") {
844
- if (node.body.length === 0) {
845
- leadingComments = node.comments;
846
- }
847
- }
848
-
849
- return {
850
- leading: leadingComments,
851
- trailing: trailingComments
852
- };
853
- };
854
-
855
- /**
856
- * Retrieves the JSDoc comment for a given node.
857
- * @param {ASTNode} node The AST node to get the comment for.
858
- * @returns {ASTNode} The BlockComment node containing the JSDoc for the
859
- * given node or null if not found.
860
- */
861
- api.getJSDocComment = function(node) {
862
-
863
- var parent = node.parent,
864
- line = node.loc.start.line;
865
-
866
- /**
867
- * Finds a JSDoc comment node in an array of comment nodes.
868
- * @param {ASTNode[]} comments The array of comment nodes to search.
869
- * @returns {ASTNode} The node if found, null if not.
870
- * @private
871
- */
872
- function findJSDocComment(comments) {
873
-
874
- if (comments) {
875
- for (var i = comments.length - 1; i >= 0; i--) {
876
- if (comments[i].type === "Block" && comments[i].value.charAt(0) === "*") {
877
-
878
- if (line - comments[i].loc.end.line <= 1) {
879
- return comments[i];
880
- } else {
881
- break;
882
- }
883
- }
884
- }
885
- }
886
-
887
- return null;
888
- }
889
-
890
- /**
891
- * Check to see if its a ES6 export declaration
892
- * @param {ASTNode} astNode - any node
893
- * @returns {boolean} whether the given node represents a export declaration
894
- */
895
- function looksLikeExport(astNode) {
896
- return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" ||
897
- astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier";
898
- }
899
-
900
- switch (node.type) {
901
- case "FunctionDeclaration":
902
- if (looksLikeExport(parent)) {
903
- return findJSDocComment(parent.leadingComments);
904
- } else {
905
- return findJSDocComment(node.leadingComments);
906
- }
907
- break;
908
-
909
- case "ArrowFunctionExpression":
910
- case "FunctionExpression":
911
-
912
- if (parent.type !== "CallExpression" || parent.callee !== node) {
913
- while (parent && !parent.leadingComments && !/Function/.test(parent.type)) {
914
- parent = parent.parent;
915
- }
916
-
917
- return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parent.leadingComments) : null;
918
- }
919
-
920
- // falls through
921
-
922
- default:
923
- return null;
924
- }
925
- };
926
-
927
- /**
928
- * Gets nodes that are ancestors of current node.
929
- * @returns {ASTNode[]} Array of objects representing ancestors.
930
- */
931
- api.getAncestors = function() {
932
- return controller.parents();
933
- };
934
-
935
- /**
936
- * Gets the deepest node containing a range index.
937
- * @param {int} index Range index of the desired node.
938
- * @returns {ASTNode} [description]
939
- */
940
- api.getNodeByRangeIndex = function(index) {
941
- var result = null;
942
-
943
- estraverse.traverse(controller.root, {
944
- enter: function (node) {
945
- if (node.range[0] <= index && index < node.range[1]) {
946
- result = node;
947
- } else {
948
- this.skip();
949
- }
950
- },
951
- leave: function (node) {
952
- if (node === result) {
953
- this.break();
954
- }
955
- }
956
- });
957
-
958
- return result;
959
- };
960
-
961
- /**
962
- * Gets the scope for the current node.
963
- * @returns {Object} An object representing the current node's scope.
964
- */
965
- api.getScope = function() {
966
- var parents = controller.parents(),
967
- scope = currentScopes[0];
968
-
969
- // Don't do this for Program nodes - they have no parents
970
- if (parents.length) {
971
-
972
- // if current node is function declaration, add it to the list
973
- var current = controller.current();
974
- if (["FunctionDeclaration", "FunctionExpression",
975
- "ArrowFunctionExpression", "SwitchStatement"].indexOf(current.type) >= 0) {
976
- parents.push(current);
977
- }
978
-
979
- // Ascend the current node's parents
980
- for (var i = parents.length - 1; i >= 0; --i) {
981
-
982
- scope = scopeManager.acquire(parents[i]);
983
- if (scope) {
984
- if (scope.type === "function-expression-name") {
985
- return scope.childScopes[0];
986
- } else {
987
- return scope;
988
- }
989
- }
990
-
991
- }
992
-
993
- }
994
-
995
- return currentScopes[0];
996
- };
997
-
998
- /**
999
- * Record that a particular variable has been used in code
1000
- * @param {string} name The name of the variable to mark as used
1001
- * @returns {boolean} True if the variable was found and marked as used,
1002
- * false if not.
1003
- */
1004
- api.markVariableAsUsed = function(name) {
1005
- var scope = this.getScope(),
1006
- specialScope = currentConfig.ecmaFeatures.globalReturn || currentConfig.ecmaFeatures.modules,
1007
- variables,
1008
- i,
1009
- len;
1010
-
1011
- // Special Node.js scope means we need to start one level deeper
1012
- if (scope.type === "global" && specialScope) {
1013
- scope = scope.childScopes[0];
1014
- }
1015
-
1016
- do {
1017
- variables = scope.variables;
1018
- for (i = 0, len = variables.length; i < len; i++) {
1019
- if (variables[i].name === name) {
1020
- variables[i].eslintUsed = true;
1021
- return true;
1022
- }
1023
- }
1024
- } while ( (scope = scope.upper) );
1025
-
1026
- return false;
1027
- };
1028
-
1029
- /**
1030
- * Gets the filename for the currently parsed source.
1031
- * @returns {string} The filename associated with the source being parsed.
1032
- * Defaults to "<input>" if no filename info is present.
1033
- */
1034
- api.getFilename = function() {
1035
- if (typeof currentFilename === "string") {
1036
- return currentFilename;
1037
- } else {
1038
- return "<input>";
1039
- }
1040
- };
1041
-
1042
- /**
1043
- * Defines a new linting rule.
1044
- * @param {string} ruleId A unique rule identifier
1045
- * @param {Function} ruleModule Function from context to object mapping AST node types to event handlers
1046
- * @returns {void}
1047
- */
1048
- var defineRule = api.defineRule = function(ruleId, ruleModule) {
1049
- rules.define(ruleId, ruleModule);
1050
- };
1051
-
1052
- /**
1053
- * Defines many new linting rules.
1054
- * @param {object} rulesToDefine map from unique rule identifier to rule
1055
- * @returns {void}
1056
- */
1057
- api.defineRules = function(rulesToDefine) {
1058
- Object.getOwnPropertyNames(rulesToDefine).forEach(function(ruleId) {
1059
- defineRule(ruleId, rulesToDefine[ruleId]);
1060
- });
1061
- };
1062
-
1063
- /**
1064
- * Gets the default eslint configuration.
1065
- * @returns {Object} Object mapping rule IDs to their default configurations
1066
- */
1067
- api.defaults = function() {
1068
- return require("../conf/eslint.json");
1069
- };
1070
-
1071
- return api;
1072
-
1073
- }());
1
+ /**
2
+ * @fileoverview Main ESLint object.
3
+ * @author Nicholas C. Zakas
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ var estraverse = require("estraverse-fb"),
12
+ escope = require("escope"),
13
+ environments = require("../conf/environments"),
14
+ assign = require("object-assign"),
15
+ rules = require("./rules"),
16
+ util = require("./util"),
17
+ RuleContext = require("./rule-context"),
18
+ timing = require("./timing"),
19
+ createTokenStore = require("./token-store.js"),
20
+ EventEmitter = require("events").EventEmitter,
21
+ escapeRegExp = require("escape-string-regexp"),
22
+ validator = require("./config-validator");
23
+
24
+ //------------------------------------------------------------------------------
25
+ // Helpers
26
+ //------------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
30
+ * whitespace.
31
+ * @param {string} string The string to parse.
32
+ * @returns {Object} Result map object of names and boolean values
33
+ */
34
+ function parseBooleanConfig(string) {
35
+ var items = {};
36
+ // Collapse whitespace around : to make parsing easier
37
+ string = string.replace(/\s*:\s*/g, ":");
38
+ // Collapse whitespace around ,
39
+ string = string.replace(/\s*,\s*/g, ",");
40
+ string.split(/\s|,+/).forEach(function(name) {
41
+ if (!name) {
42
+ return;
43
+ }
44
+ var pos = name.indexOf(":"),
45
+ value;
46
+ if (pos !== -1) {
47
+ value = name.substring(pos + 1, name.length);
48
+ name = name.substring(0, pos);
49
+ }
50
+
51
+ items[name] = (value === "true");
52
+
53
+ });
54
+ return items;
55
+ }
56
+
57
+ /**
58
+ * Parses a JSON-like config.
59
+ * @param {string} string The string to parse.
60
+ * @param {Object} location Start line and column of comments for potential error message.
61
+ * @param {Object[]} messages The messages queue for potential error message.
62
+ * @returns {Object} Result map object
63
+ */
64
+ function parseJsonConfig(string, location, messages) {
65
+ var items = {};
66
+ string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
67
+ try {
68
+ items = JSON.parse("{" + string + "}");
69
+ } catch(ex) {
70
+
71
+ messages.push({
72
+ fatal: true,
73
+ severity: 2,
74
+ message: "Failed to parse JSON from '" + string + "': " + ex.message,
75
+ line: location.start.line,
76
+ column: location.start.column
77
+ });
78
+
79
+ }
80
+
81
+ return items;
82
+ }
83
+
84
+ /**
85
+ * Parses a config of values separated by comma.
86
+ * @param {string} string The string to parse.
87
+ * @returns {Object} Result map of values and true values
88
+ */
89
+ function parseListConfig(string) {
90
+ var items = {};
91
+ // Collapse whitespace around ,
92
+ string = string.replace(/\s*,\s*/g, ",");
93
+ string.split(/,+/).forEach(function(name) {
94
+ name = name.trim();
95
+ if (!name) {
96
+ return;
97
+ }
98
+ items[name] = true;
99
+ });
100
+ return items;
101
+ }
102
+
103
+ /**
104
+ * @param {Scope} scope The scope object to check.
105
+ * @param {string} name The name of the variable to look up.
106
+ * @returns {Variable} The variable object if found or null if not.
107
+ */
108
+ function getVariable(scope, name) {
109
+ var variable = null;
110
+ scope.variables.some(function(v) {
111
+ if (v.name === name) {
112
+ variable = v;
113
+ return true;
114
+ } else {
115
+ return false;
116
+ }
117
+
118
+ });
119
+ return variable;
120
+ }
121
+
122
+ /**
123
+ * Ensures that variables representing built-in properties of the Global Object,
124
+ * and any globals declared by special block comments, are present in the global
125
+ * scope.
126
+ * @param {ASTNode} program The top node of the AST.
127
+ * @param {Scope} globalScope The global scope.
128
+ * @param {Object} config The existing configuration data.
129
+ * @returns {void}
130
+ */
131
+ function addDeclaredGlobals(program, globalScope, config) {
132
+ var declaredGlobals = {},
133
+ explicitGlobals = {},
134
+ builtin = environments.builtin;
135
+
136
+ assign(declaredGlobals, builtin);
137
+
138
+ Object.keys(config.env).forEach(function (name) {
139
+ if (config.env[name]) {
140
+ var environmentGlobals = environments[name] && environments[name].globals;
141
+ if (environmentGlobals) {
142
+ assign(declaredGlobals, environmentGlobals);
143
+ }
144
+ }
145
+ });
146
+
147
+ assign(declaredGlobals, config.globals);
148
+ assign(explicitGlobals, config.astGlobals);
149
+
150
+ Object.keys(declaredGlobals).forEach(function(name) {
151
+ var variable = getVariable(globalScope, name);
152
+ if (!variable) {
153
+ variable = new escope.Variable(name, globalScope);
154
+ variable.eslintExplicitGlobal = false;
155
+ globalScope.variables.push(variable);
156
+ }
157
+ variable.writeable = declaredGlobals[name];
158
+ });
159
+
160
+ Object.keys(explicitGlobals).forEach(function(name) {
161
+ var variable = getVariable(globalScope, name);
162
+ if (!variable) {
163
+ variable = new escope.Variable(name, globalScope);
164
+ variable.eslintExplicitGlobal = true;
165
+ globalScope.variables.push(variable);
166
+ }
167
+ variable.writeable = explicitGlobals[name];
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Add data to reporting configuration to disable reporting for list of rules
173
+ * starting from start location
174
+ * @param {Object[]} reportingConfig Current reporting configuration
175
+ * @param {Object} start Position to start
176
+ * @param {string[]} rulesToDisable List of rules
177
+ * @returns {void}
178
+ */
179
+ function disableReporting(reportingConfig, start, rulesToDisable) {
180
+
181
+ if (rulesToDisable.length) {
182
+ rulesToDisable.forEach(function(rule) {
183
+ reportingConfig.push({
184
+ start: start,
185
+ end: null,
186
+ rule: rule
187
+ });
188
+ });
189
+ } else {
190
+ reportingConfig.push({
191
+ start: start,
192
+ end: null,
193
+ rule: null
194
+ });
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Add data to reporting configuration to enable reporting for list of rules
200
+ * starting from start location
201
+ * @param {Object[]} reportingConfig Current reporting configuration
202
+ * @param {Object} start Position to start
203
+ * @param {string[]} rulesToEnable List of rules
204
+ * @returns {void}
205
+ */
206
+ function enableReporting(reportingConfig, start, rulesToEnable) {
207
+ var i;
208
+
209
+ if (rulesToEnable.length) {
210
+ rulesToEnable.forEach(function(rule) {
211
+ for (i = reportingConfig.length - 1; i >= 0; i--) {
212
+ if (!reportingConfig[i].end && reportingConfig[i].rule === rule ) {
213
+ reportingConfig[i].end = start;
214
+ break;
215
+ }
216
+ }
217
+ });
218
+ } else {
219
+ // find all previous disabled locations if they was started as list of rules
220
+ var prevStart;
221
+ for (i = reportingConfig.length - 1; i >= 0; i--) {
222
+ if (prevStart && prevStart !== reportingConfig[i].start) {
223
+ break;
224
+ }
225
+
226
+ if (!reportingConfig[i].end) {
227
+ reportingConfig[i].end = start;
228
+ prevStart = reportingConfig[i].start;
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Parses comments in file to extract file-specific config of rules, globals
236
+ * and environments and merges them with global config; also code blocks
237
+ * where reporting is disabled or enabled and merges them with reporting config.
238
+ * @param {string} filename The file being checked.
239
+ * @param {ASTNode} ast The top node of the AST.
240
+ * @param {Object} config The existing configuration data.
241
+ * @param {Object[]} reportingConfig The existing reporting configuration data.
242
+ * @param {Object[]} messages The messages queue.
243
+ * @returns {object} Modified config object
244
+ */
245
+ function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) {
246
+
247
+ var commentConfig = {
248
+ astGlobals: {},
249
+ rules: {},
250
+ env: {}
251
+ };
252
+ var commentRules = {};
253
+
254
+ ast.comments.forEach(function(comment) {
255
+
256
+ var value = comment.value.trim();
257
+ var match = /^(eslint-\w+|eslint-\w+-\w+|eslint|globals?)(\s|$)/.exec(value);
258
+
259
+ if (match) {
260
+ value = value.substring(match.index + match[1].length);
261
+
262
+ if (comment.type === "Block") {
263
+ switch (match[1]) {
264
+ case "globals":
265
+ case "global":
266
+ assign(commentConfig.astGlobals, parseBooleanConfig(value));
267
+ break;
268
+
269
+ case "eslint-env":
270
+ assign(commentConfig.env, parseListConfig(value));
271
+ break;
272
+
273
+ case "eslint-disable":
274
+ disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
275
+ break;
276
+
277
+ case "eslint-enable":
278
+ enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
279
+ break;
280
+
281
+ case "eslint":
282
+ var items = parseJsonConfig(value, comment.loc, messages);
283
+ Object.keys(items).forEach(function(name) {
284
+ var ruleValue = items[name];
285
+ validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
286
+ commentRules[name] = ruleValue;
287
+ });
288
+ break;
289
+
290
+ // no default
291
+ }
292
+ } else {
293
+ // comment.type === "Line"
294
+ if (match[1] === "eslint-disable-line") {
295
+ disableReporting(reportingConfig, { "line": comment.loc.start.line, "column": 0 }, Object.keys(parseListConfig(value)));
296
+ enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
297
+ }
298
+ }
299
+ }
300
+ });
301
+
302
+ // apply environment configs
303
+ Object.keys(commentConfig.env).forEach(function (name) {
304
+ if (environments[name]) {
305
+ commentConfig = util.mergeConfigs(commentConfig, environments[name]);
306
+ }
307
+ });
308
+ assign(commentConfig.rules, commentRules);
309
+
310
+ return util.mergeConfigs(config, commentConfig);
311
+ }
312
+
313
+ /**
314
+ * Check if message of rule with ruleId should be ignored in location
315
+ * @param {Object[]} reportingConfig Collection of ignore records
316
+ * @param {string} ruleId Id of rule
317
+ * @param {Object} location Location of message
318
+ * @returns {boolean} True if message should be ignored, false otherwise
319
+ */
320
+ function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
321
+
322
+ for (var i = 0, c = reportingConfig.length; i < c; i++) {
323
+
324
+ var ignore = reportingConfig[i];
325
+ if ((!ignore.rule || ignore.rule === ruleId) &&
326
+ (location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
327
+ (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
328
+ return true;
329
+ }
330
+ }
331
+
332
+ return false;
333
+ }
334
+
335
+ /**
336
+ * Process initial config to make it safe to extend by file comment config
337
+ * @param {Object} config Initial config
338
+ * @returns {Object} Processed config
339
+ */
340
+ function prepareConfig(config) {
341
+
342
+ config.globals = config.globals || config.global || {};
343
+ delete config.global;
344
+
345
+ var copiedRules = {},
346
+ ecmaFeatures = {},
347
+ preparedConfig;
348
+
349
+ if (typeof config.rules === "object") {
350
+ Object.keys(config.rules).forEach(function(k) {
351
+ var rule = config.rules[k];
352
+ if (rule === null) {
353
+ throw new Error("Invalid config for rule '" + k + "'\.");
354
+ }
355
+ if (Array.isArray(rule)) {
356
+ copiedRules[k] = rule.slice();
357
+ } else {
358
+ copiedRules[k] = rule;
359
+ }
360
+ });
361
+ }
362
+
363
+ // merge in environment ecmaFeatures
364
+ if (typeof config.env === "object") {
365
+ Object.keys(config.env).forEach(function(env) {
366
+ if (config.env[env] && environments[env].ecmaFeatures) {
367
+ assign(ecmaFeatures, environments[env].ecmaFeatures);
368
+ }
369
+ });
370
+ }
371
+
372
+ preparedConfig = {
373
+ rules: copiedRules,
374
+ parser: config.parser || "espree",
375
+ globals: util.mergeConfigs({}, config.globals),
376
+ env: util.mergeConfigs({}, config.env || {}),
377
+ settings: util.mergeConfigs({}, config.settings || {}),
378
+ ecmaFeatures: util.mergeConfigs(ecmaFeatures, config.ecmaFeatures || {})
379
+ };
380
+
381
+ // can't have global return inside of modules
382
+ if (preparedConfig.ecmaFeatures.modules) {
383
+ preparedConfig.ecmaFeatures.globalReturn = false;
384
+ }
385
+
386
+ return preparedConfig;
387
+ }
388
+
389
+ //------------------------------------------------------------------------------
390
+ // Public Interface
391
+ //------------------------------------------------------------------------------
392
+
393
+ /**
394
+ * Object that is responsible for verifying JavaScript text
395
+ * @name eslint
396
+ */
397
+ module.exports = (function() {
398
+
399
+ var api = Object.create(new EventEmitter()),
400
+ messages = [],
401
+ currentText = null,
402
+ currentTextLines = [],
403
+ currentConfig = null,
404
+ currentTokens = null,
405
+ currentScopes = null,
406
+ scopeMap = null,
407
+ scopeManager = null,
408
+ currentFilename = null,
409
+ controller = null,
410
+ reportingConfig = [],
411
+ commentLocsEnter = [],
412
+ commentLocsExit = [],
413
+ currentAST = null;
414
+
415
+ /**
416
+ * Parses text into an AST. Moved out here because the try-catch prevents
417
+ * optimization of functions, so it's best to keep the try-catch as isolated
418
+ * as possible
419
+ * @param {string} text The text to parse.
420
+ * @param {Object} config The ESLint configuration object.
421
+ * @returns {ASTNode} The AST if successful or null if not.
422
+ * @private
423
+ */
424
+ function parse(text, config) {
425
+
426
+ var parser;
427
+
428
+ try {
429
+ parser = require(config.parser);
430
+ } catch (ex) {
431
+ messages.push({
432
+ fatal: true,
433
+ severity: 2,
434
+ message: ex.message,
435
+ line: 0,
436
+ column: 0
437
+ });
438
+
439
+ return null;
440
+ }
441
+
442
+ /*
443
+ * Check for parsing errors first. If there's a parsing error, nothing
444
+ * else can happen. However, a parsing error does not throw an error
445
+ * from this method - it's just considered a fatal error message, a
446
+ * problem that ESLint identified just like any other.
447
+ */
448
+ try {
449
+ return parser.parse(text, {
450
+ loc: true,
451
+ range: true,
452
+ raw: true,
453
+ tokens: true,
454
+ comment: true,
455
+ attachComment: true,
456
+ ecmaFeatures: config.ecmaFeatures
457
+ });
458
+ } catch (ex) {
459
+
460
+ // If the message includes a leading line number, strip it:
461
+ var message = ex.message.replace(/^line \d+:/i, "").trim();
462
+
463
+ messages.push({
464
+ fatal: true,
465
+ severity: 2,
466
+
467
+ message: message,
468
+
469
+ line: ex.lineNumber,
470
+ column: ex.column
471
+ });
472
+
473
+ return null;
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Check collection of comments to prevent double event for comment as
479
+ * leading and trailing, then emit event if passing
480
+ * @param {ASTNode[]} comments Collection of comment nodes
481
+ * @param {Object[]} locs List of locations of previous comment nodes
482
+ * @param {string} eventName Event name postfix
483
+ * @returns {void}
484
+ */
485
+ function emitComments(comments, locs, eventName) {
486
+
487
+ if (comments.length) {
488
+ comments.forEach(function(node) {
489
+ if (locs.indexOf(node.loc) >= 0) {
490
+ locs.splice(locs.indexOf(node.loc), 1);
491
+ } else {
492
+ locs.push(node.loc);
493
+ api.emit(node.type + eventName, node);
494
+ }
495
+ });
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Shortcut to check and emit enter of comment nodes
501
+ * @param {ASTNode[]} comments Collection of comment nodes
502
+ * @returns {void}
503
+ */
504
+ function emitCommentsEnter(comments) {
505
+ emitComments(comments, commentLocsEnter, "Comment");
506
+ }
507
+
508
+ /**
509
+ * Shortcut to check and emit exit of comment nodes
510
+ * @param {ASTNode[]} comments Collection of comment nodes
511
+ * @returns {void}
512
+ */
513
+ function emitCommentsExit(comments) {
514
+ emitComments(comments, commentLocsExit, "Comment:exit");
515
+ }
516
+
517
+ /**
518
+ * Get the severity level of a rule (0 - none, 1 - warning, 2 - error)
519
+ * Returns 0 if the rule config is not valid (an Array or a number)
520
+ * @param {Array|number} ruleConfig rule configuration
521
+ * @returns {number} 0, 1, or 2, indicating rule severity
522
+ */
523
+ function getRuleSeverity(ruleConfig) {
524
+ if (typeof ruleConfig === "number") {
525
+ return ruleConfig;
526
+ } else if (Array.isArray(ruleConfig)) {
527
+ return ruleConfig[0];
528
+ } else {
529
+ return 0;
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Get the options for a rule (not including severity), if any
535
+ * @param {Array|number} ruleConfig rule configuration
536
+ * @returns {Array} of rule options, empty Array if none
537
+ */
538
+ function getRuleOptions(ruleConfig) {
539
+ if (Array.isArray(ruleConfig)) {
540
+ return ruleConfig.slice(1);
541
+ } else {
542
+ return [];
543
+ }
544
+ }
545
+
546
+ // set unlimited listeners (see https://github.com/eslint/eslint/issues/524)
547
+ api.setMaxListeners(0);
548
+
549
+ /**
550
+ * Resets the internal state of the object.
551
+ * @returns {void}
552
+ */
553
+ api.reset = function() {
554
+ this.removeAllListeners();
555
+ messages = [];
556
+ currentAST = null;
557
+ currentConfig = null;
558
+ currentText = null;
559
+ currentTextLines = [];
560
+ currentTokens = null;
561
+ currentScopes = null;
562
+ scopeMap = null;
563
+ scopeManager = null;
564
+ controller = null;
565
+ reportingConfig = [];
566
+ commentLocsEnter = [];
567
+ commentLocsExit = [];
568
+ };
569
+
570
+ /**
571
+ * Verifies the text against the rules specified by the second argument.
572
+ * @param {string} text The JavaScript text to verify.
573
+ * @param {Object} config An object whose keys specify the rules to use.
574
+ * @param {string=} filename The optional filename of the file being checked.
575
+ * If this is not set, the filename will default to '<input>' in the rule context.
576
+ * @param {boolean=} saveState Indicates if the state from the last run should be saved.
577
+ * Mostly useful for testing purposes.
578
+ * @returns {Object[]} The results as an array of messages or null if no messages.
579
+ */
580
+ api.verify = function(text, config, filename, saveState) {
581
+
582
+ var ast,
583
+ shebang,
584
+ ecmaFeatures,
585
+ ecmaVersion;
586
+
587
+ // set the current parsed filename
588
+ currentFilename = filename;
589
+
590
+ if (!saveState) {
591
+ this.reset();
592
+ }
593
+
594
+ // there's no input, just exit here
595
+ if (text.trim().length === 0) {
596
+ currentText = text;
597
+ return messages;
598
+ }
599
+
600
+ // process initial config to make it safe to extend
601
+ config = prepareConfig(config || {});
602
+
603
+ ast = parse(text.replace(/^#!([^\r\n]+)/, function(match, captured) {
604
+ shebang = captured;
605
+ return "//" + captured;
606
+ }), config);
607
+
608
+ // if espree failed to parse the file, there's no sense in setting up rules
609
+ if (ast) {
610
+
611
+ currentAST = ast;
612
+
613
+ // parse global comments and modify config
614
+ config = modifyConfigsFromComments(filename, ast, config, reportingConfig, messages);
615
+
616
+ // enable appropriate rules
617
+ Object.keys(config.rules).filter(function(key) {
618
+ return getRuleSeverity(config.rules[key]) > 0;
619
+ }).forEach(function(key) {
620
+
621
+ var ruleCreator = rules.get(key),
622
+ severity = getRuleSeverity(config.rules[key]),
623
+ options = getRuleOptions(config.rules[key]),
624
+ rule;
625
+
626
+ if (ruleCreator) {
627
+ try {
628
+ rule = ruleCreator(new RuleContext(
629
+ key, api, severity, options,
630
+ config.settings, config.ecmaFeatures
631
+ ));
632
+
633
+ // add all the node types as listeners
634
+ Object.keys(rule).forEach(function(nodeType) {
635
+ api.on(nodeType, timing.enabled
636
+ ? timing.time(key, rule[nodeType])
637
+ : rule[nodeType]
638
+ );
639
+ });
640
+ } catch(ex) {
641
+ ex.message = "Error while loading rule '" + key + "': " + ex.message;
642
+ throw ex;
643
+ }
644
+
645
+ } else {
646
+ throw new Error("Definition for rule '" + key + "' was not found.");
647
+ }
648
+ });
649
+
650
+ // save config so rules can access as necessary
651
+ currentConfig = config;
652
+ currentText = text;
653
+ controller = new estraverse.Controller();
654
+
655
+ ecmaFeatures = currentConfig.ecmaFeatures;
656
+ ecmaVersion = (ecmaFeatures.blockBindings || ecmaFeatures.classes ||
657
+ ecmaFeatures.modules || ecmaFeatures.defaultParams ||
658
+ ecmaFeatures.destructuring) ? 6 : 5;
659
+
660
+
661
+ // gather data that may be needed by the rules
662
+ scopeManager = escope.analyze(ast, {
663
+ ignoreEval: true,
664
+ nodejsScope: ecmaFeatures.globalReturn,
665
+ ecmaVersion: ecmaVersion,
666
+ sourceType: ecmaFeatures.modules ? "module" : "script"
667
+ });
668
+ currentScopes = scopeManager.scopes;
669
+
670
+ /*
671
+ * Index the scopes by the start range of their block for efficient
672
+ * lookup in getScope.
673
+ */
674
+ scopeMap = [];
675
+ currentScopes.forEach(function (scope, index) {
676
+ var range = scope.block.range[0];
677
+
678
+ // Sometimes two scopes are returned for a given node. This is
679
+ // handled later in a known way, so just don't overwrite here.
680
+ if (!scopeMap[range]) {
681
+ scopeMap[range] = index;
682
+ }
683
+ });
684
+
685
+ /*
686
+ * Split text here into array of lines so
687
+ * it's not being done repeatedly
688
+ * by individual rules.
689
+ */
690
+ currentTextLines = currentText.split(/\r\n|\r|\n|\u2028|\u2029/g);
691
+
692
+ // Freezing so array isn't accidentally changed by a rule.
693
+ Object.freeze(currentTextLines);
694
+
695
+ currentTokens = createTokenStore(ast.tokens);
696
+ Object.keys(currentTokens).forEach(function(method) {
697
+ api[method] = currentTokens[method];
698
+ });
699
+
700
+ // augment global scope with declared global variables
701
+ addDeclaredGlobals(ast, currentScopes[0], currentConfig);
702
+
703
+ // remove shebang comments
704
+ if (shebang && ast.comments.length && ast.comments[0].value === shebang) {
705
+ ast.comments.splice(0, 1);
706
+
707
+ if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) {
708
+ ast.body[0].leadingComments.splice(0, 1);
709
+ }
710
+ }
711
+
712
+ /*
713
+ * Each node has a type property. Whenever a particular type of node is found,
714
+ * an event is fired. This allows any listeners to automatically be informed
715
+ * that this type of node has been found and react accordingly.
716
+ */
717
+ controller.traverse(ast, {
718
+ enter: function(node, parent) {
719
+
720
+ var comments = api.getComments(node);
721
+
722
+ emitCommentsEnter(comments.leading);
723
+ node.parent = parent;
724
+ api.emit(node.type, node);
725
+ emitCommentsEnter(comments.trailing);
726
+ },
727
+ leave: function(node) {
728
+
729
+ var comments = api.getComments(node);
730
+
731
+ emitCommentsExit(comments.trailing);
732
+ api.emit(node.type + ":exit", node);
733
+ emitCommentsExit(comments.leading);
734
+ }
735
+ });
736
+
737
+ }
738
+
739
+ // sort by line and column
740
+ messages.sort(function(a, b) {
741
+ var lineDiff = a.line - b.line;
742
+
743
+ if (lineDiff === 0) {
744
+ return a.column - b.column;
745
+ } else {
746
+ return lineDiff;
747
+ }
748
+ });
749
+
750
+ return messages;
751
+ };
752
+
753
+ /**
754
+ * Reports a message from one of the rules.
755
+ * @param {string} ruleId The ID of the rule causing the message.
756
+ * @param {number} severity The severity level of the rule as configured.
757
+ * @param {ASTNode} node The AST node that the message relates to.
758
+ * @param {Object=} location An object containing the error line and column
759
+ * numbers. If location is not provided the node's start location will
760
+ * be used.
761
+ * @param {string} message The actual message.
762
+ * @param {Object} opts Optional template data which produces a formatted message
763
+ * with symbols being replaced by this object's values.
764
+ * @returns {void}
765
+ */
766
+ api.report = function(ruleId, severity, node, location, message, opts) {
767
+
768
+ if (typeof location === "string") {
769
+ opts = message;
770
+ message = location;
771
+ location = node.loc.start;
772
+ }
773
+
774
+ Object.keys(opts || {}).forEach(function (key) {
775
+ var rx = new RegExp(escapeRegExp("{{" + key + "}}"), "g");
776
+ message = message.replace(rx, opts[key]);
777
+ });
778
+
779
+ if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
780
+ return;
781
+ }
782
+
783
+ messages.push({
784
+ ruleId: ruleId,
785
+ severity: severity,
786
+ message: message,
787
+ line: location.line,
788
+ column: location.column,
789
+ nodeType: node.type,
790
+ source: currentTextLines[location.line - 1] || ""
791
+ });
792
+ };
793
+
794
+ /**
795
+ * Gets the source code for the given node.
796
+ * @param {ASTNode=} node The AST node to get the text for.
797
+ * @param {int=} beforeCount The number of characters before the node to retrieve.
798
+ * @param {int=} afterCount The number of characters after the node to retrieve.
799
+ * @returns {string} The text representing the AST node.
800
+ */
801
+ api.getSource = function(node, beforeCount, afterCount) {
802
+ if (node) {
803
+ return (currentText !== null) ? currentText.slice(Math.max(node.range[0] - (beforeCount || 0), 0),
804
+ node.range[1] + (afterCount || 0)) : null;
805
+ } else {
806
+ return currentText;
807
+ }
808
+
809
+ };
810
+
811
+ /**
812
+ * Gets the entire source text split into an array of lines.
813
+ * @returns {Array} The source text as an array of lines.
814
+ */
815
+ api.getSourceLines = function() {
816
+ return currentTextLines;
817
+ };
818
+
819
+ /**
820
+ * Retrieves an array containing all comments in the source code.
821
+ * @returns {ASTNode[]} An array of comment nodes.
822
+ */
823
+ api.getAllComments = function() {
824
+ return currentAST.comments;
825
+ };
826
+
827
+ /**
828
+ * Gets all comments for the given node.
829
+ * @param {ASTNode} node The AST node to get the comments for.
830
+ * @returns {Object} The list of comments indexed by their position.
831
+ */
832
+ api.getComments = function(node) {
833
+
834
+ var leadingComments = node.leadingComments || [],
835
+ trailingComments = node.trailingComments || [];
836
+
837
+ /*
838
+ * espree adds a "comments" array on Program nodes rather than
839
+ * leadingComments/trailingComments. Comments are only left in the
840
+ * Program node comments array if there is no executable code.
841
+ */
842
+ if (node.type === "Program") {
843
+ if (node.body.length === 0) {
844
+ leadingComments = node.comments;
845
+ }
846
+ }
847
+
848
+ return {
849
+ leading: leadingComments,
850
+ trailing: trailingComments
851
+ };
852
+ };
853
+
854
+ /**
855
+ * Retrieves the JSDoc comment for a given node.
856
+ * @param {ASTNode} node The AST node to get the comment for.
857
+ * @returns {ASTNode} The BlockComment node containing the JSDoc for the
858
+ * given node or null if not found.
859
+ */
860
+ api.getJSDocComment = function(node) {
861
+
862
+ var parent = node.parent,
863
+ line = node.loc.start.line;
864
+
865
+ /**
866
+ * Finds a JSDoc comment node in an array of comment nodes.
867
+ * @param {ASTNode[]} comments The array of comment nodes to search.
868
+ * @returns {ASTNode} The node if found, null if not.
869
+ * @private
870
+ */
871
+ function findJSDocComment(comments) {
872
+
873
+ if (comments) {
874
+ for (var i = comments.length - 1; i >= 0; i--) {
875
+ if (comments[i].type === "Block" && comments[i].value.charAt(0) === "*") {
876
+
877
+ if (line - comments[i].loc.end.line <= 1) {
878
+ return comments[i];
879
+ } else {
880
+ break;
881
+ }
882
+ }
883
+ }
884
+ }
885
+
886
+ return null;
887
+ }
888
+
889
+ /**
890
+ * Check to see if its a ES6 export declaration
891
+ * @param {ASTNode} astNode - any node
892
+ * @returns {boolean} whether the given node represents a export declaration
893
+ */
894
+ function looksLikeExport(astNode) {
895
+ return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" ||
896
+ astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier";
897
+ }
898
+
899
+ switch (node.type) {
900
+ case "FunctionDeclaration":
901
+ if (looksLikeExport(parent)) {
902
+ return findJSDocComment(parent.leadingComments);
903
+ } else {
904
+ return findJSDocComment(node.leadingComments);
905
+ }
906
+ break;
907
+
908
+ case "ArrowFunctionExpression":
909
+ case "FunctionExpression":
910
+
911
+ if (parent.type !== "CallExpression") {
912
+ while (parent && !parent.leadingComments && !/Function/.test(parent.type)) {
913
+ parent = parent.parent;
914
+ }
915
+
916
+ return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parent.leadingComments) : null;
917
+ }
918
+
919
+ // falls through
920
+
921
+ default:
922
+ return null;
923
+ }
924
+ };
925
+
926
+ /**
927
+ * Gets nodes that are ancestors of current node.
928
+ * @returns {ASTNode[]} Array of objects representing ancestors.
929
+ */
930
+ api.getAncestors = function() {
931
+ return controller.parents();
932
+ };
933
+
934
+ /**
935
+ * Gets the deepest node containing a range index.
936
+ * @param {int} index Range index of the desired node.
937
+ * @returns {ASTNode} [description]
938
+ */
939
+ api.getNodeByRangeIndex = function(index) {
940
+ var result = null;
941
+
942
+ estraverse.traverse(controller.root, {
943
+ enter: function (node) {
944
+ if (node.range[0] <= index && index < node.range[1]) {
945
+ result = node;
946
+ } else {
947
+ this.skip();
948
+ }
949
+ },
950
+ leave: function (node) {
951
+ if (node === result) {
952
+ this.break();
953
+ }
954
+ }
955
+ });
956
+
957
+ return result;
958
+ };
959
+
960
+ /**
961
+ * Gets the scope for the current node.
962
+ * @returns {Object} An object representing the current node's scope.
963
+ */
964
+ api.getScope = function() {
965
+ var parents = controller.parents(),
966
+ scope = currentScopes[0];
967
+
968
+ // Don't do this for Program nodes - they have no parents
969
+ if (parents.length) {
970
+
971
+ // if current node is function declaration, add it to the list
972
+ var current = controller.current();
973
+ if (["FunctionDeclaration", "FunctionExpression",
974
+ "ArrowFunctionExpression", "SwitchStatement"].indexOf(current.type) >= 0) {
975
+ parents.push(current);
976
+ }
977
+
978
+ // Ascend the current node's parents
979
+ for (var i = parents.length - 1; i >= 0; --i) {
980
+
981
+ scope = scopeManager.acquire(parents[i]);
982
+ if (scope) {
983
+ if (scope.type === "function-expression-name") {
984
+ return scope.childScopes[0];
985
+ } else {
986
+ return scope;
987
+ }
988
+ }
989
+
990
+ }
991
+
992
+ }
993
+
994
+ return currentScopes[0];
995
+ };
996
+
997
+ /**
998
+ * Record that a particular variable has been used in code
999
+ * @param {string} name The name of the variable to mark as used
1000
+ * @returns {boolean} True if the variable was found and marked as used,
1001
+ * false if not.
1002
+ */
1003
+ api.markVariableAsUsed = function(name) {
1004
+ var scope = this.getScope(),
1005
+ specialScope = currentConfig.ecmaFeatures.globalReturn || currentConfig.ecmaFeatures.modules,
1006
+ variables,
1007
+ i,
1008
+ len;
1009
+
1010
+ // Special Node.js scope means we need to start one level deeper
1011
+ if (scope.type === "global" && specialScope) {
1012
+ scope = scope.childScopes[0];
1013
+ }
1014
+
1015
+ do {
1016
+ variables = scope.variables;
1017
+ for (i = 0, len = variables.length; i < len; i++) {
1018
+ if (variables[i].name === name) {
1019
+ variables[i].eslintUsed = true;
1020
+ return true;
1021
+ }
1022
+ }
1023
+ } while ( (scope = scope.upper) );
1024
+
1025
+ return false;
1026
+ };
1027
+
1028
+ /**
1029
+ * Gets the filename for the currently parsed source.
1030
+ * @returns {string} The filename associated with the source being parsed.
1031
+ * Defaults to "<input>" if no filename info is present.
1032
+ */
1033
+ api.getFilename = function() {
1034
+ if (typeof currentFilename === "string") {
1035
+ return currentFilename;
1036
+ } else {
1037
+ return "<input>";
1038
+ }
1039
+ };
1040
+
1041
+ /**
1042
+ * Defines a new linting rule.
1043
+ * @param {string} ruleId A unique rule identifier
1044
+ * @param {Function} ruleModule Function from context to object mapping AST node types to event handlers
1045
+ * @returns {void}
1046
+ */
1047
+ var defineRule = api.defineRule = function(ruleId, ruleModule) {
1048
+ rules.define(ruleId, ruleModule);
1049
+ };
1050
+
1051
+ /**
1052
+ * Defines many new linting rules.
1053
+ * @param {object} rulesToDefine map from unique rule identifier to rule
1054
+ * @returns {void}
1055
+ */
1056
+ api.defineRules = function(rulesToDefine) {
1057
+ Object.getOwnPropertyNames(rulesToDefine).forEach(function(ruleId) {
1058
+ defineRule(ruleId, rulesToDefine[ruleId]);
1059
+ });
1060
+ };
1061
+
1062
+ /**
1063
+ * Gets the default eslint configuration.
1064
+ * @returns {Object} Object mapping rule IDs to their default configurations
1065
+ */
1066
+ api.defaults = function() {
1067
+ return require("../conf/eslint.json");
1068
+ };
1069
+
1070
+ return api;
1071
+
1072
+ }());