UNPKG

7.16 kBJavaScriptView Raw
1'use strict';
2
3var fs = require('fs');
4var parseComments = require('parse-comments');
5var _ = require('lodash');
6var eslintAngularIndex = require('../index');
7var ruleCategories = require('./ruleCategories.json');
8var RuleTester = require('eslint').RuleTester;
9
10var templates = require('./templates.js');
11
12var ruleNames = Object.keys(eslintAngularIndex.rules).filter(function(ruleName) {
13 // filter legacy rules
14 return !/^ng_/.test(ruleName);
15});
16
17// create rule documentation objects from ruleNames
18var rules = ruleNames.map(_createRule);
19
20module.exports = {
21 rules: rules,
22 createDocFiles: createDocFiles,
23 updateReadme: updateReadme,
24 testDocs: testDocs
25};
26
27/**
28 * Create a markdown documentation for each rule from rule comments and examples.
29 *
30 * @param cb callback
31 */
32function createDocFiles(cb) {
33 this.rules.forEach(function(rule) {
34 fs.writeFileSync(rule.documentationPath, _trimTrailingSpacesAndMultilineBreaks(templates.ruleDocumentationContent(rule)));
35 });
36 (cb || _.noop)();
37}
38
39/**
40 * Update the rules section in the readme file.
41 *
42 * @param cb callback
43 */
44function updateReadme(readmePath, cb) {
45 ruleCategories.rulesByCategory = _.groupBy(this.rules, 'category');
46
47 // filter categories without rules
48 ruleCategories.categoryOrder = ruleCategories.categoryOrder.filter(function(categoryName) {
49 var rulesForCategory = ruleCategories.rulesByCategory[categoryName];
50 return rulesForCategory && rulesForCategory.length > 0;
51 });
52
53 var readmeContent = fs.readFileSync(readmePath).toString();
54 var newRuleSection = templates.readmeRuleSectionContent(ruleCategories);
55
56 // use split and join to prevent the replace() and dollar sign problem (http://stackoverflow.com/questions/9423722)
57 readmeContent = readmeContent.split(/## Rules[\S\s]*?----\n/).join(newRuleSection);
58
59 fs.writeFileSync(readmePath, readmeContent);
60 (cb || _.noop)();
61}
62
63/**
64 * Test documentation examples.
65 *
66 * @param cb callback
67 */
68function testDocs(cb) {
69 this.rules.forEach(function(rule) {
70 if (rule.examples !== undefined) {
71 var eslintTester = new RuleTester();
72 eslintTester.run(rule.ruleName, rule.module, rule.examples);
73 }
74 });
75 (cb || _.noop)();
76}
77
78function _trimTrailingSpacesAndMultilineBreaks(content) {
79 return content.replace(/( )+\n/g, '\n').replace(/\n\n+\n/g, '\n\n').replace(/\n+$/g, '\n');
80}
81
82function _parseConfigLine(configLine) {
83 // surround config keys with quotes for json parsing
84 var preparedConfigLine = configLine.replace(/(\w+):/g, '"$1":').replace('\\:', ':');
85 var example = JSON.parse('{' + preparedConfigLine + '}');
86 return example;
87}
88
89function _wrapErrorMessage(errorMessage) {
90 return {
91 message: errorMessage
92 };
93}
94
95function _parseExample(exampleSource) {
96 var lines = exampleSource.split('\n');
97
98 // parse first example line as config
99 var example = _parseConfigLine(lines[0]);
100
101 // other lines are the example code
102 example.code = lines.slice(1).join('\n').trim();
103
104 // wrap the errorMessage in the format needed for the eslint rule tester.
105 if (example.errorMessage) {
106 if (_.isString(example.errorMessage)) {
107 example.errors = [_wrapErrorMessage(example.errorMessage)];
108 } else {
109 throw new Error('Example "errorMessage" must be a string');
110 }
111 } else if (example.errorMessages) {
112 if (_.isArray(example.errorMessages)) {
113 example.errors = example.errorMessages.map(_wrapErrorMessage);
114 } else {
115 throw new Error('Example "errorMessages" must be an array');
116 }
117 }
118
119 // invalid examples require an errorMessage
120 if (!example.valid && !(example.errorMessage || example.errorMessages)) {
121 throw new Error('Example config requires "errorMessage(s)" when valid: false');
122 }
123
124 // json options needed as group key
125 example.jsonOptions = example.options ? JSON.stringify(example.options) : '';
126
127 // use options for tests or default options of no options are configured
128 if (example.options) {
129 example.displayOptions = example.options;
130 }
131
132 return example;
133}
134
135function _loadExamples(rule) {
136 var examplesSource = fs.readFileSync(rule.examplesPath).toString();
137 var exampleRegex = /\s*\/\/\s*example\s*-/;
138 if (!exampleRegex.test(examplesSource)) {
139 return [];
140 }
141
142 return examplesSource.split(exampleRegex).slice(1).map(_parseExample.bind(rule));
143}
144
145function _filterByValidity(examples, validity) {
146 return _.filter(examples, {valid: validity}) || [];
147}
148
149function _appendGroupedExamplesByValidity(groupedExamples, examples, config, validity) {
150 var examplesByValidity = _filterByValidity(examples, validity);
151 if (examplesByValidity.length > 0) {
152 groupedExamples.push({
153 config: config,
154 valid: validity,
155 examples: examplesByValidity
156 });
157 }
158}
159
160function _createRule(ruleName) {
161 // create basic rule object
162 var rule = {
163 ruleName: ruleName
164 };
165
166 // add paths
167 rule.sourcePath = templates.ruleSourcePath(rule);
168 rule.documentationPath = templates.ruleDocumentationPath(rule);
169 rule.examplesPath = templates.ruleExamplesPath(rule);
170
171 // parse rule comments
172 var mainRuleComment = parseComments(fs.readFileSync(rule.sourcePath).toString())[0];
173
174 // set lead, description, linkDescription and styleguideReferences
175 rule.lead = mainRuleComment.lead;
176 rule.description = mainRuleComment.description.trim();
177 rule.linkDescription = mainRuleComment.linkDescription ? mainRuleComment.linkDescription : rule.lead;
178 rule.styleguideReferences = mainRuleComment.styleguideReferences || [];
179 rule.version = mainRuleComment.version;
180 rule.category = mainRuleComment.category || 'uncategorizedRule';
181
182 rule.deprecated = !!mainRuleComment.deprecated;
183 rule.sinceAngularVersion = mainRuleComment.sinceAngularVersion;
184
185 if (rule.deprecated) {
186 rule.deprecationReason = mainRuleComment.deprecated;
187 rule.category = 'deprecatedRule';
188 }
189
190 if (!rule.version) {
191 throw new Error('No @version found for ' + ruleName);
192 }
193
194 // load rule module for tests
195 rule.module = require('../rules/' + rule.ruleName); // eslint-disable-line global-require
196
197 // load examples, prepare them for the tests and group the for the template
198 rule.allExamples = _loadExamples(rule);
199 rule.examples = {
200 valid: _filterByValidity(rule.allExamples, true),
201 invalid: _filterByValidity(rule.allExamples, false)
202 };
203
204 rule.groupedExamples = [];
205 var examplesGroupedByConfig = _.groupBy(rule.allExamples, 'jsonOptions');
206 _.each(examplesGroupedByConfig, function(examples, config) {
207 // append invalid examples if existing
208 _appendGroupedExamplesByValidity(rule.groupedExamples, examples, config, false);
209
210 // append valid examples if existing
211 _appendGroupedExamplesByValidity(rule.groupedExamples, examples, config, true);
212 });
213 rule.hasOnlyOneConfig = Object.keys(examplesGroupedByConfig).length === 1;
214
215 return rule;
216}