UNPKG

8.49 kBJavaScriptView Raw
1/**
2 * @fileoverview Responsible for loading config files
3 * @author Seth McLaughlin
4 * @copyright 2014 Nicholas C. Zakas. All rights reserved.
5 * @copyright 2013 Seth McLaughlin. All rights reserved.
6 */
7"use strict";
8
9//------------------------------------------------------------------------------
10// Requirements
11//------------------------------------------------------------------------------
12
13var fs = require("fs"),
14 path = require("path"),
15 environments = require("../conf/environments.json"),
16 util = require("./util"),
17 FileFinder = require("./file-finder"),
18 stripComments = require("strip-json-comments"),
19 assign = require("object-assign"),
20 debug = require("debug"),
21 yaml = require("js-yaml");
22
23
24//------------------------------------------------------------------------------
25// Constants
26//------------------------------------------------------------------------------
27
28var CONFIG_FILENAME = ".eslintrc",
29 PERSONAL_CONFIG_PATH = path.resolve("~/" + CONFIG_FILENAME);
30
31//------------------------------------------------------------------------------
32// Helpers
33//------------------------------------------------------------------------------
34
35debug = debug("eslint:config");
36
37/**
38 * Load and parse a JSON config object from a file.
39 * @param {string} filePath the path to the JSON config file
40 * @returns {Object} the parsed config object (empty object if there was a parse error)
41 */
42function loadConfig(filePath) {
43 var config = {};
44
45 if (filePath) {
46 try {
47 config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {};
48 } catch (e) {
49 e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
50 throw e;
51 }
52 }
53
54 return config;
55}
56
57/**
58 * Get personal config object from ~/.eslintrc.
59 * @returns {Object} the personal config object (empty object if there is no personal config)
60 * @private
61 */
62function getPersonalConfig() {
63 var config = {};
64
65 if (fs.existsSync(PERSONAL_CONFIG_PATH)) {
66 debug("Using personal config");
67 config = loadConfig(PERSONAL_CONFIG_PATH);
68 }
69
70 return config;
71}
72
73/**
74 * Get a local config object.
75 * @param {Config} helper The configuration helper to use.
76 * @param {string} directory the directory to start looking for a local config
77 * @returns {Object} the local config object (empty object if there is no local config)
78 */
79function getLocalConfig(helper, directory) {
80 var localConfigFile,
81 config = {},
82 parentDirectory,
83 found = false;
84
85 while ((localConfigFile = helper.findLocalConfigFile(directory))) {
86 found = true;
87
88 // don't automatically merge the personal config settings
89 if (localConfigFile === PERSONAL_CONFIG_PATH) {
90 break;
91 }
92
93 debug("Using " + localConfigFile);
94 config = util.mergeConfigs(loadConfig(localConfigFile), config);
95
96 parentDirectory = path.dirname(directory);
97 if (directory === parentDirectory) {
98 break;
99 }
100
101 directory = parentDirectory;
102 }
103
104 if (!found) {
105 config = util.mergeConfigs(config, getPersonalConfig());
106 }
107
108 return config;
109}
110
111/**
112 * Creates an environment config based on the specified environments.
113 * @param {Object<string,boolean>} envs The environment settings.
114 * @param {boolean} reset The value of the command line reset option. If true,
115 * rules are not automatically merged into the config.
116 * @returns {Object} A configuration object with the appropriate rules and globals
117 * set.
118 * @private
119 */
120function createEnvironmentConfig(envs, reset) {
121
122 var envConfig = {
123 globals: {},
124 env: envs || {},
125 rules: {}
126 };
127
128 if (envs) {
129 Object.keys(envs).filter(function (name) {
130 return envs[name];
131 }).forEach(function(name) {
132 var environment = environments[name];
133 if (environment) {
134 if (!reset && environment.rules) {
135 assign(envConfig.rules, environment.rules);
136 }
137
138 if (environment.globals) {
139 assign(envConfig.globals, environment.globals);
140 }
141 }
142 });
143 }
144
145 return envConfig;
146}
147
148//------------------------------------------------------------------------------
149// API
150//------------------------------------------------------------------------------
151
152/**
153 * Config
154 * @constructor
155 * @class Config
156 * @param {Object} options Options to be passed in
157 * @param {string} [cwd] current working directory. Defaults to process.cwd()
158 */
159function Config(options) {
160 var useConfig;
161 options = options || {};
162
163 this.ignore = options.ignore;
164 this.ignorePath = options.ignorePath;
165
166 this.cache = {};
167 this.baseConfig = options.reset ? { rules: {} } :
168 require(path.resolve(__dirname, "..", "conf", "eslint.json"));
169 this.baseConfig.format = options.format;
170 this.useEslintrc = (options.useEslintrc !== false);
171 this.env = (options.envs || []).reduce(function (envs, name) {
172 envs[name] = true;
173 return envs;
174 }, {});
175 this.globals = (options.globals || []).reduce(function (globals, def) {
176 // Default "foo" to false and handle "foo:false" and "foo:true"
177 var parts = def.split(":");
178 globals[parts[0]] = (parts.length > 1 && parts[1] === "true");
179 return globals;
180 }, {});
181 useConfig = options.configFile;
182 this.options = options;
183
184 if (useConfig) {
185 debug("Using command line config " + useConfig);
186 this.useSpecificConfig = loadConfig(path.resolve(process.cwd(), useConfig));
187 }
188}
189
190/**
191 * Build a config object merging the base config (conf/eslint.json), the
192 * environments config (conf/environments.json) and eventually the user config.
193 * @param {string} filePath a file in whose directory we start looking for a local config
194 * @returns {Object} config object
195 */
196Config.prototype.getConfig = function (filePath) {
197
198 var config,
199 userConfig,
200 directory = filePath ? path.dirname(filePath) : process.cwd();
201
202 debug("Constructing config for " + filePath);
203
204 config = this.cache[directory];
205
206 if (config) {
207 debug("Using config from cache");
208 return config;
209 }
210
211 // Step 1: Determine user-specified config from .eslintrc files
212 if (this.useEslintrc) {
213 debug("Using .eslintrc files");
214 userConfig = getLocalConfig(this, directory);
215 } else {
216 debug("Not using .eslintrc files");
217 userConfig = {};
218 }
219
220 // Step 2: Create a copy of the baseConfig
221 config = util.mergeConfigs({}, this.baseConfig);
222
223 // Step 3: Merge in environment-specific globals and rules from .eslintrc files
224 config = util.mergeConfigs(config, createEnvironmentConfig(userConfig.env, this.options.reset));
225
226 // Step 4: Merge in the user-specified configuration from .eslintrc files
227 config = util.mergeConfigs(config, userConfig);
228
229 // Step 5: Merge in command line config file
230 if (this.useSpecificConfig) {
231 debug("Merging command line config file");
232 config = util.mergeConfigs(config, this.useSpecificConfig);
233
234 if (this.useSpecificConfig.env) {
235 config = util.mergeConfigs(config, createEnvironmentConfig(this.useSpecificConfig.env, this.options.reset));
236 }
237 }
238
239 // Step 6: Merge in command line environments
240 if (this.env) {
241 debug("Merging command line environment settings");
242 config = util.mergeConfigs(config, createEnvironmentConfig(this.env, this.options.reset));
243 }
244
245 // Step 7: Merge in command line rules
246 if (this.options.rules) {
247 debug("Merging command line rules");
248 config = util.mergeConfigs(config, { rules: this.options.rules });
249 }
250
251 // Step 8: Merge in command line globals
252 config = util.mergeConfigs(config, { globals: this.globals });
253
254
255 this.cache[directory] = config;
256
257 return config;
258};
259
260/**
261 * Find a local config file, relative to a specified directory.
262 * @param {string} directory the directory to start searching from
263 * @returns {string|boolean} path of config file if found, or false if no config is found
264 */
265Config.prototype.findLocalConfigFile = function (directory) {
266 if (!this.localConfigFinder) {
267 this.localConfigFinder = new FileFinder(CONFIG_FILENAME);
268 }
269 return this.localConfigFinder.findInDirectory(directory);
270};
271
272
273module.exports = Config;