eslint-docgen 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/build-docs-from-tests.js +59 -50
- package/src/fix.js +26 -19
- package/src/rule-tester.js +3 -14
- package/src/write-docs-from-tests.js +10 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-docgen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Automatically generate ESLint plugin documentation from rule metadata and test cases.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"chalk": "^4.1.2",
|
|
27
27
|
"ejs": "^3.1.6",
|
|
28
|
-
"eslint": ">=
|
|
28
|
+
"eslint": ">=8.0.0",
|
|
29
29
|
"import-fresh": "^3.3.0",
|
|
30
30
|
"jsonschema": "^1.4.0",
|
|
31
31
|
"merge-options": "^3.0.4",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"upath": "^2.0.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"eslint-config-wikimedia": "^0.
|
|
39
|
+
"eslint-config-wikimedia": "^0.22.1",
|
|
40
40
|
"eslint-plugin-node": "^11.1.0",
|
|
41
|
-
"mocha": "^9.
|
|
41
|
+
"mocha": "^9.2.0",
|
|
42
42
|
"nyc": "^15.1.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const pluralize = require( 'pluralize' );
|
|
4
4
|
const path = require( 'upath' );
|
|
5
|
+
// Mapping from extension to language name for markdown
|
|
6
|
+
// Default language is 'js'
|
|
7
|
+
const languageFromExtension = {
|
|
8
|
+
'.html': 'html',
|
|
9
|
+
'.ts': 'ts',
|
|
10
|
+
'.vue': 'vue'
|
|
11
|
+
};
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Format a list of items with natural language
|
|
@@ -30,7 +37,7 @@ function mdLink( target, label ) {
|
|
|
30
37
|
return '[' + label + '](' + target + ')';
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
function buildDocsFromTests(
|
|
40
|
+
async function buildDocsFromTests(
|
|
34
41
|
name, ruleMeta, tests, configMap, config, globalTemplates, loadRuleTemplate, testerConfig
|
|
35
42
|
) {
|
|
36
43
|
|
|
@@ -60,28 +67,31 @@ function buildDocsFromTests(
|
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
|
-
*
|
|
64
|
-
* @
|
|
65
|
-
* @return {string|undefined} The base file name, or undefined if not set
|
|
70
|
+
* @param {string|Object} test Test case
|
|
71
|
+
* @return {string|null} The base file name, or null if not set
|
|
66
72
|
*/
|
|
67
73
|
function getFilename( test ) {
|
|
68
74
|
if ( typeof test === 'string' || test.filename === undefined ) {
|
|
69
|
-
// Either the test is just a code snippet, or
|
|
70
|
-
|
|
71
|
-
return undefined;
|
|
75
|
+
// Either the test is just a code snippet, or no file name is set
|
|
76
|
+
return null;
|
|
72
77
|
}
|
|
73
|
-
// We don't need the full file path, just the last bit with the name and extension
|
|
74
78
|
return path.basename( test.filename );
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
function buildRuleDetails( testList,
|
|
81
|
+
async function buildRuleDetails( testList, isValid, showFixes ) {
|
|
82
|
+
testList = testList.filter( ( test ) =>
|
|
83
|
+
test.docgen === undefined ?
|
|
84
|
+
!config.excludeExamplesByDefault :
|
|
85
|
+
test.docgen
|
|
86
|
+
);
|
|
87
|
+
|
|
78
88
|
let fixedCode, fixedOutput, maxCodeLength;
|
|
79
89
|
const testsByOptions = {};
|
|
80
90
|
|
|
81
91
|
const codeList = testList.map( getCode );
|
|
82
92
|
if ( config.fixCodeExamples ) {
|
|
83
93
|
const fix = require( './fix' );
|
|
84
|
-
fixedCode = fix.batchLintFix( codeList, testerConfig );
|
|
94
|
+
fixedCode = await fix.batchLintFix( codeList, testerConfig );
|
|
85
95
|
} else {
|
|
86
96
|
fixedCode = codeList;
|
|
87
97
|
}
|
|
@@ -100,7 +110,7 @@ function buildDocsFromTests(
|
|
|
100
110
|
const outputList = testList.map( ( test ) => test.output );
|
|
101
111
|
if ( config.fixCodeExamples ) {
|
|
102
112
|
const fix = require( './fix' );
|
|
103
|
-
fixedOutput = fix.batchLintFix( outputList, testerConfig );
|
|
113
|
+
fixedOutput = await fix.batchLintFix( outputList, testerConfig );
|
|
104
114
|
} else {
|
|
105
115
|
fixedOutput = outputList;
|
|
106
116
|
}
|
|
@@ -111,39 +121,37 @@ function buildDocsFromTests(
|
|
|
111
121
|
const codeSet = {};
|
|
112
122
|
let previousMultiLine = false;
|
|
113
123
|
testList.forEach( function ( test, i ) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
const filename = getFilename( test );
|
|
125
|
+
let lang = 'js';
|
|
126
|
+
// Switch to Vue if we are showing file names and its a Vue file.
|
|
127
|
+
// optionsAndSettings.filename is only set if it should be shown
|
|
128
|
+
// TODO should we add other languages too?
|
|
129
|
+
if ( filename ) {
|
|
130
|
+
const ext = path.extname( filename );
|
|
131
|
+
if ( ext in languageFromExtension ) {
|
|
132
|
+
lang = languageFromExtension[ ext ];
|
|
133
|
+
}
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
// Keys are in reverse sort order, e.g. examples with options
|
|
137
|
+
// are shown before examples with settings
|
|
138
|
+
const optionsAndSettings = {
|
|
139
|
+
// Always separate tests by language for syntax highlighting
|
|
140
|
+
lang: lang,
|
|
141
|
+
settings: test.settings,
|
|
142
|
+
options: test.options,
|
|
143
|
+
// Only group by filename if filenames are shown
|
|
144
|
+
filename: config.showFilenames && filename
|
|
145
|
+
};
|
|
146
|
+
const hashObject = {};
|
|
147
|
+
// Ensure examples without a specific key are shown first
|
|
148
|
+
Object.keys( optionsAndSettings ).forEach( ( key ) => {
|
|
149
|
+
hashObject[ key ] = optionsAndSettings[ key ] ?
|
|
150
|
+
[ '1', optionsAndSettings[ key ] ] :
|
|
151
|
+
[ '0', optionsAndSettings[ key ] ];
|
|
152
|
+
} );
|
|
129
153
|
|
|
130
|
-
|
|
131
|
-
// Only include the filename if it should be shown
|
|
132
|
-
// Don't create an object if there are no options or settings and the file name is not
|
|
133
|
-
// set, so that those examples are sorted to the top of the docs
|
|
134
|
-
if (
|
|
135
|
-
test.options || test.settings ||
|
|
136
|
-
( config.showFilenames && getFilename( test ) )
|
|
137
|
-
) {
|
|
138
|
-
optionsAndSettings = {
|
|
139
|
-
options: test.options,
|
|
140
|
-
settings: test.settings
|
|
141
|
-
};
|
|
142
|
-
if ( config.showFilenames ) {
|
|
143
|
-
optionsAndSettings.filename = getFilename( test );
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const hash = optionsAndSettings ? JSON.stringify( optionsAndSettings ) : '';
|
|
154
|
+
const hash = JSON.stringify( hashObject );
|
|
147
155
|
|
|
148
156
|
codeSet[ hash ] = codeSet[ hash ] || {};
|
|
149
157
|
|
|
@@ -168,9 +176,8 @@ function buildDocsFromTests(
|
|
|
168
176
|
};
|
|
169
177
|
|
|
170
178
|
if ( showFixes && test.output ) {
|
|
171
|
-
const output = fixedOutput[ i ];
|
|
172
179
|
const codeLines = code.split( '\n' );
|
|
173
|
-
const outputLines =
|
|
180
|
+
const outputLines = fixedOutput[ i ].split( '\n' );
|
|
174
181
|
const maxLines = Math.max( codeLines.length, outputLines.length );
|
|
175
182
|
const exampleLines = [];
|
|
176
183
|
for ( let l = 0; l < maxLines; l++ ) {
|
|
@@ -188,7 +195,8 @@ function buildDocsFromTests(
|
|
|
188
195
|
example = code;
|
|
189
196
|
multiLine = code.split( '\n' ).length > 1;
|
|
190
197
|
}
|
|
191
|
-
|
|
198
|
+
// Put linebreaks around multi-line examples
|
|
199
|
+
if ( testsByOptions[ hash ].tests.length && ( multiLine || previousMultiLine ) ) {
|
|
192
200
|
example = '\n' + example;
|
|
193
201
|
}
|
|
194
202
|
testsByOptions[ hash ].tests.push( example );
|
|
@@ -207,18 +215,19 @@ function buildDocsFromTests(
|
|
|
207
215
|
if ( config.fixCodeExamples ) {
|
|
208
216
|
const fix = require( './fix' );
|
|
209
217
|
// Fixes whitespace in block comment. Too expensive for such a small fix?
|
|
210
|
-
comments = fix.batchLintFix( comments, testerConfig );
|
|
218
|
+
comments = await fix.batchLintFix( comments, testerConfig );
|
|
211
219
|
}
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
return Object.keys( testsByOptions ).map( ( key, i ) => {
|
|
215
223
|
const section = testsByOptions[ key ];
|
|
216
224
|
const optionsAndSettings = section.optionsAndSettings;
|
|
225
|
+
const lang = optionsAndSettings && optionsAndSettings.lang;
|
|
217
226
|
const options = optionsAndSettings && optionsAndSettings.options;
|
|
218
227
|
const settings = optionsAndSettings && optionsAndSettings.settings;
|
|
219
228
|
const filename = optionsAndSettings && optionsAndSettings.filename;
|
|
220
229
|
|
|
221
|
-
let examples = '```
|
|
230
|
+
let examples = '```' + lang + '\n';
|
|
222
231
|
if ( config.showConfigComments ) {
|
|
223
232
|
examples += comments[ i ] + '\n';
|
|
224
233
|
}
|
|
@@ -227,7 +236,7 @@ function buildDocsFromTests(
|
|
|
227
236
|
|
|
228
237
|
return {
|
|
229
238
|
key: key,
|
|
230
|
-
valid:
|
|
239
|
+
valid: isValid,
|
|
231
240
|
options: options ? JSON.stringify( options ) : '',
|
|
232
241
|
settings: settings ? JSON.stringify( settings ) : '',
|
|
233
242
|
filename: filename || '',
|
|
@@ -259,8 +268,8 @@ function buildDocsFromTests(
|
|
|
259
268
|
} );
|
|
260
269
|
}
|
|
261
270
|
|
|
262
|
-
const invalid = buildRuleDetails( tests.invalid, false );
|
|
263
|
-
const valid = buildRuleDetails( tests.valid, true );
|
|
271
|
+
const invalid = await buildRuleDetails( tests.invalid, false );
|
|
272
|
+
const valid = await buildRuleDetails( tests.valid, true );
|
|
264
273
|
|
|
265
274
|
const validInvalid = invalid.concat( valid ).sort( ( a, b ) => {
|
|
266
275
|
return a.key === b.key ?
|
|
@@ -270,7 +279,7 @@ function buildDocsFromTests(
|
|
|
270
279
|
|
|
271
280
|
let fixed = [];
|
|
272
281
|
if ( ruleMeta.fixable && config.showFixExamples ) {
|
|
273
|
-
fixed = buildRuleDetails(
|
|
282
|
+
fixed = await buildRuleDetails(
|
|
274
283
|
tests.invalid.filter( ( test ) => !!test.output ),
|
|
275
284
|
false,
|
|
276
285
|
true
|
package/src/fix.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const linter = new
|
|
5
|
-
const
|
|
6
|
-
const config = cli.getConfigForFile( require( './main' ) );
|
|
7
|
-
const rules = cli.getRules();
|
|
3
|
+
const { Linter, ESLint } = require( 'eslint' );
|
|
4
|
+
const linter = new Linter();
|
|
5
|
+
const eslint = new ESLint();
|
|
8
6
|
const mergeOptions = require( 'merge-options' );
|
|
7
|
+
// eslint-disable-next-line node/no-missing-require
|
|
8
|
+
const { builtinRules } = require( 'eslint/use-at-your-own-risk' );
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
async function getConfig() {
|
|
11
|
+
const config = await eslint.calculateConfigForFile( require( './main' ) );
|
|
12
|
+
|
|
13
|
+
// Optimization: Only use fixable rules
|
|
14
|
+
const fixableRules = {};
|
|
15
|
+
Object.keys( config.rules ).forEach( function ( ruleName ) {
|
|
16
|
+
if ( builtinRules.has( ruleName ) ) {
|
|
17
|
+
const rule = builtinRules.get( ruleName );
|
|
18
|
+
if ( rule.meta.fixable ) {
|
|
19
|
+
fixableRules[ ruleName ] = config.rules[ ruleName ];
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
} );
|
|
23
|
+
config.rules = fixableRules;
|
|
24
|
+
|
|
25
|
+
return config;
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
29
|
* Lint and fix some code
|
|
@@ -26,8 +32,8 @@ config.rules = fixableRules;
|
|
|
26
32
|
* @param {Object} testerConfig Config
|
|
27
33
|
* @return {string} Fixed code
|
|
28
34
|
*/
|
|
29
|
-
function lintFix( code, testerConfig ) {
|
|
30
|
-
const mergedConfig = mergeOptions(
|
|
35
|
+
async function lintFix( code, testerConfig ) {
|
|
36
|
+
const mergedConfig = mergeOptions( await getConfig(), testerConfig );
|
|
31
37
|
|
|
32
38
|
// TODO
|
|
33
39
|
// istanbul ignore next
|
|
@@ -55,11 +61,12 @@ function lintFix( code, testerConfig ) {
|
|
|
55
61
|
* @param {Object} testerConfig Config
|
|
56
62
|
* @return {string[]} Fixed code list
|
|
57
63
|
*/
|
|
58
|
-
function batchLintFix( codeList, testerConfig ) {
|
|
64
|
+
async function batchLintFix( codeList, testerConfig ) {
|
|
59
65
|
const separator = '\n/* - */\n';
|
|
60
66
|
const codeBlock = codeList.join( ';' + separator );
|
|
61
67
|
// Add an extra semicolon to avoid syntax error
|
|
62
|
-
|
|
68
|
+
const fixed = await lintFix( codeBlock, testerConfig );
|
|
69
|
+
return fixed.split( separator ).map( ( code ) => code.trim() );
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
module.exports = {
|
package/src/rule-tester.js
CHANGED
|
@@ -3,37 +3,26 @@
|
|
|
3
3
|
const packagePath = require( './package-path' );
|
|
4
4
|
// Use plugin's version of ESLint
|
|
5
5
|
const ESLintRuleTester = require( packagePath( 'node_modules/eslint' ) ).RuleTester;
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
if ( process.argv.includes( '--doc' ) ) {
|
|
9
|
-
const formatter = require( './formatter' );
|
|
10
|
-
console.log(
|
|
11
|
-
formatter.warn( '--doc is deprecated, use the DOCGEN=1 environment variable instead.' )
|
|
12
|
-
);
|
|
13
|
-
}
|
|
6
|
+
const inDocMode = !!process.env.DOCGEN;
|
|
14
7
|
|
|
15
8
|
/**
|
|
16
9
|
* Extends ESLint's RuleTester to also build documentation
|
|
17
10
|
*/
|
|
18
11
|
class RuleTester extends ESLintRuleTester {
|
|
19
12
|
run( name, rule, tests ) {
|
|
20
|
-
if (
|
|
13
|
+
if ( inDocMode ) {
|
|
21
14
|
RuleTester.it( name, ( done ) => {
|
|
22
15
|
const writeDocsFromTests = require( './write-docs-from-tests' );
|
|
23
16
|
writeDocsFromTests( name, rule, tests, this.testerConfig, done );
|
|
24
17
|
} );
|
|
25
18
|
} else {
|
|
26
|
-
// Filter out invalid property "
|
|
19
|
+
// Filter out invalid property "docgen"
|
|
27
20
|
// (used in documentation building mode).
|
|
28
21
|
tests.valid.forEach( ( test ) => {
|
|
29
22
|
delete test.docgen;
|
|
30
|
-
// Deprecated
|
|
31
|
-
delete test.noDoc;
|
|
32
23
|
} );
|
|
33
24
|
tests.invalid.forEach( ( test ) => {
|
|
34
25
|
delete test.docgen;
|
|
35
|
-
// Deprecated
|
|
36
|
-
delete test.noDoc;
|
|
37
26
|
} );
|
|
38
27
|
|
|
39
28
|
// Filter out invalid top level property "docgenConfig"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* eslint-disable no-process-exit */
|
|
4
3
|
const fs = require( 'fs' );
|
|
5
4
|
const mkdirp = require( 'mkdirp' );
|
|
6
5
|
const path = require( 'upath' );
|
|
@@ -14,20 +13,14 @@ let config, configPath;
|
|
|
14
13
|
try {
|
|
15
14
|
( { config, configPath } = getConfig() );
|
|
16
15
|
} catch ( e ) {
|
|
17
|
-
|
|
18
|
-
console.log( formatter.error( e.message ) );
|
|
19
|
-
console.log();
|
|
20
|
-
process.exit( 1 );
|
|
16
|
+
throw new Error( [ formatter.heading( 'eslint-docgen' ), formatter.error( e.message ) ].join( '\n' ) );
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
const configValidator = require( './validate-config' );
|
|
24
20
|
function assertValidConfig( maybeValidConfig, configSource ) {
|
|
25
21
|
const configErrors = configValidator( maybeValidConfig );
|
|
26
22
|
if ( configErrors.length ) {
|
|
27
|
-
|
|
28
|
-
configErrors.forEach( ( error ) => console.log( formatter.error( error ) ) );
|
|
29
|
-
console.log();
|
|
30
|
-
process.exit( 1 );
|
|
23
|
+
throw new Error( [ formatter.heading( configSource ), ...configErrors.map( formatter.error ) ].join( '\n' ) );
|
|
31
24
|
}
|
|
32
25
|
}
|
|
33
26
|
|
|
@@ -42,7 +35,7 @@ if ( config.globalTemplatePath ) {
|
|
|
42
35
|
}
|
|
43
36
|
const { globalTemplates, loadRuleTemplate } = loadTemplates( templatePaths );
|
|
44
37
|
|
|
45
|
-
function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
38
|
+
async function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
46
39
|
// If the tests have a `docgenConfig` property, this overrides the global configuration
|
|
47
40
|
let configForRule = config;
|
|
48
41
|
if ( tests.docgenConfig !== undefined ) {
|
|
@@ -53,25 +46,17 @@ function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
|
53
46
|
const outputPath = packagePath( configForRule.docPath.replace( '{name}', name ) );
|
|
54
47
|
const ruleWithConfig = rulesWithConfig.get( name );
|
|
55
48
|
if ( !ruleWithConfig ) {
|
|
56
|
-
|
|
57
|
-
console.log( formatter.heading( outputPath ) );
|
|
58
|
-
console.log( formatter.error( 'Rule not found.' ) );
|
|
59
|
-
console.log();
|
|
60
|
-
process.exit( 1 );
|
|
49
|
+
throw new Error( [ formatter.heading( outputPath ), formatter.error( 'Rule not found.' ) ].join( '\n' ) );
|
|
61
50
|
}
|
|
62
51
|
const configMap = rulesWithConfig.get( name ).configMap;
|
|
63
52
|
let output, messages;
|
|
64
53
|
try {
|
|
65
|
-
( { output, messages } = buildDocsFromTests(
|
|
54
|
+
( { output, messages } = await buildDocsFromTests(
|
|
66
55
|
name, rule.meta, tests, configMap, configForRule,
|
|
67
56
|
globalTemplates, loadRuleTemplate, testerConfig
|
|
68
57
|
) );
|
|
69
58
|
} catch ( e ) {
|
|
70
|
-
|
|
71
|
-
console.log( formatter.heading( outputPath ) );
|
|
72
|
-
console.log( formatter.error( e.message ) );
|
|
73
|
-
console.log();
|
|
74
|
-
process.exit( 1 );
|
|
59
|
+
throw new Error( [ formatter.heading( outputPath ), formatter.error( e.message ) ].join( '\n' ) );
|
|
75
60
|
}
|
|
76
61
|
|
|
77
62
|
const outputDir = path.dirname( outputPath );
|
|
@@ -92,8 +77,10 @@ function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
|
92
77
|
console.log();
|
|
93
78
|
}
|
|
94
79
|
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
const errors = messages.filter( ( message ) => message.type === 'error' );
|
|
81
|
+
|
|
82
|
+
if ( errors.length ) {
|
|
83
|
+
throw new Error( errors.map( formatter.error ).join( '\n' ) );
|
|
97
84
|
}
|
|
98
85
|
|
|
99
86
|
done();
|