UNPKG

7.75 kBJavaScriptView Raw
1var fs = require('fs');
2
3var Vow = require('vow');
4var Table = require('cli-table');
5var prompt = require('prompt');
6var chalk = require('chalk');
7var assign = require('lodash').assign;
8
9var Checker = require('../checker');
10var utils = require('../utils');
11
12prompt.message = '';
13prompt.delimiter = '';
14prompt.start();
15
16/**
17 * Script that walks the user through the autoconfig flow
18 *
19 * @type {String[]}
20 * @private
21 */
22var prompts = [
23 {
24 name: chalk.green('Please choose a preset number:'),
25 require: true,
26 pattern: /\d+/
27 },
28 {
29 name: 'Create an (e)xception for this rule, or (f)ix the errors yourself?',
30 require: true,
31 pattern: /(e|f)/
32 }
33];
34
35/**
36 * JSCS Configuration Generator
37 *
38 * @name Generator
39 */
40function Generator() {
41 this._config = {};
42}
43
44/**
45 * Generates a configuration object based for the given path
46 * based on the best fitting preset
47 *
48 * @param {String} path - The path containing file(s) used to guide the configuration
49 *
50 * @return {Promise} Resolved with the generated, JSCS configuration
51 */
52Generator.prototype.generate = function(path) {
53 var checker = getChecker();
54 var _path = utils.normalizePath(path, checker.getConfiguration().getBasePath());
55 var presetNames = Object.keys(checker.getConfiguration().getRegisteredPresets());
56 var statsForPresets;
57
58 console.log('Checking', _path, 'against the presets');
59
60 return Vow
61 .all(presetNames.map(this._checkAgainstPreset.bind(this, _path)))
62 .then(function(resultsPerPreset) {
63 statsForPresets = this._generateStatsForPresets(resultsPerPreset, presetNames);
64 return statsForPresets;
65 }.bind(this))
66 .then(this._showErrorCounts.bind(this))
67 .then(this._getUserPresetChoice.bind(this, prompts[0]))
68 .then(function showViolatedRules(choiceObj) {
69 var presetIndex = choiceObj[prompts[0].name] - 1;
70 var presetName = statsForPresets[presetIndex].name;
71
72 console.log('You chose the ' + presetName + ' preset');
73
74 this._config.preset = presetName;
75
76 var errorStats = getErrorsByRuleName(statsForPresets[presetIndex].errors);
77
78 var violatedRuleCount = Object.keys(errorStats).length;
79
80 if (!violatedRuleCount) { return this._config; }
81
82 console.log(_path + ' violates ' + violatedRuleCount + ' rule' + (violatedRuleCount > 1 ? 's' : ''));
83
84 var errorPrompts = generateRuleHandlingPrompts(errorStats);
85
86 return this._getUserViolationChoices(errorPrompts)
87 .then(this._handleViolatedRules.bind(this, errorPrompts))
88 .then(function() {
89 return this._config;
90 }.bind(this));
91 }.bind(this))
92 .then(function flushConfig() {
93 fs.writeFileSync(process.cwd() + '/.jscsrc', JSON.stringify(this._config, null, '\t'));
94 console.log('Generated a .jscsrc configuration file in ' + process.cwd());
95 }.bind(this));
96};
97
98/**
99 * @private
100 * @param {Array.<Object[]>} resultsPerPreset - List of error objects for each preset's run of checkPath
101 * @param {String[]} presetNames
102 * @return {Object[]} Aggregated datapoints for each preset
103 */
104Generator.prototype._generateStatsForPresets = function(resultsPerPreset, presetNames) {
105 return resultsPerPreset.map(function(presetResults, idx) {
106 var errorCollection = [].concat.apply([], presetResults);
107
108 var presetStats = {
109 name: presetNames[idx],
110 sum: 0,
111 errors: []
112 };
113
114 errorCollection.forEach(function(error) {
115 presetStats.sum += error.getErrorCount();
116 presetStats.errors = presetStats.errors.concat(error.getErrorList());
117 });
118
119 return presetStats;
120 });
121};
122
123/**
124 * @private
125 * @param {Object[]} statsForPresets
126 */
127Generator.prototype._showErrorCounts = function(statsForPresets) {
128 var table = getTable();
129
130 statsForPresets.forEach(function(presetStats, idx) {
131 table.push([idx + 1, presetStats.name, presetStats.sum, getUniqueErrorNames(presetStats.errors).length]);
132 });
133
134 console.log(table.toString());
135};
136
137/**
138 * Prompts the user to choose a preset
139 *
140 * @private
141 * @param {Object} prompt
142 * @return {Promise}
143 */
144Generator.prototype._getUserPresetChoice = function(prompt) {
145 return this._showPrompt(prompt);
146};
147
148/**
149 * Prompts the user to nullify rules or fix violations themselves
150 *
151 * @private
152 * @param {Object[]} errorPrompts
153 * @return {Promise}
154 */
155Generator.prototype._getUserViolationChoices = function(errorPrompts) {
156 return this._showPrompt(errorPrompts);
157};
158
159/** @private */
160Generator.prototype._showPrompt = utils.promisify(prompt.get.bind(prompt));
161
162/**
163 * @private
164 * @param {Object[]} errorPrompts
165 * @param {Object} choices
166 */
167Generator.prototype._handleViolatedRules = function(errorPrompts, choices) {
168 errorPrompts.forEach(function(errorPrompt) {
169 var userChoice = choices[errorPrompt.name];
170
171 if (userChoice && userChoice.toLowerCase() === 'e') {
172 this._config[errorPrompt.associatedRuleName] = null;
173 }
174 }, this);
175};
176
177/**
178 * @private
179 * @param {String} path
180 * @param {String} presetName
181 * @return {Promise}
182 */
183Generator.prototype._checkAgainstPreset = function(path, presetName) {
184 var checker = getChecker();
185
186 checker.configure({preset: presetName, maxErrors: Infinity});
187
188 return checker.checkPath(path);
189};
190
191/**
192 * @private
193 * @return {module:lib/Checker}
194 */
195function getChecker() {
196 var checker = new Checker();
197 checker.registerDefaultRules();
198 return checker;
199}
200
201/**
202 * @private
203 * @param {Object} errors
204 * @return {Object[]}
205 */
206function generateRuleHandlingPrompts(errors) {
207 // Generate list of rule names, sorted by violation count (descending)
208 var violatedRuleNames = Object.keys(errors);
209 violatedRuleNames.sort(function(a, b) {
210 return errors[b].violations - errors[a].violations;
211 });
212
213 return violatedRuleNames.map(function(ruleName) {
214 var violationCount = errors[ruleName].violations;
215 var fileCount = Object.keys(errors[ruleName].files).length;
216 var prompt = assign({}, prompts[1]);
217
218 prompt.associatedRuleName = ruleName;
219 prompt.name = chalk.green(ruleName) +
220 ' (' + violationCount + ' violation' + (violationCount > 1 ? 's' : '') +
221 ' in ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '):\n ' +
222 prompt.name;
223 return prompt;
224 });
225}
226
227/**
228 * @private
229 * @param {Object[]} errorsList
230 * @return {Object}
231 */
232function getErrorsByRuleName(errorsList) {
233 var errors = {};
234
235 errorsList.forEach(function(error) {
236 var rulename = error.rule;
237 errors[rulename] = errors[rulename] || {
238 files: {},
239 violations: 0
240 };
241 errors[rulename].violations += 1;
242 errors[rulename].files[error.filename] = true;
243 });
244
245 return errors;
246}
247
248/**
249 * @private
250 * @param {Object[]} errorsList
251 * @return {String[]}
252 */
253function getUniqueErrorNames(errorsList) {
254 var errorNameLUT = {};
255
256 errorsList.forEach(function(error) {
257 errorNameLUT[error.rule] = true;
258 });
259
260 return Object.keys(errorNameLUT);
261}
262
263/**
264 * @private
265 * @return {Object}
266 */
267function getTable() {
268 return new Table({
269 chars: {
270 top: '', 'top-mid': '', 'top-left': '', 'top-right': '',
271 bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
272 left: '', 'left-mid': '',
273 mid: '', 'mid-mid': '',
274 right: '', 'right-mid': '' ,
275 middle: ' '
276 },
277 style: {
278 'padding-left': 0,
279 'padding-right': 0
280 },
281 head: ['', 'Preset', '#Errors', '#Rules']
282 });
283}
284
285module.exports = Generator;