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 | debug = require("debug"),
|
22 |
|
23 | options = require("./options"),
|
24 | CLIEngine = require("./cli-engine"),
|
25 | mkdirp = require("mkdirp");
|
26 |
|
27 | //------------------------------------------------------------------------------
|
28 | // Helpers
|
29 | //------------------------------------------------------------------------------
|
30 |
|
31 | debug = debug("eslint:cli");
|
32 |
|
33 | /**
|
34 | * Translates the CLI options into the options expected by the CLIEngine.
|
35 | * @param {Object} cliOptions The CLI options to translate.
|
36 | * @returns {CLIEngineOptions} The options object for the CLIEngine.
|
37 | * @private
|
38 | */
|
39 | function translateOptions(cliOptions) {
|
40 | return {
|
41 | envs: cliOptions.env,
|
42 | rules: cliOptions.rule,
|
43 | plugins: cliOptions.plugin,
|
44 | globals: cliOptions.global,
|
45 | ignore: cliOptions.ignore,
|
46 | ignorePath: cliOptions.ignorePath,
|
47 | configFile: cliOptions.config,
|
48 | rulePaths: cliOptions.rulesdir,
|
49 | reset: cliOptions.reset,
|
50 | useEslintrc: cliOptions.eslintrc
|
51 | };
|
52 | }
|
53 |
|
54 | /**
|
55 | * Outputs the results of the linting.
|
56 | * @param {LintResult[]} results The results to print.
|
57 | * @param {string} format The name of the formatter to use or the path to the formatter.
|
58 | * @param {string} outputFile The path for the output file.
|
59 | * @param {Config} config The configuration options for the results.
|
60 | * @returns {boolean} True if the printing succeeds, false if not.
|
61 | * @private
|
62 | */
|
63 | function printResults(results, format, outputFile, config) {
|
64 | var formatter,
|
65 | formatterPath,
|
66 | output,
|
67 | filePath;
|
68 |
|
69 | if (fs.existsSync(path.resolve(process.cwd(), format))) {
|
70 | formatterPath = path.resolve(process.cwd(), format);
|
71 | } else {
|
72 | formatterPath = "./formatters/" + format;
|
73 | }
|
74 | try {
|
75 | formatter = require(formatterPath);
|
76 | } catch (ex) {
|
77 | console.error("Could not find formatter '%s'.", format);
|
78 | return false;
|
79 | }
|
80 |
|
81 | output = formatter(results, config);
|
82 |
|
83 | if (output) {
|
84 | if (outputFile) {
|
85 | filePath = path.resolve(process.cwd(), outputFile);
|
86 |
|
87 | if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
88 | console.error("Cannot write to output file path, it is a directory: %s", outputFile);
|
89 | return false;
|
90 | }
|
91 |
|
92 | try {
|
93 | mkdirp.sync(path.dirname(filePath));
|
94 | fs.writeFileSync(filePath, output);
|
95 | } catch (ex) {
|
96 | console.error("There was a problem writing the output file:\n%s", ex);
|
97 | return false;
|
98 | }
|
99 | } else {
|
100 | console.log(output);
|
101 | }
|
102 | }
|
103 |
|
104 | return true;
|
105 |
|
106 | }
|
107 |
|
108 | /**
|
109 | * Calculates the exit code for ESLint. If there is even one error then the
|
110 | * exit code is 1.
|
111 | * @param {LintResult[]} results The results to calculate from.
|
112 | * @returns {int} The exit code to use.
|
113 | * @private
|
114 | */
|
115 | function calculateExitCode(results) {
|
116 | return results.some(function(result) {
|
117 | return result.messages.some(function(message) {
|
118 | return message.severity === 2;
|
119 | });
|
120 | }) ? 1 : 0;
|
121 | }
|
122 |
|
123 | //------------------------------------------------------------------------------
|
124 | // Public Interface
|
125 | //------------------------------------------------------------------------------
|
126 |
|
127 | /**
|
128 | * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
|
129 | * for other Node.js programs to effectively run the CLI.
|
130 | */
|
131 | var cli = {
|
132 |
|
133 | /**
|
134 | * Executes the CLI based on an array of arguments that is passed in.
|
135 | * @param {string|Array|Object} args The arguments to process.
|
136 | * @returns {int} The exit code for the operation.
|
137 | */
|
138 | execute: function(args) {
|
139 |
|
140 | var currentOptions,
|
141 | files,
|
142 | result,
|
143 | engine;
|
144 |
|
145 | try {
|
146 | currentOptions = options.parse(args);
|
147 | } catch (error) {
|
148 | console.error(error.message);
|
149 | return 1;
|
150 | }
|
151 |
|
152 | files = currentOptions._;
|
153 |
|
154 | if (currentOptions.version) { // version from package.json
|
155 |
|
156 | console.log("v" + require("../package.json").version);
|
157 |
|
158 | } else if (currentOptions.help || !files.length) {
|
159 |
|
160 | console.log(options.generateHelp());
|
161 |
|
162 | } else {
|
163 |
|
164 | engine = new CLIEngine(translateOptions(currentOptions));
|
165 | result = engine.executeOnFiles(files);
|
166 | if (printResults(result.results, currentOptions.format, currentOptions.outputFile)) {
|
167 | return calculateExitCode(result.results);
|
168 | } else {
|
169 | return 1;
|
170 | }
|
171 |
|
172 | }
|
173 |
|
174 | return 0;
|
175 | }
|
176 | };
|
177 |
|
178 | module.exports = cli;
|