eslint-docgen 0.7.0 → 0.8.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/README.md +24 -3
- package/package.json +12 -10
- package/src/build-docs-from-tests.js +21 -15
- package/src/build-readme.js +108 -0
- package/src/config-schema.json +2 -0
- package/src/default-config.js +3 -0
- package/src/fix.js +4 -4
- package/src/formatter.js +2 -2
- package/src/get-config.js +11 -5
- package/src/load-templates.js +10 -8
- package/src/main.js +1 -0
- package/src/naming.js +6 -4
- package/src/rule-tester.js +1 -0
- package/src/rules-with-config.js +20 -2
- package/src/templates/inConfig.ejs +1 -1
- package/src/validate-config.js +1 -2
- package/src/write-docs-from-tests.js +7 -13
package/README.md
CHANGED
|
@@ -25,6 +25,18 @@ To build your documentation, run your rule tests with the `DOCGEN` environment v
|
|
|
25
25
|
DOCGEN=1 mocha tests/rules/
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
You could add this to your package.json to make it available as `npm run doc`, e.g.
|
|
29
|
+
```jsonc
|
|
30
|
+
{
|
|
31
|
+
//
|
|
32
|
+
"scripts": {
|
|
33
|
+
//
|
|
34
|
+
"doc": "rm -rf docs/rules && DOCGEN=1 mochan tests/rules/"
|
|
35
|
+
}
|
|
36
|
+
//
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
28
40
|
Documentation will be built using **rule metadata** and **test data** passed to `RuleTester`:
|
|
29
41
|
|
|
30
42
|
#### `rule.meta.docs.description`
|
|
@@ -104,6 +116,14 @@ When defined, will try to use a rule specific template instead of [`index.ejs`](
|
|
|
104
116
|
#### `globalTemplatePath`
|
|
105
117
|
When defined, templates in this path will override the global templates defined in [`src/templates`](src/templates).
|
|
106
118
|
|
|
119
|
+
#### `readmeTemplate` (default `'README.md.template'`)
|
|
120
|
+
File path containing a README template, with placeholders for a generated rules list (`<!-- rules -->`), and optionally deprecated rules (`<!-- deprecated -->`).
|
|
121
|
+
|
|
122
|
+
Invoke by running `eslint-docgen-build-readme`.
|
|
123
|
+
|
|
124
|
+
#### `readmeTarget` (default `'README.md'`)
|
|
125
|
+
File path to write generated README to, after compiling `readmeTemplate`.
|
|
126
|
+
|
|
107
127
|
#### `docLink` (default `false`)
|
|
108
128
|
Add a link to the documentation source in the "Resources" section.
|
|
109
129
|
|
|
@@ -146,7 +166,7 @@ Number of spaces to convert tabs to in code examples. Tabs in examples are alway
|
|
|
146
166
|
|
|
147
167
|
## 🔍 Rules index
|
|
148
168
|
|
|
149
|
-
To assist with building an index of your rules, for example to put in a root README, this package exports `rulesWithConfig`. The value is a Map much like the one returned by [Linter#getRules](https://eslint.org/docs/developer-guide/nodejs-api#linter-getrules) but each rule has an additional `configMap` property that describes which configs include the rule and
|
|
169
|
+
To assist with building an index of your rules, for example to put in a root README, this package exports `rulesWithConfig`. The value is a Map much like the one returned by [Linter#getRules](https://eslint.org/docs/developer-guide/nodejs-api#linter-getrules) but each rule has an additional `configMap` property that describes which configs include the rule, the severity (normalised to a string) and options used (`null` if none are used).
|
|
150
170
|
|
|
151
171
|
Note that the rule names do not include the plugin prefix.
|
|
152
172
|
|
|
@@ -158,8 +178,9 @@ require( 'eslint-docgen' ).rulesWithConfig.get( 'no-event-shorthand' );
|
|
|
158
178
|
meta: [Object],
|
|
159
179
|
create: [Function],
|
|
160
180
|
configMap: Map {
|
|
161
|
-
'deprecated-
|
|
162
|
-
'deprecated-3.
|
|
181
|
+
'deprecated-4.0' => { severity: 'error', options: null },
|
|
182
|
+
'deprecated-3.5' => { severity: 'warn', options: null },
|
|
183
|
+
'deprecated-3.3' => { severity: 'warn', options: [ { allowAjaxEvents: true } ] }
|
|
163
184
|
}
|
|
164
185
|
}
|
|
165
186
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-docgen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Automatically generate ESLint plugin documentation from rule metadata and test cases.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"documentation"
|
|
13
13
|
],
|
|
14
14
|
"main": "src/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"eslint-docgen-build-readme": "src/build-readme.js"
|
|
17
|
+
},
|
|
15
18
|
"files": [
|
|
16
19
|
"src"
|
|
17
20
|
],
|
|
@@ -20,25 +23,24 @@
|
|
|
20
23
|
"report": "nyc report --reporter=html"
|
|
21
24
|
},
|
|
22
25
|
"engines": {
|
|
23
|
-
"node": ">=
|
|
26
|
+
"node": ">=20.0.0"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"chalk": "^4.1.2",
|
|
27
|
-
"ejs": "^
|
|
30
|
+
"ejs": "^4.0.1",
|
|
28
31
|
"eslint": ">=8.0.0",
|
|
29
|
-
"import-fresh": "^3.3.
|
|
30
|
-
"jsonschema": "^1.
|
|
32
|
+
"import-fresh": "^3.3.1",
|
|
33
|
+
"jsonschema": "^1.5.0",
|
|
31
34
|
"merge-options": "^3.0.4",
|
|
32
|
-
"mkdirp": "^
|
|
35
|
+
"mkdirp": "^3.0.1",
|
|
33
36
|
"pkg-dir": "^5.0.0",
|
|
34
37
|
"pluralize": "^8.0.0",
|
|
35
38
|
"simple-mock": "^0.8.0",
|
|
36
39
|
"upath": "^2.0.1"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
|
-
"eslint-config-wikimedia": "^0.
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"nyc": "^15.1.0"
|
|
42
|
+
"eslint-config-wikimedia": "^0.32.3",
|
|
43
|
+
"mocha": "^11.7.5",
|
|
44
|
+
"nyc": "^17.1.0"
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -21,9 +21,10 @@ const languageFromExtension = {
|
|
|
21
21
|
* @return {string} Concatenated list
|
|
22
22
|
*/
|
|
23
23
|
function listFormatter( list ) {
|
|
24
|
-
return list.reduce(
|
|
25
|
-
acc + ( i === 0 ? '' : ( ( i === arr.length - 1 ) ? ' and ' : ', ' ) ) + cur,
|
|
26
|
-
|
|
24
|
+
return list.reduce(
|
|
25
|
+
( acc, cur, i, arr ) => acc + ( i === 0 ? '' : ( ( i === arr.length - 1 ) ? ' and ' : ', ' ) ) + cur,
|
|
26
|
+
''
|
|
27
|
+
);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -79,8 +80,8 @@ async function buildDocsFromTests(
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
async function buildRuleDetails( testList, isValid, showFixes ) {
|
|
82
|
-
testList = testList.filter(
|
|
83
|
-
test.docgen === undefined ?
|
|
83
|
+
testList = testList.filter(
|
|
84
|
+
( test ) => test.docgen === undefined ?
|
|
84
85
|
!config.excludeExamplesByDefault :
|
|
85
86
|
test.docgen
|
|
86
87
|
);
|
|
@@ -100,12 +101,13 @@ async function buildDocsFromTests(
|
|
|
100
101
|
|
|
101
102
|
if ( showFixes ) {
|
|
102
103
|
// Calculate maxCodeLength for alignment
|
|
103
|
-
maxCodeLength = fixedCode.reduce(
|
|
104
|
-
code.split( '\n' ).reduce(
|
|
104
|
+
maxCodeLength = fixedCode.reduce(
|
|
105
|
+
( acc, code ) => code.split( '\n' ).reduce(
|
|
105
106
|
( lineAcc, line ) => Math.max( lineAcc, line.length ),
|
|
106
107
|
acc
|
|
107
108
|
),
|
|
108
|
-
|
|
109
|
+
0
|
|
110
|
+
);
|
|
109
111
|
|
|
110
112
|
const outputList = testList.map( ( test ) => test.output );
|
|
111
113
|
if ( config.fixCodeExamples ) {
|
|
@@ -120,7 +122,7 @@ async function buildDocsFromTests(
|
|
|
120
122
|
|
|
121
123
|
const codeSet = {};
|
|
122
124
|
let previousMultiLine = false;
|
|
123
|
-
testList.forEach(
|
|
125
|
+
testList.forEach( ( test, i ) => {
|
|
124
126
|
const filename = getFilename( test );
|
|
125
127
|
let lang = 'js';
|
|
126
128
|
// Switch to Vue if we are showing file names and its a Vue file.
|
|
@@ -260,10 +262,12 @@ async function buildDocsFromTests(
|
|
|
260
262
|
let inConfigs = [];
|
|
261
263
|
if ( configMap ) {
|
|
262
264
|
inConfigs = Array.from( configMap.keys() ).map( ( configName ) => {
|
|
263
|
-
const options = configMap.get( configName );
|
|
265
|
+
const { options, severity } = configMap.get( configName );
|
|
266
|
+
|
|
264
267
|
return {
|
|
265
268
|
config: configName,
|
|
266
|
-
options: options && Object.keys( options[ 0 ] ).length ? JSON.stringify( options ) : ''
|
|
269
|
+
options: options && Object.keys( options[ 0 ] ).length ? JSON.stringify( options ) : '',
|
|
270
|
+
severity: severity
|
|
267
271
|
};
|
|
268
272
|
} );
|
|
269
273
|
}
|
|
@@ -271,11 +275,12 @@ async function buildDocsFromTests(
|
|
|
271
275
|
const invalid = await buildRuleDetails( tests.invalid, false );
|
|
272
276
|
const valid = await buildRuleDetails( tests.valid, true );
|
|
273
277
|
|
|
274
|
-
const validInvalid = invalid.concat( valid ).sort(
|
|
275
|
-
|
|
278
|
+
const validInvalid = invalid.concat( valid ).sort(
|
|
279
|
+
// sort by key, then value
|
|
280
|
+
( a, b ) => a.key === b.key ?
|
|
276
281
|
( a.valid < b.valid ? -1 : 1 ) :
|
|
277
|
-
( a.key < b.key ? -1 : 1 )
|
|
278
|
-
|
|
282
|
+
( a.key < b.key ? -1 : 1 )
|
|
283
|
+
);
|
|
279
284
|
|
|
280
285
|
let fixed = [];
|
|
281
286
|
if ( ruleMeta.fixable && config.showFixExamples ) {
|
|
@@ -323,6 +328,7 @@ async function buildDocsFromTests(
|
|
|
323
328
|
const fs = require( 'fs' );
|
|
324
329
|
const packagePath = require( './package-path' );
|
|
325
330
|
const ruleTemplatePath = packagePath( config.ruleTemplatePath.replace( '{name}', name ) );
|
|
331
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
326
332
|
if ( fs.existsSync( ruleTemplatePath ) ) {
|
|
327
333
|
index = loadRuleTemplate( ruleTemplatePath );
|
|
328
334
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const configRename = ( name ) => name.replace( 'deprecated-', '' );
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build the README contents from a template
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} config Resolved eslint-docgen config
|
|
10
|
+
* @param {Map} rulesWithConfig Map of rule names to rule data (meta and configMap)
|
|
11
|
+
* @param {string} template README template containing `<!-- rules -->` and
|
|
12
|
+
* `<!-- deprecated -->` placeholders
|
|
13
|
+
* @return {string} README contents
|
|
14
|
+
*/
|
|
15
|
+
function buildReadme( config, rulesWithConfig, template ) {
|
|
16
|
+
function getRules( deprecated ) {
|
|
17
|
+
return Array.from( rulesWithConfig.keys() ).map( ( rule ) => {
|
|
18
|
+
const fullRule = config.pluginName + '/' + rule;
|
|
19
|
+
const ruleData = rulesWithConfig.get( rule );
|
|
20
|
+
const docs = ruleData.meta.docs;
|
|
21
|
+
const configs = Array.from( ruleData.configMap.keys() ).map( ( name ) => {
|
|
22
|
+
const { options, severity } = ruleData.configMap.get( name );
|
|
23
|
+
// TODO: Create util to compare options to defaults
|
|
24
|
+
return {
|
|
25
|
+
name: configRename( name ),
|
|
26
|
+
hasOptions: !!( options && Object.keys( options[ 0 ] ).length ),
|
|
27
|
+
severity
|
|
28
|
+
};
|
|
29
|
+
} );
|
|
30
|
+
// Sort configs by name - sort numerical values by version number
|
|
31
|
+
configs.sort( ( a, b ) => {
|
|
32
|
+
if ( a.name.includes( '.' ) === -1 ) {
|
|
33
|
+
return a.name.localeCompare( b.name );
|
|
34
|
+
}
|
|
35
|
+
const aVersion = a.name.split( '.' ).map( ( n ) => parseInt( n, 10 ) );
|
|
36
|
+
const bVersion = b.name.split( '.' ).map( ( n ) => parseInt( n, 10 ) );
|
|
37
|
+
for ( let i = 0; i < Math.max( aVersion.length, bVersion.length ); i++ ) {
|
|
38
|
+
if ( aVersion[ i ] !== bVersion[ i ] ) {
|
|
39
|
+
return ( aVersion[ i ] || 0 ) - ( bVersion[ i ] || 0 );
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
} );
|
|
44
|
+
if ( !!docs.deprecated === deprecated ) {
|
|
45
|
+
return '* [`' + fullRule + '`](' + config.docPath.replace( '{name}', rule ) + ')' +
|
|
46
|
+
(
|
|
47
|
+
ruleData.meta.schema.length ?
|
|
48
|
+
' ⚙️' :
|
|
49
|
+
''
|
|
50
|
+
) +
|
|
51
|
+
(
|
|
52
|
+
ruleData.meta.fixable ?
|
|
53
|
+
' 🔧' :
|
|
54
|
+
''
|
|
55
|
+
) +
|
|
56
|
+
(
|
|
57
|
+
docs.replacedBy ?
|
|
58
|
+
' (use [`' + config.pluginName + '/' + docs.replacedBy + '`](' + config.docPath.replace( '{name}', docs.replacedBy ) + '))' :
|
|
59
|
+
''
|
|
60
|
+
) +
|
|
61
|
+
(
|
|
62
|
+
ruleData.configMap.size ?
|
|
63
|
+
' ' +
|
|
64
|
+
Array.from( configs ).map(
|
|
65
|
+
( c ) => ( c.severity === 'error' ? '**' : '' ) +
|
|
66
|
+
'`' + c.name + ( c.hasOptions ? '†' : '' ) + '`' +
|
|
67
|
+
( c.severity === 'error' ? '**' : '' ) ).join( ', ' ) :
|
|
68
|
+
''
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
} ).filter( ( rule ) => rule ).join( '\n' );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return `<!-- This file is built by eslint-docgen-build-readme. Do not edit it directly; edit ${ config.readmeTemplate } instead. -->\n` +
|
|
76
|
+
template
|
|
77
|
+
.replace( '<!-- rules -->', getRules( false ) )
|
|
78
|
+
.replace( '<!-- deprecated -->', getRules( true ) );
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* istanbul ignore next */
|
|
82
|
+
function main() {
|
|
83
|
+
const fs = require( 'fs' );
|
|
84
|
+
const rulesWithConfig = require( './rules-with-config' );
|
|
85
|
+
const getConfig = require( './get-config' );
|
|
86
|
+
|
|
87
|
+
const config = getConfig();
|
|
88
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
89
|
+
const template = fs.readFileSync( config.readmeTemplate, { encoding: 'UTF8' } );
|
|
90
|
+
|
|
91
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
92
|
+
fs.writeFile(
|
|
93
|
+
config.readmeTarget,
|
|
94
|
+
buildReadme( config, rulesWithConfig, template ),
|
|
95
|
+
( err ) => {
|
|
96
|
+
if ( err ) {
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* istanbul ignore next */
|
|
104
|
+
if ( require.main === module ) {
|
|
105
|
+
main();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = buildReadme;
|
package/src/config-schema.json
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"type": "null",
|
|
22
22
|
"message": "must be renamed to \"globalTemplatePath\""
|
|
23
23
|
},
|
|
24
|
+
"readmeTemplate": { "type": "string" },
|
|
25
|
+
"readmeTarget": { "type": "string" },
|
|
24
26
|
"docLink": { "type": "boolean" },
|
|
25
27
|
"ruleLink": { "type": "boolean" },
|
|
26
28
|
"testLink": { "type": "boolean" },
|
package/src/default-config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const packagePath = require( './package-path' );
|
|
4
|
+
// eslint-disable-next-line security/detect-non-literal-require
|
|
4
5
|
const packageName = require( packagePath( './package' ) ).name;
|
|
5
6
|
const naming = require( './naming' );
|
|
6
7
|
const pluginName = naming.getShorthandName( packageName, 'eslint-plugin' );
|
|
@@ -17,6 +18,8 @@ module.exports = {
|
|
|
17
18
|
testPath: null,
|
|
18
19
|
ruleTemplatePath: null,
|
|
19
20
|
globalTemplatePath: null,
|
|
21
|
+
readmeTemplate: 'README.md.template',
|
|
22
|
+
readmeTarget: 'README.md',
|
|
20
23
|
docLink: false,
|
|
21
24
|
ruleLink: true,
|
|
22
25
|
testLink: true,
|
package/src/fix.js
CHANGED
|
@@ -4,7 +4,6 @@ const { Linter, ESLint } = require( 'eslint' );
|
|
|
4
4
|
const linter = new Linter();
|
|
5
5
|
const eslint = new ESLint();
|
|
6
6
|
const mergeOptions = require( 'merge-options' );
|
|
7
|
-
// eslint-disable-next-line node/no-missing-require
|
|
8
7
|
const { builtinRules } = require( 'eslint/use-at-your-own-risk' );
|
|
9
8
|
|
|
10
9
|
async function getConfig() {
|
|
@@ -12,7 +11,7 @@ async function getConfig() {
|
|
|
12
11
|
|
|
13
12
|
// Optimization: Only use fixable rules
|
|
14
13
|
const fixableRules = {};
|
|
15
|
-
Object.keys( config.rules ).forEach(
|
|
14
|
+
Object.keys( config.rules ).forEach( ( ruleName ) => {
|
|
16
15
|
if ( builtinRules.has( ruleName ) ) {
|
|
17
16
|
const rule = builtinRules.get( ruleName );
|
|
18
17
|
if ( rule.meta.fixable ) {
|
|
@@ -37,8 +36,9 @@ async function lintFix( code, testerConfig ) {
|
|
|
37
36
|
|
|
38
37
|
// TODO
|
|
39
38
|
// istanbul ignore next
|
|
40
|
-
if (
|
|
41
|
-
|
|
39
|
+
if ( typeof mergedConfig.parser === 'string' ) {
|
|
40
|
+
// eslint-disable-next-line security/detect-non-literal-require
|
|
41
|
+
linter.defineParser( mergedConfig.parser, require( mergedConfig.parser ) );
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const result = linter.verifyAndFix( code, mergedConfig );
|
package/src/formatter.js
CHANGED
|
@@ -20,8 +20,8 @@ function labelSuffix( label ) {
|
|
|
20
20
|
* @return {string}
|
|
21
21
|
*/
|
|
22
22
|
function indentAfterFirst( text, indent ) {
|
|
23
|
-
return text.split( '\n' ).map(
|
|
24
|
-
i ? ' '.repeat( indent ) + line : line
|
|
23
|
+
return text.split( '\n' ).map(
|
|
24
|
+
( line, i ) => i ? ' '.repeat( indent ) + line : line
|
|
25
25
|
).join( '\n' );
|
|
26
26
|
}
|
|
27
27
|
|
package/src/get-config.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const fs = require( 'fs' );
|
|
4
4
|
const packagePath = require( './package-path' );
|
|
5
5
|
const importFresh = require( 'import-fresh' );
|
|
6
|
+
const validateConfig = require( './validate-config' );
|
|
7
|
+
const formatter = require( './formatter' );
|
|
6
8
|
|
|
7
9
|
const configFilenames = [
|
|
8
10
|
'.eslintdocgenrc.js',
|
|
@@ -15,6 +17,7 @@ const configFilenames = [
|
|
|
15
17
|
* Missing values are backfilled with the defaults
|
|
16
18
|
*
|
|
17
19
|
* @return {Object} Config
|
|
20
|
+
* @throws {Error} If no config file is found, or if the config is invalid
|
|
18
21
|
*/
|
|
19
22
|
function getConfig() {
|
|
20
23
|
let config, configPath;
|
|
@@ -22,6 +25,7 @@ function getConfig() {
|
|
|
22
25
|
configFilenames.some( ( configFilename ) => {
|
|
23
26
|
configPath = packagePath( configFilename );
|
|
24
27
|
|
|
28
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
25
29
|
if ( fs.existsSync( configPath ) ) {
|
|
26
30
|
config = importFresh( configPath );
|
|
27
31
|
return true;
|
|
@@ -30,16 +34,18 @@ function getConfig() {
|
|
|
30
34
|
} );
|
|
31
35
|
|
|
32
36
|
if ( !config ) {
|
|
33
|
-
throw new Error( '.eslintdocgenrc not found' );
|
|
37
|
+
throw new Error( [ formatter.heading( 'eslint-docgen' ), formatter.error( '.eslintdocgenrc not found' ) ].join( '\n' ) );
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
const defaultConfig = require( './default-config.js' );
|
|
37
41
|
config = Object.assign( {}, defaultConfig, config );
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
configPath
|
|
42
|
-
}
|
|
43
|
+
const configErrors = validateConfig( config );
|
|
44
|
+
if ( configErrors.length ) {
|
|
45
|
+
throw new Error( [ formatter.heading( configPath ), ...configErrors.map( formatter.error ) ].join( '\n' ) );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return config;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
module.exports = getConfig;
|
package/src/load-templates.js
CHANGED
|
@@ -5,10 +5,12 @@ const path = require( 'upath' );
|
|
|
5
5
|
const ejs = require( 'ejs' );
|
|
6
6
|
|
|
7
7
|
function loadTemplatesFromPath( dirPath ) {
|
|
8
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
8
9
|
const files = fs.readdirSync( dirPath );
|
|
9
10
|
const templates = {};
|
|
10
11
|
files.forEach( ( filename ) => {
|
|
11
12
|
templates[ path.parse( filename ).name ] =
|
|
13
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
12
14
|
fs.readFileSync( path.join( dirPath, filename ) ).toString();
|
|
13
15
|
} );
|
|
14
16
|
return templates;
|
|
@@ -31,14 +33,13 @@ function loadTemplates( dirPaths ) {
|
|
|
31
33
|
|
|
32
34
|
function compile( string, filename ) {
|
|
33
35
|
const compiled = ejs.compile( string, { client: true } );
|
|
34
|
-
return ( data ) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} );
|
|
36
|
+
return ( data ) => compiled( data, null, ( template, includeData ) => {
|
|
37
|
+
const mergedData = Object.assign( {}, data, includeData );
|
|
38
|
+
if ( !hasOwn.call( templateStrings, template ) ) {
|
|
39
|
+
throw new Error( 'Template `' + template + '` not found in template `' + filename + '`' );
|
|
40
|
+
}
|
|
41
|
+
return globalTemplates[ template ]( mergedData );
|
|
42
|
+
} );
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
Object.keys( templateStrings ).forEach( ( filename ) => {
|
|
@@ -47,6 +48,7 @@ function loadTemplates( dirPaths ) {
|
|
|
47
48
|
|
|
48
49
|
function loadRuleTemplate( ruleTemplatePath ) {
|
|
49
50
|
return compile(
|
|
51
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
50
52
|
fs.readFileSync( ruleTemplatePath ).toString()
|
|
51
53
|
);
|
|
52
54
|
}
|
package/src/main.js
CHANGED
package/src/naming.js
CHANGED
|
@@ -14,17 +14,19 @@
|
|
|
14
14
|
*/
|
|
15
15
|
function getShorthandName( fullname, prefix ) {
|
|
16
16
|
if ( fullname[ 0 ] === '@' ) {
|
|
17
|
-
|
|
17
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
18
|
+
let matchResult = new RegExp( `^(@[^/]+)/${ prefix }$`, 'u' ).exec( fullname );
|
|
18
19
|
|
|
19
20
|
if ( matchResult ) {
|
|
20
21
|
return matchResult[ 1 ];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
25
|
+
matchResult = new RegExp( `^(@[^/]+)/${ prefix }-(.+)$`, 'u' ).exec( fullname );
|
|
24
26
|
if ( matchResult ) {
|
|
25
|
-
return `${matchResult[ 1 ]}/${matchResult[ 2 ]}`;
|
|
27
|
+
return `${ matchResult[ 1 ] }/${ matchResult[ 2 ] }`;
|
|
26
28
|
}
|
|
27
|
-
} else if ( fullname.startsWith( `${prefix}-` ) ) {
|
|
29
|
+
} else if ( fullname.startsWith( `${ prefix }-` ) ) {
|
|
28
30
|
return fullname.slice( prefix.length + 1 );
|
|
29
31
|
}
|
|
30
32
|
|
package/src/rule-tester.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const packagePath = require( './package-path' );
|
|
4
4
|
// Use plugin's version of ESLint
|
|
5
|
+
// eslint-disable-next-line security/detect-non-literal-require
|
|
5
6
|
const ESLintRuleTester = require( packagePath( 'node_modules/eslint' ) ).RuleTester;
|
|
6
7
|
const inDocMode = !!process.env.DOCGEN;
|
|
7
8
|
|
package/src/rules-with-config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// eslint-disable-next-line security/detect-non-literal-require
|
|
3
4
|
const main = require( require( './main' ) );
|
|
4
|
-
const pluginName = require( './get-config' )().
|
|
5
|
+
const pluginName = require( './get-config' )().pluginName;
|
|
5
6
|
const configs = main.configs;
|
|
6
7
|
const rulesWithConfig = new Map( Object.entries( main.rules ) );
|
|
7
8
|
|
|
@@ -16,9 +17,26 @@ for ( const name in configs ) {
|
|
|
16
17
|
for ( const fullName in rules ) {
|
|
17
18
|
// Configs use the full rule name with the plugin prefix, so remove this
|
|
18
19
|
const shortName = fullName.slice( pluginName.length + 1 );
|
|
20
|
+
let options, rawSeverity;
|
|
21
|
+
|
|
22
|
+
if ( Array.isArray( rules[ fullName ] ) ) {
|
|
23
|
+
options = rules[ fullName ].slice( 1 );
|
|
24
|
+
rawSeverity = rules[ fullName ][ 0 ];
|
|
25
|
+
} else {
|
|
26
|
+
options = null;
|
|
27
|
+
rawSeverity = rules[ fullName ];
|
|
28
|
+
}
|
|
29
|
+
const severityMap = [ 'off', 'warn', 'error' ];
|
|
30
|
+
const severity = typeof rawSeverity === 'number' ?
|
|
31
|
+
severityMap[ rawSeverity ] || 'error' :
|
|
32
|
+
rawSeverity;
|
|
33
|
+
|
|
19
34
|
rulesWithConfig.get( shortName ).configMap.set(
|
|
20
35
|
name,
|
|
21
|
-
|
|
36
|
+
{
|
|
37
|
+
severity,
|
|
38
|
+
options
|
|
39
|
+
}
|
|
22
40
|
);
|
|
23
41
|
}
|
|
24
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
📋 This rule is enabled in `plugin:<%- pluginName %>/<%- inConfig.config %>`<%- include( 'optionsAndSettings', { data: inConfig } ) %>.
|
|
1
|
+
📋 This rule is enabled as <%- inConfig.severity === 'warn' ? 'a warning' : 'an error' %> in `plugin:<%- pluginName %>/<%- inConfig.config %>`<%- include( 'optionsAndSettings', { data: inConfig } ) %>.
|
package/src/validate-config.js
CHANGED
|
@@ -11,8 +11,7 @@ const schema = require( './config-schema' );
|
|
|
11
11
|
* @return {string[]} A list of errors found
|
|
12
12
|
*/
|
|
13
13
|
function validateConfig( config ) {
|
|
14
|
-
const errors = v.validate( config, schema ).errors.map( ( e ) =>
|
|
15
|
-
e.schema.message ? e.property + ' ' + e.schema.message : e.stack );
|
|
14
|
+
const errors = v.validate( config, schema ).errors.map( ( e ) => e.schema.message ? e.property + ' ' + e.schema.message : e.stack );
|
|
16
15
|
|
|
17
16
|
return errors;
|
|
18
17
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require( 'fs' );
|
|
4
|
-
const mkdirp = require( 'mkdirp' );
|
|
4
|
+
const { mkdirp } = require( 'mkdirp' );
|
|
5
5
|
const path = require( 'upath' );
|
|
6
6
|
const buildDocsFromTests = require( './build-docs-from-tests' );
|
|
7
7
|
|
|
@@ -9,23 +9,16 @@ const formatter = require( './formatter' );
|
|
|
9
9
|
const rulesWithConfig = require( './rules-with-config' );
|
|
10
10
|
|
|
11
11
|
const getConfig = require( './get-config' );
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
( { config, configPath } = getConfig() );
|
|
15
|
-
} catch ( e ) {
|
|
16
|
-
throw new Error( [ formatter.heading( 'eslint-docgen' ), formatter.error( e.message ) ].join( '\n' ) );
|
|
17
|
-
}
|
|
12
|
+
const config = getConfig();
|
|
18
13
|
|
|
19
|
-
const
|
|
14
|
+
const validateConfig = require( './validate-config' );
|
|
20
15
|
function assertValidConfig( maybeValidConfig, configSource ) {
|
|
21
|
-
const configErrors =
|
|
16
|
+
const configErrors = validateConfig( maybeValidConfig );
|
|
22
17
|
if ( configErrors.length ) {
|
|
23
18
|
throw new Error( [ formatter.heading( configSource ), ...configErrors.map( formatter.error ) ].join( '\n' ) );
|
|
24
19
|
}
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
assertValidConfig( config, configPath );
|
|
28
|
-
|
|
29
22
|
const packagePath = require( './package-path' );
|
|
30
23
|
|
|
31
24
|
const loadTemplates = require( './load-templates' );
|
|
@@ -61,6 +54,7 @@ async function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
|
61
54
|
|
|
62
55
|
const outputDir = path.dirname( outputPath );
|
|
63
56
|
mkdirp( outputDir ).then( () => {
|
|
57
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
64
58
|
fs.writeFile(
|
|
65
59
|
outputPath,
|
|
66
60
|
output,
|
|
@@ -71,8 +65,8 @@ async function writeDocsFromTests( name, rule, tests, testerConfig, done ) {
|
|
|
71
65
|
if ( messages.length ) {
|
|
72
66
|
console.log();
|
|
73
67
|
console.log( formatter.heading( outputPath ) );
|
|
74
|
-
messages.forEach(
|
|
75
|
-
console.log( formatter[ message.type ]( message.text, message.label ) )
|
|
68
|
+
messages.forEach(
|
|
69
|
+
( message ) => console.log( formatter[ message.type ]( message.text, message.label ) )
|
|
76
70
|
);
|
|
77
71
|
console.log();
|
|
78
72
|
}
|