2020-05-19 11:43:42 +03:00

217 lines
7.0 KiB
JavaScript

'use strict';
var fs = require('fs');
var parseComments = require('parse-comments');
var _ = require('lodash');
var eslintAngularIndex = require('../index');
var ruleCategories = require('./ruleCategories.json');
var RuleTester = require('eslint').RuleTester;
var templates = require('./templates.js');
var ruleNames = Object.keys(eslintAngularIndex.rules).filter(function(ruleName) {
// filter legacy rules
return !/^ng_/.test(ruleName);
});
// create rule documentation objects from ruleNames
var rules = ruleNames.map(_createRule);
module.exports = {
rules: rules,
createDocFiles: createDocFiles,
updateReadme: updateReadme,
testDocs: testDocs
};
/**
* Create a markdown documentation for each rule from rule comments and examples.
*
* @param cb callback
*/
function createDocFiles(cb) {
this.rules.forEach(function(rule) {
fs.writeFileSync(rule.documentationPath, _trimTrailingSpacesAndMultilineBreaks(templates.ruleDocumentationContent(rule)));
});
(cb || _.noop)();
}
/**
* Update the rules section in the readme file.
*
* @param cb callback
*/
function updateReadme(readmePath, cb) {
ruleCategories.rulesByCategory = _.groupBy(this.rules, 'category');
// filter categories without rules
ruleCategories.categoryOrder = ruleCategories.categoryOrder.filter(function(categoryName) {
var rulesForCategory = ruleCategories.rulesByCategory[categoryName];
return rulesForCategory && rulesForCategory.length > 0;
});
var readmeContent = fs.readFileSync(readmePath).toString();
var newRuleSection = templates.readmeRuleSectionContent(ruleCategories);
// use split and join to prevent the replace() and dollar sign problem (http://stackoverflow.com/questions/9423722)
readmeContent = readmeContent.split(/## Rules[\S\s]*?----\n/).join(newRuleSection);
fs.writeFileSync(readmePath, readmeContent);
(cb || _.noop)();
}
/**
* Test documentation examples.
*
* @param cb callback
*/
function testDocs(cb) {
this.rules.forEach(function(rule) {
if (rule.examples !== undefined) {
var eslintTester = new RuleTester();
eslintTester.run(rule.ruleName, rule.module, rule.examples);
}
});
(cb || _.noop)();
}
function _trimTrailingSpacesAndMultilineBreaks(content) {
return content.replace(/( )+\n/g, '\n').replace(/\n\n+\n/g, '\n\n').replace(/\n+$/g, '\n');
}
function _parseConfigLine(configLine) {
// surround config keys with quotes for json parsing
var preparedConfigLine = configLine.replace(/(\w+):/g, '"$1":').replace('\\:', ':');
var example = JSON.parse('{' + preparedConfigLine + '}');
return example;
}
function _wrapErrorMessage(errorMessage) {
return {
message: errorMessage
};
}
function _parseExample(exampleSource) {
var lines = exampleSource.split('\n');
// parse first example line as config
var example = _parseConfigLine(lines[0]);
// other lines are the example code
example.code = lines.slice(1).join('\n').trim();
// wrap the errorMessage in the format needed for the eslint rule tester.
if (example.errorMessage) {
if (_.isString(example.errorMessage)) {
example.errors = [_wrapErrorMessage(example.errorMessage)];
} else {
throw new Error('Example "errorMessage" must be a string');
}
} else if (example.errorMessages) {
if (_.isArray(example.errorMessages)) {
example.errors = example.errorMessages.map(_wrapErrorMessage);
} else {
throw new Error('Example "errorMessages" must be an array');
}
}
// invalid examples require an errorMessage
if (!example.valid && !(example.errorMessage || example.errorMessages)) {
throw new Error('Example config requires "errorMessage(s)" when valid: false');
}
// json options needed as group key
example.jsonOptions = example.options ? JSON.stringify(example.options) : '';
// use options for tests or default options of no options are configured
if (example.options) {
example.displayOptions = example.options;
}
return example;
}
function _loadExamples(rule) {
var examplesSource = fs.readFileSync(rule.examplesPath).toString();
var exampleRegex = /\s*\/\/\s*example\s*-/;
if (!exampleRegex.test(examplesSource)) {
return [];
}
return examplesSource.split(exampleRegex).slice(1).map(_parseExample.bind(rule));
}
function _filterByValidity(examples, validity) {
return _.filter(examples, {valid: validity}) || [];
}
function _appendGroupedExamplesByValidity(groupedExamples, examples, config, validity) {
var examplesByValidity = _filterByValidity(examples, validity);
if (examplesByValidity.length > 0) {
groupedExamples.push({
config: config,
valid: validity,
examples: examplesByValidity
});
}
}
function _createRule(ruleName) {
// create basic rule object
var rule = {
ruleName: ruleName
};
// add paths
rule.sourcePath = templates.ruleSourcePath(rule);
rule.documentationPath = templates.ruleDocumentationPath(rule);
rule.examplesPath = templates.ruleExamplesPath(rule);
// parse rule comments
var mainRuleComment = parseComments(fs.readFileSync(rule.sourcePath).toString())[0];
// set lead, description, linkDescription and styleguideReferences
rule.lead = mainRuleComment.lead;
rule.description = mainRuleComment.description.trim();
rule.linkDescription = mainRuleComment.linkDescription ? mainRuleComment.linkDescription : rule.lead;
rule.styleguideReferences = mainRuleComment.styleguideReferences || [];
rule.version = mainRuleComment.version;
rule.category = mainRuleComment.category || 'uncategorizedRule';
rule.deprecated = !!mainRuleComment.deprecated;
rule.sinceAngularVersion = mainRuleComment.sinceAngularVersion;
if (rule.deprecated) {
rule.deprecationReason = mainRuleComment.deprecated;
rule.category = 'deprecatedRule';
}
if (!rule.version) {
throw new Error('No @version found for ' + ruleName);
}
// load rule module for tests
rule.module = require('../rules/' + rule.ruleName);// eslint-disable-line global-require
// load examples, prepare them for the tests and group the for the template
rule.allExamples = _loadExamples(rule);
rule.examples = {
valid: _filterByValidity(rule.allExamples, true),
invalid: _filterByValidity(rule.allExamples, false)
};
rule.groupedExamples = [];
var examplesGroupedByConfig = _.groupBy(rule.allExamples, 'jsonOptions');
_.each(examplesGroupedByConfig, function(examples, config) {
// append invalid examples if existing
_appendGroupedExamplesByValidity(rule.groupedExamples, examples, config, false);
// append valid examples if existing
_appendGroupedExamplesByValidity(rule.groupedExamples, examples, config, true);
});
rule.hasOnlyOneConfig = Object.keys(examplesGroupedByConfig).length === 1;
return rule;
}