1 | /**
|
2 | * @fileoverview Main CLI object.
|
3 | * @author Nicholas C. Zakas
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | /*
|
9 | * The CLI object should *not* call process.exit() directly. It should only return
|
10 | * exit codes. This allows other programs to use the CLI object and still control
|
11 | * when the program exits.
|
12 | */
|
13 |
|
14 | //------------------------------------------------------------------------------
|
15 | // Requirements
|
16 | //------------------------------------------------------------------------------
|
17 |
|
18 | var fs = require("fs"),
|
19 | path = require("path"),
|
20 |
|
21 | assign = require("object-assign"),
|
22 | debug = require("debug"),
|
23 |
|
24 | rules = require("./rules"),
|
25 | eslint = require("./eslint"),
|
26 | traverse = require("./util/traverse"),
|
27 | IgnoredPaths = require("./ignored-paths"),
|
28 | Config = require("./config");
|
29 |
|
30 | //------------------------------------------------------------------------------
|
31 | // Typedefs
|
32 | //------------------------------------------------------------------------------
|
33 |
|
34 | /**
|
35 | * The options to configure a CLI engine with.
|
36 | * @typedef {Object} CLIEngineOptions
|
37 | * @property {string} configFile The configuration file to use.
|
38 | * @property {boolean} reset True disables all default rules and environments.
|
39 | * @property {boolean} ignore False disables use of .eslintignore.
|
40 | * @property {string[]} rulePaths An array of directories to load custom rules from.
|
41 | * @property {boolean} useEslintrc False disables looking for .eslintrc
|
42 | * @property {string[]} envs An array of environments to load.
|
43 | * @property {string[]} globals An array of global variables to declare.
|
44 | * @property {Object<string,*>} rules An object of rules to use.
|
45 | * @property {string} ignorePath The ignore file to use instead of .eslintignore.
|
46 | */
|
47 |
|
48 | /**
|
49 | * A linting warning or error.
|
50 | * @typedef {Object} LintMessage
|
51 | * @property {string} message The message to display to the user.
|
52 | */
|
53 |
|
54 | /**
|
55 | * A linting result.
|
56 | * @typedef {Object} LintResult
|
57 | * @property {string} filePath The path to the file that was linted.
|
58 | * @property {LintMessage[]} messages All of the messages for the result.
|
59 | */
|
60 |
|
61 | //------------------------------------------------------------------------------
|
62 | // Constants
|
63 | //------------------------------------------------------------------------------
|
64 |
|
65 | var PLUGIN_NAME_PREFIX = "eslint-plugin-";
|
66 |
|
67 | //------------------------------------------------------------------------------
|
68 | // Private
|
69 | //------------------------------------------------------------------------------
|
70 |
|
71 |
|
72 | var defaultOptions = {
|
73 | configFile: null,
|
74 | reset: false,
|
75 | rulePaths: [],
|
76 | useEslintrc: true,
|
77 | envs: [],
|
78 | globals: [],
|
79 | rules: {},
|
80 | ignore: true,
|
81 | ignorePath: null
|
82 | },
|
83 | loadedPlugins = Object.create(null);
|
84 |
|
85 | //------------------------------------------------------------------------------
|
86 | // Helpers
|
87 | //------------------------------------------------------------------------------
|
88 |
|
89 | debug = debug("eslint:cli-engine");
|
90 |
|
91 | /**
|
92 | * Removes the prefix `eslint-plugin-` from a plugin name.
|
93 | * @param {string} pluginName The name of the plugin which may has the prefix.
|
94 | * @returns {string} The name of the plugin without prefix.
|
95 | */
|
96 | function removePluginPrefix(pluginName) {
|
97 | var nameWithoutPrefix;
|
98 |
|
99 | if (pluginName.indexOf(PLUGIN_NAME_PREFIX) === 0) {
|
100 | nameWithoutPrefix = pluginName.substring(PLUGIN_NAME_PREFIX.length);
|
101 | } else {
|
102 | nameWithoutPrefix = pluginName;
|
103 | }
|
104 |
|
105 | return nameWithoutPrefix;
|
106 | }
|
107 |
|
108 | /**
|
109 | * Load the given plugins if they are not loaded already.
|
110 | * @param {string[]} pluginNames An array of plugin names which should be loaded.
|
111 | * @returns {void}
|
112 | */
|
113 | function loadPlugins(pluginNames) {
|
114 | if (pluginNames) {
|
115 | pluginNames.forEach(function (pluginName) {
|
116 | var pluginNameWithoutPrefix = removePluginPrefix(pluginName),
|
117 | plugin;
|
118 |
|
119 | if (!loadedPlugins[pluginNameWithoutPrefix]) {
|
120 | debug("Load plugin " + pluginNameWithoutPrefix);
|
121 |
|
122 | plugin = require(PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix);
|
123 | rules.import(plugin.rules, pluginNameWithoutPrefix);
|
124 |
|
125 | loadedPlugins[pluginNameWithoutPrefix] = true;
|
126 | }
|
127 | });
|
128 | }
|
129 | }
|
130 |
|
131 | /**
|
132 | * Processes an individual file using ESLint.
|
133 | * @param {string} filename The filename of the file being checked.
|
134 | * @param {Object} configHelper The configuration options for ESLint.
|
135 | * @returns {Result} The results for linting on this file.
|
136 | * @private
|
137 | */
|
138 | function processFile(filename, configHelper) {
|
139 |
|
140 | // clear all existing settings for a new file
|
141 | eslint.reset();
|
142 |
|
143 | var filePath = path.resolve(filename),
|
144 | config,
|
145 | text,
|
146 | messages;
|
147 |
|
148 | if (fs.existsSync(filePath)) {
|
149 | debug("Linting " + filePath);
|
150 | config = configHelper.getConfig(filePath);
|
151 | loadPlugins(config.plugins);
|
152 | text = fs.readFileSync(path.resolve(filename), "utf8");
|
153 | messages = eslint.verify(text, config, filename);
|
154 | } else {
|
155 | debug("Couldn't find " + filePath);
|
156 | messages = [{
|
157 | fatal: true,
|
158 | message: "Could not find file at '" + filePath + "'."
|
159 | }];
|
160 | }
|
161 |
|
162 | return {
|
163 | filePath: filename,
|
164 | messages: messages
|
165 | };
|
166 | }
|
167 |
|
168 | //------------------------------------------------------------------------------
|
169 | // Public Interface
|
170 | //------------------------------------------------------------------------------
|
171 |
|
172 | /**
|
173 | * Creates a new instance of the core CLI engine.
|
174 | * @param {CLIEngineOptions} options The options for this instance.
|
175 | * @constructor
|
176 | */
|
177 | function CLIEngine(options) {
|
178 |
|
179 | /**
|
180 | * Stored options for this instance
|
181 | * @type {Object}
|
182 | */
|
183 | this.options = assign(Object.create(defaultOptions), options || {});
|
184 |
|
185 | // load in additional rules
|
186 | if (this.options.rulePaths) {
|
187 | this.options.rulePaths.forEach(function(rulesdir) {
|
188 | debug("Loading rules from " + rulesdir);
|
189 | rules.load(rulesdir);
|
190 | });
|
191 | }
|
192 |
|
193 | loadPlugins(this.options.plugins);
|
194 | }
|
195 |
|
196 | CLIEngine.prototype = {
|
197 |
|
198 | constructor: CLIEngine,
|
199 |
|
200 | /**
|
201 | * Executes the current configuration on an array of file and directory names.
|
202 | * @param {string[]} files An array of file and directory names.
|
203 | * @returns {Object} The results for all files that were linted.
|
204 | */
|
205 | executeOnFiles: function(files) {
|
206 |
|
207 | var results = [],
|
208 | processed = [],
|
209 | configHelper = new Config(this.options),
|
210 | ignoredPaths = IgnoredPaths.load(this.options),
|
211 | exclude = ignoredPaths.contains.bind(ignoredPaths);
|
212 |
|
213 | traverse({
|
214 | files: files,
|
215 | exclude: this.options.ignore ? exclude : false
|
216 | }, function(filename) {
|
217 |
|
218 | debug("Processing " + filename);
|
219 |
|
220 | if (path.extname(filename) === ".js") {
|
221 | processed.push(filename);
|
222 | results.push(processFile(filename, configHelper));
|
223 | }
|
224 |
|
225 | });
|
226 |
|
227 | // only warn for files explicitly passes on the command line
|
228 | if (this.options.ignore) {
|
229 | files.forEach(function(file) {
|
230 | if (path.extname(file) === ".js" && processed.indexOf(file) === -1) {
|
231 | results.push({
|
232 | filePath: file,
|
233 | messages: [
|
234 | {
|
235 | fatal: false,
|
236 | message: "File ignored because of your .eslintignore file. Use --no-ignore to override."
|
237 | }
|
238 | ]
|
239 | });
|
240 | }
|
241 | });
|
242 | }
|
243 |
|
244 | return {
|
245 | results: results
|
246 | };
|
247 | }
|
248 |
|
249 | };
|
250 |
|
251 | module.exports = CLIEngine;
|