1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const assert = require("assert"),
|
13 | EventEmitter = require("events").EventEmitter,
|
14 | escope = require("escope"),
|
15 | levn = require("levn"),
|
16 | blankScriptAST = require("../conf/blank-script.json"),
|
17 | DEFAULT_PARSER = require("../conf/eslint.json").parser,
|
18 | replacements = require("../conf/replacements.json"),
|
19 | CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
20 | ConfigOps = require("./config/config-ops"),
|
21 | validator = require("./config/config-validator"),
|
22 | Environments = require("./config/environments"),
|
23 | CommentEventGenerator = require("./util/comment-event-generator"),
|
24 | NodeEventGenerator = require("./util/node-event-generator"),
|
25 | SourceCode = require("./util/source-code"),
|
26 | Traverser = require("./util/traverser"),
|
27 | RuleContext = require("./rule-context"),
|
28 | rules = require("./rules"),
|
29 | timing = require("./timing");
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | function parseBooleanConfig(string, comment) {
|
43 | const items = {};
|
44 |
|
45 |
|
46 | string = string.replace(/\s*([:,])\s*/g, "$1");
|
47 |
|
48 | string.split(/\s|,+/).forEach(function(name) {
|
49 | if (!name) {
|
50 | return;
|
51 | }
|
52 | const pos = name.indexOf(":");
|
53 | let value;
|
54 |
|
55 | if (pos !== -1) {
|
56 | value = name.substring(pos + 1, name.length);
|
57 | name = name.substring(0, pos);
|
58 | }
|
59 |
|
60 | items[name] = {
|
61 | value: (value === "true"),
|
62 | comment
|
63 | };
|
64 |
|
65 | });
|
66 | return items;
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function parseJsonConfig(string, location, messages) {
|
77 | let items = {};
|
78 |
|
79 |
|
80 | try {
|
81 | items = levn.parse("Object", string) || {};
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | if (ConfigOps.isEverySeverityValid(items)) {
|
88 | return items;
|
89 | }
|
90 | } catch (ex) {
|
91 |
|
92 |
|
93 | }
|
94 |
|
95 |
|
96 |
|
97 | items = {};
|
98 | string = string.replace(/([a-zA-Z0-9\-\/]+):/g, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/, "$1,");
|
99 | try {
|
100 | items = JSON.parse("{" + string + "}");
|
101 | } catch (ex) {
|
102 |
|
103 | messages.push({
|
104 | ruleId: null,
|
105 | fatal: true,
|
106 | severity: 2,
|
107 | source: null,
|
108 | message: "Failed to parse JSON from '" + string + "': " + ex.message,
|
109 | line: location.start.line,
|
110 | column: location.start.column + 1
|
111 | });
|
112 |
|
113 | }
|
114 |
|
115 | return items;
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | function parseListConfig(string) {
|
124 | const items = {};
|
125 |
|
126 |
|
127 | string = string.replace(/\s*,\s*/g, ",");
|
128 |
|
129 | string.split(/,+/).forEach(function(name) {
|
130 | name = name.trim();
|
131 | if (!name) {
|
132 | return;
|
133 | }
|
134 | items[name] = true;
|
135 | });
|
136 | return items;
|
137 | }
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | function addDeclaredGlobals(program, globalScope, config) {
|
149 | const declaredGlobals = {},
|
150 | exportedGlobals = {},
|
151 | explicitGlobals = {},
|
152 | builtin = Environments.get("builtin");
|
153 |
|
154 | Object.assign(declaredGlobals, builtin);
|
155 |
|
156 | Object.keys(config.env).forEach(function(name) {
|
157 | if (config.env[name]) {
|
158 | const env = Environments.get(name),
|
159 | environmentGlobals = env && env.globals;
|
160 |
|
161 | if (environmentGlobals) {
|
162 | Object.assign(declaredGlobals, environmentGlobals);
|
163 | }
|
164 | }
|
165 | });
|
166 |
|
167 | Object.assign(exportedGlobals, config.exported);
|
168 | Object.assign(declaredGlobals, config.globals);
|
169 | Object.assign(explicitGlobals, config.astGlobals);
|
170 |
|
171 | Object.keys(declaredGlobals).forEach(function(name) {
|
172 | let variable = globalScope.set.get(name);
|
173 |
|
174 | if (!variable) {
|
175 | variable = new escope.Variable(name, globalScope);
|
176 | variable.eslintExplicitGlobal = false;
|
177 | globalScope.variables.push(variable);
|
178 | globalScope.set.set(name, variable);
|
179 | }
|
180 | variable.writeable = declaredGlobals[name];
|
181 | });
|
182 |
|
183 | Object.keys(explicitGlobals).forEach(function(name) {
|
184 | let variable = globalScope.set.get(name);
|
185 |
|
186 | if (!variable) {
|
187 | variable = new escope.Variable(name, globalScope);
|
188 | variable.eslintExplicitGlobal = true;
|
189 | variable.eslintExplicitGlobalComment = explicitGlobals[name].comment;
|
190 | globalScope.variables.push(variable);
|
191 | globalScope.set.set(name, variable);
|
192 | }
|
193 | variable.writeable = explicitGlobals[name].value;
|
194 | });
|
195 |
|
196 |
|
197 | Object.keys(exportedGlobals).forEach(function(name) {
|
198 | const variable = globalScope.set.get(name);
|
199 |
|
200 | if (variable) {
|
201 | variable.eslintUsed = true;
|
202 | }
|
203 | });
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 | globalScope.through = globalScope.through.filter(function(reference) {
|
211 | const name = reference.identifier.name;
|
212 | const variable = globalScope.set.get(name);
|
213 |
|
214 | if (variable) {
|
215 |
|
216 | |
217 |
|
218 |
|
219 |
|
220 | reference.resolved = variable;
|
221 | variable.references.push(reference);
|
222 |
|
223 | return false;
|
224 | }
|
225 |
|
226 | return true;
|
227 | });
|
228 | }
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | function disableReporting(reportingConfig, start, rulesToDisable) {
|
239 |
|
240 | if (rulesToDisable.length) {
|
241 | rulesToDisable.forEach(function(rule) {
|
242 | reportingConfig.push({
|
243 | start,
|
244 | end: null,
|
245 | rule
|
246 | });
|
247 | });
|
248 | } else {
|
249 | reportingConfig.push({
|
250 | start,
|
251 | end: null,
|
252 | rule: null
|
253 | });
|
254 | }
|
255 | }
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 | function enableReporting(reportingConfig, start, rulesToEnable) {
|
266 | let i;
|
267 |
|
268 | if (rulesToEnable.length) {
|
269 | rulesToEnable.forEach(function(rule) {
|
270 | for (i = reportingConfig.length - 1; i >= 0; i--) {
|
271 | if (!reportingConfig[i].end && reportingConfig[i].rule === rule) {
|
272 | reportingConfig[i].end = start;
|
273 | break;
|
274 | }
|
275 | }
|
276 | });
|
277 | } else {
|
278 |
|
279 |
|
280 | let prevStart;
|
281 |
|
282 | for (i = reportingConfig.length - 1; i >= 0; i--) {
|
283 | if (prevStart && prevStart !== reportingConfig[i].start) {
|
284 | break;
|
285 | }
|
286 |
|
287 | if (!reportingConfig[i].end) {
|
288 | reportingConfig[i].end = start;
|
289 | prevStart = reportingConfig[i].start;
|
290 | }
|
291 | }
|
292 | }
|
293 | }
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 | function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) {
|
307 |
|
308 | let commentConfig = {
|
309 | exported: {},
|
310 | astGlobals: {},
|
311 | rules: {},
|
312 | env: {}
|
313 | };
|
314 | const commentRules = {};
|
315 |
|
316 | ast.comments.forEach(function(comment) {
|
317 |
|
318 | let value = comment.value.trim();
|
319 | const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
|
320 |
|
321 | if (match) {
|
322 | value = value.substring(match.index + match[1].length);
|
323 |
|
324 | if (comment.type === "Block") {
|
325 | switch (match[1]) {
|
326 | case "exported":
|
327 | Object.assign(commentConfig.exported, parseBooleanConfig(value, comment));
|
328 | break;
|
329 |
|
330 | case "globals":
|
331 | case "global":
|
332 | Object.assign(commentConfig.astGlobals, parseBooleanConfig(value, comment));
|
333 | break;
|
334 |
|
335 | case "eslint-env":
|
336 | Object.assign(commentConfig.env, parseListConfig(value));
|
337 | break;
|
338 |
|
339 | case "eslint-disable":
|
340 | disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
|
341 | break;
|
342 |
|
343 | case "eslint-enable":
|
344 | enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
|
345 | break;
|
346 |
|
347 | case "eslint": {
|
348 | const items = parseJsonConfig(value, comment.loc, messages);
|
349 |
|
350 | Object.keys(items).forEach(function(name) {
|
351 | const ruleValue = items[name];
|
352 |
|
353 | validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
|
354 | commentRules[name] = ruleValue;
|
355 | });
|
356 | break;
|
357 | }
|
358 |
|
359 |
|
360 | }
|
361 | } else {
|
362 | if (match[1] === "eslint-disable-line") {
|
363 | disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value)));
|
364 | enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value)));
|
365 | } else if (match[1] === "eslint-disable-next-line") {
|
366 | disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value)));
|
367 | enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value)));
|
368 | }
|
369 | }
|
370 | }
|
371 | });
|
372 |
|
373 |
|
374 | Object.keys(commentConfig.env).forEach(function(name) {
|
375 | const env = Environments.get(name);
|
376 |
|
377 | if (env) {
|
378 | commentConfig = ConfigOps.merge(commentConfig, env);
|
379 | }
|
380 | });
|
381 | Object.assign(commentConfig.rules, commentRules);
|
382 |
|
383 | return ConfigOps.merge(config, commentConfig);
|
384 | }
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 | function isDisabledByReportingConfig(reportingConfig, ruleId, location) {
|
394 |
|
395 | for (let i = 0, c = reportingConfig.length; i < c; i++) {
|
396 |
|
397 | const ignore = reportingConfig[i];
|
398 |
|
399 | if ((!ignore.rule || ignore.rule === ruleId) &&
|
400 | (location.line > ignore.start.line || (location.line === ignore.start.line && location.column >= ignore.start.column)) &&
|
401 | (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column <= ignore.end.column)))) {
|
402 | return true;
|
403 | }
|
404 | }
|
405 |
|
406 | return false;
|
407 | }
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 | function normalizeEcmaVersion(ecmaVersion, isModule) {
|
416 |
|
417 |
|
418 | if (isModule && (!ecmaVersion || ecmaVersion < 6)) {
|
419 | ecmaVersion = 6;
|
420 | }
|
421 |
|
422 |
|
423 |
|
424 | if (ecmaVersion >= 2015) {
|
425 | ecmaVersion -= 2009;
|
426 | }
|
427 |
|
428 | return ecmaVersion;
|
429 | }
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 | function prepareConfig(config) {
|
437 |
|
438 | config.globals = config.globals || config.global || {};
|
439 | delete config.global;
|
440 |
|
441 | const copiedRules = {};
|
442 | let parserOptions = {};
|
443 |
|
444 | if (typeof config.rules === "object") {
|
445 | Object.keys(config.rules).forEach(function(k) {
|
446 | const rule = config.rules[k];
|
447 |
|
448 | if (rule === null) {
|
449 | throw new Error("Invalid config for rule '" + k + "'\.");
|
450 | }
|
451 | if (Array.isArray(rule)) {
|
452 | copiedRules[k] = rule.slice();
|
453 | } else {
|
454 | copiedRules[k] = rule;
|
455 | }
|
456 | });
|
457 | }
|
458 |
|
459 |
|
460 | if (typeof config.env === "object") {
|
461 | Object.keys(config.env).forEach(function(envName) {
|
462 | const env = Environments.get(envName);
|
463 |
|
464 | if (config.env[envName] && env && env.parserOptions) {
|
465 | parserOptions = ConfigOps.merge(parserOptions, env.parserOptions);
|
466 | }
|
467 | });
|
468 | }
|
469 |
|
470 | const preparedConfig = {
|
471 | rules: copiedRules,
|
472 | parser: config.parser || DEFAULT_PARSER,
|
473 | globals: ConfigOps.merge({}, config.globals),
|
474 | env: ConfigOps.merge({}, config.env || {}),
|
475 | settings: ConfigOps.merge({}, config.settings || {}),
|
476 | parserOptions: ConfigOps.merge(parserOptions, config.parserOptions || {})
|
477 | };
|
478 | const isModule = preparedConfig.parserOptions.sourceType === "module";
|
479 |
|
480 | if (isModule) {
|
481 | if (!preparedConfig.parserOptions.ecmaFeatures) {
|
482 | preparedConfig.parserOptions.ecmaFeatures = {};
|
483 | }
|
484 |
|
485 |
|
486 | preparedConfig.parserOptions.ecmaFeatures.globalReturn = false;
|
487 | }
|
488 |
|
489 | preparedConfig.parserOptions.ecmaVersion = normalizeEcmaVersion(preparedConfig.parserOptions.ecmaVersion, isModule);
|
490 |
|
491 | return preparedConfig;
|
492 | }
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 | function createStubRule(message) {
|
500 |
|
501 | |
502 |
|
503 |
|
504 |
|
505 |
|
506 | function createRuleModule(context) {
|
507 | return {
|
508 | Program(node) {
|
509 | context.report(node, message);
|
510 | }
|
511 | };
|
512 | }
|
513 |
|
514 | if (message) {
|
515 | return createRuleModule;
|
516 | } else {
|
517 | throw new Error("No message passed to stub rule");
|
518 | }
|
519 | }
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 | function getRuleReplacementMessage(ruleId) {
|
527 | if (ruleId in replacements.rules) {
|
528 | const newRules = replacements.rules[ruleId];
|
529 |
|
530 | return "Rule \'" + ruleId + "\' was removed and replaced by: " + newRules.join(", ");
|
531 | }
|
532 |
|
533 | return null;
|
534 | }
|
535 |
|
536 | const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//g;
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 | function findEslintEnv(text) {
|
544 | let match, retv;
|
545 |
|
546 | eslintEnvPattern.lastIndex = 0;
|
547 |
|
548 | while ((match = eslintEnvPattern.exec(text))) {
|
549 | retv = Object.assign(retv || {}, parseListConfig(match[1]));
|
550 | }
|
551 |
|
552 | return retv;
|
553 | }
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 | function stripUnicodeBOM(text) {
|
562 |
|
563 | |
564 |
|
565 |
|
566 |
|
567 |
|
568 | if (text.charCodeAt(0) === 0xFEFF) {
|
569 | return text.slice(1);
|
570 | }
|
571 | return text;
|
572 | }
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 | module.exports = (function() {
|
583 |
|
584 | const api = Object.create(new EventEmitter());
|
585 | let messages = [],
|
586 | currentConfig = null,
|
587 | currentScopes = null,
|
588 | scopeManager = null,
|
589 | currentFilename = null,
|
590 | traverser = null,
|
591 | reportingConfig = [],
|
592 | sourceCode = null;
|
593 |
|
594 | |
595 |
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 | function parse(text, config, filePath) {
|
605 |
|
606 | let parser,
|
607 | parserOptions = {
|
608 | loc: true,
|
609 | range: true,
|
610 | raw: true,
|
611 | tokens: true,
|
612 | comment: true,
|
613 | attachComment: true,
|
614 | filePath
|
615 | };
|
616 |
|
617 | try {
|
618 | parser = require(config.parser);
|
619 | } catch (ex) {
|
620 | messages.push({
|
621 | ruleId: null,
|
622 | fatal: true,
|
623 | severity: 2,
|
624 | source: null,
|
625 | message: ex.message,
|
626 | line: 0,
|
627 | column: 0
|
628 | });
|
629 |
|
630 | return null;
|
631 | }
|
632 |
|
633 |
|
634 | if (config.parserOptions) {
|
635 | parserOptions = Object.assign({}, config.parserOptions, parserOptions);
|
636 | }
|
637 |
|
638 | |
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 | try {
|
645 | return parser.parse(text, parserOptions);
|
646 | } catch (ex) {
|
647 |
|
648 |
|
649 | const message = ex.message.replace(/^line \d+:/i, "").trim();
|
650 | const source = (ex.lineNumber) ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
|
651 |
|
652 | messages.push({
|
653 | ruleId: null,
|
654 | fatal: true,
|
655 | severity: 2,
|
656 | source,
|
657 | message: "Parsing error: " + message,
|
658 |
|
659 | line: ex.lineNumber,
|
660 | column: ex.column
|
661 | });
|
662 |
|
663 | return null;
|
664 | }
|
665 | }
|
666 |
|
667 | |
668 |
|
669 |
|
670 |
|
671 |
|
672 |
|
673 | function getRuleSeverity(ruleConfig) {
|
674 | if (typeof ruleConfig === "number") {
|
675 | return ruleConfig;
|
676 | } else if (Array.isArray(ruleConfig)) {
|
677 | return ruleConfig[0];
|
678 | } else {
|
679 | return 0;
|
680 | }
|
681 | }
|
682 |
|
683 | |
684 |
|
685 |
|
686 |
|
687 |
|
688 | function getRuleOptions(ruleConfig) {
|
689 | if (Array.isArray(ruleConfig)) {
|
690 | return ruleConfig.slice(1);
|
691 | } else {
|
692 | return [];
|
693 | }
|
694 | }
|
695 |
|
696 |
|
697 | api.setMaxListeners(0);
|
698 |
|
699 | |
700 |
|
701 |
|
702 |
|
703 | api.reset = function() {
|
704 | this.removeAllListeners();
|
705 | messages = [];
|
706 | currentConfig = null;
|
707 | currentScopes = null;
|
708 | scopeManager = null;
|
709 | traverser = null;
|
710 | reportingConfig = [];
|
711 | sourceCode = null;
|
712 | };
|
713 |
|
714 | |
715 |
|
716 |
|
717 |
|
718 |
|
719 |
|
720 |
|
721 |
|
722 |
|
723 |
|
724 |
|
725 | |
726 |
|
727 |
|
728 |
|
729 |
|
730 |
|
731 |
|
732 |
|
733 |
|
734 |
|
735 |
|
736 |
|
737 |
|
738 | api.verify = function(textOrSourceCode, config, filenameOrOptions, saveState) {
|
739 | const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
|
740 | let ast,
|
741 | shebang,
|
742 | allowInlineConfig;
|
743 |
|
744 |
|
745 | if (typeof filenameOrOptions === "object") {
|
746 | currentFilename = filenameOrOptions.filename;
|
747 | allowInlineConfig = filenameOrOptions.allowInlineConfig;
|
748 | saveState = filenameOrOptions.saveState;
|
749 | } else {
|
750 | currentFilename = filenameOrOptions;
|
751 | }
|
752 |
|
753 | if (!saveState) {
|
754 | this.reset();
|
755 | }
|
756 |
|
757 |
|
758 | const envInFile = findEslintEnv(text || textOrSourceCode.text);
|
759 |
|
760 | if (envInFile) {
|
761 | if (!config || !config.env) {
|
762 | config = Object.assign({}, config || {}, {env: envInFile});
|
763 | } else {
|
764 | config = Object.assign({}, config);
|
765 | config.env = Object.assign({}, config.env, envInFile);
|
766 | }
|
767 | }
|
768 |
|
769 |
|
770 | config = prepareConfig(config || {});
|
771 |
|
772 |
|
773 | if (text !== null) {
|
774 |
|
775 |
|
776 | if (text.trim().length === 0) {
|
777 | sourceCode = new SourceCode(text, blankScriptAST);
|
778 | return messages;
|
779 | }
|
780 |
|
781 | ast = parse(
|
782 | stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, function(match, captured) {
|
783 | shebang = captured;
|
784 | return "//" + captured;
|
785 | }),
|
786 | config,
|
787 | currentFilename
|
788 | );
|
789 |
|
790 | if (ast) {
|
791 | sourceCode = new SourceCode(text, ast);
|
792 | }
|
793 |
|
794 | } else {
|
795 | sourceCode = textOrSourceCode;
|
796 | ast = sourceCode.ast;
|
797 | }
|
798 |
|
799 |
|
800 | if (ast) {
|
801 |
|
802 |
|
803 | if (allowInlineConfig !== false) {
|
804 | config = modifyConfigsFromComments(currentFilename, ast, config, reportingConfig, messages);
|
805 | }
|
806 |
|
807 |
|
808 | ConfigOps.normalize(config);
|
809 |
|
810 |
|
811 | Object.keys(config.rules).filter(function(key) {
|
812 | return getRuleSeverity(config.rules[key]) > 0;
|
813 | }).forEach(function(key) {
|
814 | let ruleCreator;
|
815 |
|
816 | ruleCreator = rules.get(key);
|
817 |
|
818 | if (!ruleCreator) {
|
819 | const replacementMsg = getRuleReplacementMessage(key);
|
820 |
|
821 | if (replacementMsg) {
|
822 | ruleCreator = createStubRule(replacementMsg);
|
823 | } else {
|
824 | ruleCreator = createStubRule("Definition for rule '" + key + "' was not found");
|
825 | }
|
826 | rules.define(key, ruleCreator);
|
827 | }
|
828 |
|
829 | const severity = getRuleSeverity(config.rules[key]);
|
830 | const options = getRuleOptions(config.rules[key]);
|
831 |
|
832 | try {
|
833 | const ruleContext = new RuleContext(
|
834 | key, api, severity, options,
|
835 | config.settings, config.parserOptions, config.parser, ruleCreator.meta);
|
836 |
|
837 | const rule = ruleCreator.create ? ruleCreator.create(ruleContext) :
|
838 | ruleCreator(ruleContext);
|
839 |
|
840 |
|
841 | Object.keys(rule).forEach(function(nodeType) {
|
842 | api.on(nodeType, timing.enabled
|
843 | ? timing.time(key, rule[nodeType])
|
844 | : rule[nodeType]
|
845 | );
|
846 | });
|
847 | } catch (ex) {
|
848 | ex.message = "Error while loading rule '" + key + "': " + ex.message;
|
849 | throw ex;
|
850 | }
|
851 | });
|
852 |
|
853 |
|
854 | currentConfig = config;
|
855 | traverser = new Traverser();
|
856 |
|
857 | const ecmaFeatures = currentConfig.parserOptions.ecmaFeatures || {};
|
858 | const ecmaVersion = currentConfig.parserOptions.ecmaVersion || 5;
|
859 |
|
860 |
|
861 | scopeManager = escope.analyze(ast, {
|
862 | ignoreEval: true,
|
863 | nodejsScope: ecmaFeatures.globalReturn,
|
864 | impliedStrict: ecmaFeatures.impliedStrict,
|
865 | ecmaVersion,
|
866 | sourceType: currentConfig.parserOptions.sourceType || "script",
|
867 | fallback: Traverser.getKeys
|
868 | });
|
869 |
|
870 | currentScopes = scopeManager.scopes;
|
871 |
|
872 |
|
873 | addDeclaredGlobals(ast, currentScopes[0], currentConfig);
|
874 |
|
875 |
|
876 | if (shebang && ast.comments.length && ast.comments[0].value === shebang) {
|
877 | ast.comments.splice(0, 1);
|
878 |
|
879 | if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) {
|
880 | ast.body[0].leadingComments.splice(0, 1);
|
881 | }
|
882 | }
|
883 |
|
884 | let eventGenerator = new NodeEventGenerator(api);
|
885 |
|
886 | eventGenerator = new CodePathAnalyzer(eventGenerator);
|
887 | eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
|
888 |
|
889 | |
890 |
|
891 |
|
892 |
|
893 |
|
894 |
|
895 | traverser.traverse(ast, {
|
896 | enter(node, parent) {
|
897 | node.parent = parent;
|
898 | eventGenerator.enterNode(node);
|
899 | },
|
900 | leave(node) {
|
901 | eventGenerator.leaveNode(node);
|
902 | }
|
903 | });
|
904 | }
|
905 |
|
906 |
|
907 | messages.sort(function(a, b) {
|
908 | const lineDiff = a.line - b.line;
|
909 |
|
910 | if (lineDiff === 0) {
|
911 | return a.column - b.column;
|
912 | } else {
|
913 | return lineDiff;
|
914 | }
|
915 | });
|
916 |
|
917 | return messages;
|
918 | };
|
919 |
|
920 | |
921 |
|
922 |
|
923 |
|
924 |
|
925 |
|
926 |
|
927 |
|
928 |
|
929 |
|
930 |
|
931 |
|
932 |
|
933 |
|
934 |
|
935 | api.report = function(ruleId, severity, node, location, message, opts, fix, meta) {
|
936 | if (node) {
|
937 | assert.strictEqual(typeof node, "object", "Node must be an object");
|
938 | }
|
939 |
|
940 | if (typeof location === "string") {
|
941 | assert.ok(node, "Node must be provided when reporting error if location is not provided");
|
942 |
|
943 | meta = fix;
|
944 | fix = opts;
|
945 | opts = message;
|
946 | message = location;
|
947 | location = node.loc.start;
|
948 | }
|
949 |
|
950 |
|
951 | const endLocation = location.end;
|
952 |
|
953 | location = location.start || location;
|
954 |
|
955 | if (isDisabledByReportingConfig(reportingConfig, ruleId, location)) {
|
956 | return;
|
957 | }
|
958 |
|
959 | if (opts) {
|
960 | message = message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, function(fullMatch, term) {
|
961 | if (term in opts) {
|
962 | return opts[term];
|
963 | }
|
964 |
|
965 |
|
966 | return fullMatch;
|
967 | });
|
968 | }
|
969 |
|
970 | const problem = {
|
971 | ruleId,
|
972 | severity,
|
973 | message,
|
974 | line: location.line,
|
975 | column: location.column + 1,
|
976 | nodeType: node && node.type,
|
977 | source: sourceCode.lines[location.line - 1] || ""
|
978 | };
|
979 |
|
980 |
|
981 | if (endLocation) {
|
982 | problem.endLine = endLocation.line;
|
983 | problem.endColumn = endLocation.column + 1;
|
984 | }
|
985 |
|
986 |
|
987 | if (fix && Array.isArray(fix.range) && (typeof fix.text === "string")) {
|
988 |
|
989 |
|
990 | if (meta && !meta.fixable) {
|
991 | throw new Error("Fixable rules should export a `meta.fixable` property.");
|
992 | }
|
993 |
|
994 | problem.fix = fix;
|
995 | }
|
996 |
|
997 | messages.push(problem);
|
998 | };
|
999 |
|
1000 | |
1001 |
|
1002 |
|
1003 |
|
1004 | api.getSourceCode = function() {
|
1005 | return sourceCode;
|
1006 | };
|
1007 |
|
1008 |
|
1009 | const externalMethods = {
|
1010 | getSource: "getText",
|
1011 | getSourceLines: "getLines",
|
1012 | getAllComments: "getAllComments",
|
1013 | getNodeByRangeIndex: "getNodeByRangeIndex",
|
1014 | getComments: "getComments",
|
1015 | getJSDocComment: "getJSDocComment",
|
1016 | getFirstToken: "getFirstToken",
|
1017 | getFirstTokens: "getFirstTokens",
|
1018 | getLastToken: "getLastToken",
|
1019 | getLastTokens: "getLastTokens",
|
1020 | getTokenAfter: "getTokenAfter",
|
1021 | getTokenBefore: "getTokenBefore",
|
1022 | getTokenByRangeStart: "getTokenByRangeStart",
|
1023 | getTokens: "getTokens",
|
1024 | getTokensAfter: "getTokensAfter",
|
1025 | getTokensBefore: "getTokensBefore",
|
1026 | getTokensBetween: "getTokensBetween"
|
1027 | };
|
1028 |
|
1029 |
|
1030 | Object.keys(externalMethods).forEach(function(methodName) {
|
1031 | const exMethodName = externalMethods[methodName];
|
1032 |
|
1033 |
|
1034 | api[methodName] = function(a, b, c, d, e) {
|
1035 | if (sourceCode) {
|
1036 | return sourceCode[exMethodName](a, b, c, d, e);
|
1037 | }
|
1038 | return null;
|
1039 | };
|
1040 | });
|
1041 |
|
1042 | |
1043 |
|
1044 |
|
1045 |
|
1046 | api.getAncestors = function() {
|
1047 | return traverser.parents();
|
1048 | };
|
1049 |
|
1050 | |
1051 |
|
1052 |
|
1053 |
|
1054 | api.getScope = function() {
|
1055 | const parents = traverser.parents();
|
1056 |
|
1057 |
|
1058 | if (parents.length) {
|
1059 |
|
1060 |
|
1061 | const current = traverser.current();
|
1062 |
|
1063 | if (currentConfig.parserOptions.ecmaVersion >= 6) {
|
1064 | if (["BlockStatement", "SwitchStatement", "CatchClause", "FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
1065 | parents.push(current);
|
1066 | }
|
1067 | } else {
|
1068 | if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
1069 | parents.push(current);
|
1070 | }
|
1071 | }
|
1072 |
|
1073 |
|
1074 | for (let i = parents.length - 1; i >= 0; --i) {
|
1075 |
|
1076 |
|
1077 | const scope = scopeManager.acquire(parents[i], true);
|
1078 |
|
1079 | if (scope) {
|
1080 | if (scope.type === "function-expression-name") {
|
1081 | return scope.childScopes[0];
|
1082 | } else {
|
1083 | return scope;
|
1084 | }
|
1085 | }
|
1086 |
|
1087 | }
|
1088 |
|
1089 | }
|
1090 |
|
1091 | return currentScopes[0];
|
1092 | };
|
1093 |
|
1094 | |
1095 |
|
1096 |
|
1097 |
|
1098 |
|
1099 |
|
1100 | api.markVariableAsUsed = function(name) {
|
1101 | const hasGlobalReturn = currentConfig.parserOptions.ecmaFeatures && currentConfig.parserOptions.ecmaFeatures.globalReturn,
|
1102 | specialScope = hasGlobalReturn || currentConfig.parserOptions.sourceType === "module";
|
1103 | let scope = this.getScope(),
|
1104 | i,
|
1105 | len;
|
1106 |
|
1107 |
|
1108 | if (scope.type === "global" && specialScope) {
|
1109 | scope = scope.childScopes[0];
|
1110 | }
|
1111 |
|
1112 | do {
|
1113 | const variables = scope.variables;
|
1114 |
|
1115 | for (i = 0, len = variables.length; i < len; i++) {
|
1116 | if (variables[i].name === name) {
|
1117 | variables[i].eslintUsed = true;
|
1118 | return true;
|
1119 | }
|
1120 | }
|
1121 | } while ((scope = scope.upper));
|
1122 |
|
1123 | return false;
|
1124 | };
|
1125 |
|
1126 | |
1127 |
|
1128 |
|
1129 |
|
1130 |
|
1131 | api.getFilename = function() {
|
1132 | if (typeof currentFilename === "string") {
|
1133 | return currentFilename;
|
1134 | } else {
|
1135 | return "<input>";
|
1136 | }
|
1137 | };
|
1138 |
|
1139 | |
1140 |
|
1141 |
|
1142 |
|
1143 |
|
1144 |
|
1145 | const defineRule = api.defineRule = function(ruleId, ruleModule) {
|
1146 | rules.define(ruleId, ruleModule);
|
1147 | };
|
1148 |
|
1149 | |
1150 |
|
1151 |
|
1152 |
|
1153 |
|
1154 | api.defineRules = function(rulesToDefine) {
|
1155 | Object.getOwnPropertyNames(rulesToDefine).forEach(function(ruleId) {
|
1156 | defineRule(ruleId, rulesToDefine[ruleId]);
|
1157 | });
|
1158 | };
|
1159 |
|
1160 | |
1161 |
|
1162 |
|
1163 |
|
1164 | api.defaults = function() {
|
1165 | return require("../conf/eslint.json");
|
1166 | };
|
1167 |
|
1168 | |
1169 |
|
1170 |
|
1171 |
|
1172 |
|
1173 |
|
1174 |
|
1175 |
|
1176 |
|
1177 |
|
1178 |
|
1179 |
|
1180 |
|
1181 |
|
1182 |
|
1183 |
|
1184 |
|
1185 |
|
1186 |
|
1187 | api.getDeclaredVariables = function(node) {
|
1188 | return (scopeManager && scopeManager.getDeclaredVariables(node)) || [];
|
1189 | };
|
1190 |
|
1191 | return api;
|
1192 |
|
1193 | }());
|