kitchensink
Version:
Dispatch's awesome components and style guide
286 lines (242 loc) • 7.74 kB
JavaScript
var fs = require('fs');
var Vow = require('vow');
var Table = require('cli-table');
var prompt = require('prompt');
var chalk = require('chalk');
var assign = require('lodash').assign;
var Checker = require('../checker');
var utils = require('../utils');
prompt.message = '';
prompt.delimiter = '';
prompt.start();
/**
* Script that walks the user through the autoconfig flow
*
* @type {String[]}
* @private
*/
var prompts = [
{
name: chalk.green('Please choose a preset number:'),
require: true,
pattern: /\d+/
},
{
name: 'Create an (e)xception for this rule, or (f)ix the errors yourself?',
require: true,
pattern: /(e|f)/
}
];
/**
* JSCS Configuration Generator
*
* @name Generator
*/
function Generator() {
this._config = {};
}
/**
* Generates a configuration object based for the given path
* based on the best fitting preset
*
* @param {String} path - The path containing file(s) used to guide the configuration
*
* @return {Promise} Resolved with the generated, JSCS configuration
*/
Generator.prototype.generate = function(path) {
var checker = getChecker();
var _path = utils.normalizePath(path, checker.getConfiguration().getBasePath());
var presetNames = Object.keys(checker.getConfiguration().getRegisteredPresets());
var statsForPresets;
console.log('Checking', _path, 'against the presets');
return Vow
.all(presetNames.map(this._checkAgainstPreset.bind(this, _path)))
.then(function(resultsPerPreset) {
statsForPresets = this._generateStatsForPresets(resultsPerPreset, presetNames);
return statsForPresets;
}.bind(this))
.then(this._showErrorCounts.bind(this))
.then(this._getUserPresetChoice.bind(this, prompts[0]))
.then(function showViolatedRules(choiceObj) {
var presetIndex = choiceObj[prompts[0].name] - 1;
var presetName = statsForPresets[presetIndex].name;
console.log('You chose the ' + presetName + ' preset');
this._config.preset = presetName;
var errorStats = getErrorsByRuleName(statsForPresets[presetIndex].errors);
var violatedRuleCount = Object.keys(errorStats).length;
if (!violatedRuleCount) { return this._config; }
console.log(_path + ' violates ' + violatedRuleCount + ' rule' + (violatedRuleCount > 1 ? 's' : ''));
var errorPrompts = generateRuleHandlingPrompts(errorStats);
return this._getUserViolationChoices(errorPrompts)
.then(this._handleViolatedRules.bind(this, errorPrompts))
.then(function() {
return this._config;
}.bind(this));
}.bind(this))
.then(function flushConfig() {
fs.writeFileSync(process.cwd() + '/.jscsrc', JSON.stringify(this._config, null, '\t'));
console.log('Generated a .jscsrc configuration file in ' + process.cwd());
}.bind(this));
};
/**
* @private
* @param {Array.<Object[]>} resultsPerPreset - List of error objects for each preset's run of checkPath
* @param {String[]} presetNames
* @return {Object[]} Aggregated datapoints for each preset
*/
Generator.prototype._generateStatsForPresets = function(resultsPerPreset, presetNames) {
return resultsPerPreset.map(function(presetResults, idx) {
var errorCollection = [].concat.apply([], presetResults);
var presetStats = {
name: presetNames[idx],
sum: 0,
errors: []
};
errorCollection.forEach(function(error) {
presetStats.sum += error.getErrorCount();
presetStats.errors = presetStats.errors.concat(error.getErrorList());
});
return presetStats;
});
};
/**
* @private
* @param {Object[]} statsForPresets
*/
Generator.prototype._showErrorCounts = function(statsForPresets) {
var table = getTable();
statsForPresets.forEach(function(presetStats, idx) {
table.push([idx + 1, presetStats.name, presetStats.sum, getUniqueErrorNames(presetStats.errors).length]);
});
console.log(table.toString());
};
/**
* Prompts the user to choose a preset
*
* @private
* @param {Object} prompt
* @return {Promise}
*/
Generator.prototype._getUserPresetChoice = function(prompt) {
return this._showPrompt(prompt);
};
/**
* Prompts the user to nullify rules or fix violations themselves
*
* @private
* @param {Object[]} errorPrompts
* @return {Promise}
*/
Generator.prototype._getUserViolationChoices = function(errorPrompts) {
return this._showPrompt(errorPrompts);
};
/** @private */
Generator.prototype._showPrompt = utils.promisify(prompt.get.bind(prompt));
/**
* @private
* @param {Object[]} errorPrompts
* @param {Object} choices
*/
Generator.prototype._handleViolatedRules = function(errorPrompts, choices) {
errorPrompts.forEach(function(errorPrompt) {
var userChoice = choices[errorPrompt.name];
if (userChoice.toLowerCase() === 'e') {
this._config[errorPrompt.associatedRuleName] = null;
}
}, this);
};
/**
* @private
* @param {String} path
* @param {String} presetName
* @return {Promise}
*/
Generator.prototype._checkAgainstPreset = function(path, presetName) {
var checker = getChecker();
checker.configure({preset: presetName, maxErrors: Infinity});
return checker.checkPath(path);
};
/**
* @private
* @return {module:lib/Checker}
*/
function getChecker() {
var checker = new Checker();
checker.registerDefaultRules();
return checker;
}
/**
* @private
* @param {Object} errors
* @return {Object[]}
*/
function generateRuleHandlingPrompts(errors) {
// Generate list of rule names, sorted by violation count (descending)
var violatedRuleNames = Object.keys(errors);
violatedRuleNames.sort(function(a, b) {
return errors[b].violations - errors[a].violations;
});
return violatedRuleNames.map(function(ruleName) {
var violationCount = errors[ruleName].violations;
var fileCount = Object.keys(errors[ruleName].files).length;
var prompt = assign({}, prompts[1]);
prompt.associatedRuleName = ruleName;
prompt.name = chalk.green(ruleName) +
' (' + violationCount + ' violation' + (violationCount > 1 ? 's' : '') +
' in ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '):\n ' +
prompt.name;
return prompt;
});
}
/**
* @private
* @param {Object[]} errorsList
* @return {Object}
*/
function getErrorsByRuleName(errorsList) {
var errors = {};
errorsList.forEach(function(error) {
var rulename = error.rule;
errors[rulename] = errors[rulename] || {
files: {},
violations: 0
};
errors[rulename].violations += 1;
errors[rulename].files[error.filename] = true;
});
return errors;
}
/**
* @private
* @param {Object[]} errorsList
* @return {String[]}
*/
function getUniqueErrorNames(errorsList) {
var errorNameLUT = {};
errorsList.forEach(function(error) {
errorNameLUT[error.rule] = true;
});
return Object.keys(errorNameLUT);
}
/**
* @private
* @return {Object}
*/
function getTable() {
return new Table({
chars: {
top: '', 'top-mid': '', 'top-left': '', 'top-right': '',
bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
left: '', 'left-mid': '',
mid: '', 'mid-mid': '',
right: '', 'right-mid': '' ,
middle: ' '
},
style: {
'padding-left': 0,
'padding-right': 0
},
head: ['', 'Preset', '#Errors', '#Rules']
});
}
module.exports = Generator;