UNPKG

7.69 kBJavaScriptView Raw
1/**
2 * @fileoverview Main CLI object.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
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
18var 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
65var PLUGIN_NAME_PREFIX = "eslint-plugin-";
66
67//------------------------------------------------------------------------------
68// Private
69//------------------------------------------------------------------------------
70
71
72var 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
89debug = 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 */
96function 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 */
113function 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 */
138function 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 */
177function 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
196CLIEngine.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
251module.exports = CLIEngine;