eslint-docgen 0.8.0 → 0.8.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-docgen",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Automatically generate ESLint plugin documentation from rule metadata and test cases.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,8 +19,8 @@
19
19
  "src"
20
20
  ],
21
21
  "scripts": {
22
- "test": "eslint . --cache && nyc mocha tests/",
23
- "report": "nyc report --reporter=html"
22
+ "test": "eslint . --cache && c8 mocha tests/",
23
+ "report": "c8 report --reporter=html"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=20.0.0"
@@ -28,7 +28,6 @@
28
28
  "dependencies": {
29
29
  "chalk": "^4.1.2",
30
30
  "ejs": "^4.0.1",
31
- "eslint": ">=8.0.0",
32
31
  "import-fresh": "^3.3.1",
33
32
  "jsonschema": "^1.5.0",
34
33
  "merge-options": "^3.0.4",
@@ -38,9 +37,13 @@
38
37
  "simple-mock": "^0.8.0",
39
38
  "upath": "^2.0.1"
40
39
  },
40
+ "peerDependencies": {
41
+ "eslint": ">=8.0.0"
42
+ },
41
43
  "devDependencies": {
44
+ "c8": "^12.0.0",
45
+ "eslint": "^8.57.1",
42
46
  "eslint-config-wikimedia": "^0.32.3",
43
- "mocha": "^11.7.5",
44
- "nyc": "^17.1.0"
47
+ "mocha": "^12.0.3"
45
48
  }
46
49
  }
package/src/eslint.js ADDED
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const packagePath = require( './package-path' );
4
+
5
+ /**
6
+ * Load a module from the plugin's copy of ESLint
7
+ *
8
+ * Docs should be built with the same version of ESLint, and the same config
9
+ * system, as the plugin's tests are run with.
10
+ *
11
+ * @param {string} [subpath] Module path within the ESLint package, e.g. 'use-at-your-own-risk'
12
+ * @return {Object} Module exports
13
+ */
14
+ function requireESLint( subpath ) {
15
+ const name = subpath ? 'eslint/' + subpath : 'eslint';
16
+ // eslint-disable-next-line security/detect-non-literal-require
17
+ return require( require.resolve( name, { paths: [ packagePath( '.' ) ] } ) );
18
+ }
19
+
20
+ module.exports = requireESLint;
package/src/fix.js CHANGED
@@ -1,47 +1,150 @@
1
1
  'use strict';
2
2
 
3
- const { Linter, ESLint } = require( 'eslint' );
4
- const linter = new Linter();
5
- const eslint = new ESLint();
3
+ const requireESLint = require( './eslint' );
4
+ const { Linter, ESLint, loadESLint } = requireESLint();
5
+ const { builtinRules } = requireESLint( 'use-at-your-own-risk' );
6
6
  const mergeOptions = require( 'merge-options' );
7
- const { builtinRules } = require( 'eslint/use-at-your-own-risk' );
8
7
 
8
+ /**
9
+ * ESLint 9+'s RuleTester only supports flat config, so test cases and the
10
+ * tester config are in flat config format.
11
+ */
12
+ const isFlatRuleTester = parseInt( Linter.version, 10 ) >= 9;
13
+
14
+ const linter = new Linter( { configType: isFlatRuleTester ? 'flat' : 'eslintrc' } );
15
+
16
+ /**
17
+ * Get the plugin's ESLint config for its main file, normalized to a format
18
+ * which doesn't depend on the config system used
19
+ *
20
+ * Only fixable core rules are kept, as plugin rules would need the plugin to
21
+ * be loaded, and only fixes are needed.
22
+ *
23
+ * @return {Promise<Object>} Normalized config
24
+ */
9
25
  async function getConfig() {
10
- const config = await eslint.calculateConfigForFile( require( './main' ) );
26
+ // loadESLint (ESLint 8.57+) returns the class for the config system used
27
+ // by the plugin, i.e. depending on whether it has an eslint.config.js file
28
+ const ESLintClass = loadESLint ? await loadESLint() : ESLint;
29
+ const eslint = new ESLintClass();
30
+ const config = await eslint.calculateConfigForFile( require( './main' ) ) || {};
31
+ const isFlatConfig = ESLintClass.configType === 'flat';
32
+ const languageOptions = ( isFlatConfig ? config.languageOptions : config.parserOptions ) || {};
11
33
 
12
- // Optimization: Only use fixable rules
13
- const fixableRules = {};
14
- Object.keys( config.rules ).forEach( ( ruleName ) => {
34
+ const rules = {};
35
+ Object.keys( config.rules || {} ).forEach( ( ruleName ) => {
15
36
  if ( builtinRules.has( ruleName ) ) {
16
37
  const rule = builtinRules.get( ruleName );
17
38
  if ( rule.meta.fixable ) {
18
- fixableRules[ ruleName ] = config.rules[ ruleName ];
39
+ rules[ ruleName ] = config.rules[ ruleName ];
19
40
  }
20
41
  }
21
42
  } );
22
- config.rules = fixableRules;
23
43
 
24
- return config;
44
+ return {
45
+ rules,
46
+ settings: config.settings,
47
+ // eslintrc only, can set the ecmaVersion, e.g. "es2017"
48
+ env: config.env,
49
+ // Parser name/path (eslintrc) or object (flat config)
50
+ parser: isFlatConfig ? languageOptions.parser : config.parser,
51
+ parserOptions: isFlatConfig ? languageOptions.parserOptions : config.parserOptions,
52
+ ecmaVersion: languageOptions.ecmaVersion,
53
+ sourceType: languageOptions.sourceType
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Get a flat config array, for linting with ESLint 9+
59
+ *
60
+ * @param {Object} config Normalized config, from #getConfig
61
+ * @param {Object|Object[]} testerConfig RuleTester config
62
+ * @return {Object[]} Flat config array
63
+ */
64
+ function getFlatConfig( config, testerConfig ) {
65
+ const baseConfig = {
66
+ rules: config.rules,
67
+ languageOptions: {}
68
+ };
69
+ if ( config.settings ) {
70
+ baseConfig.settings = config.settings;
71
+ }
72
+ [ 'ecmaVersion', 'sourceType', 'parserOptions' ].forEach( ( key ) => {
73
+ if ( config[ key ] !== undefined ) {
74
+ baseConfig.languageOptions[ key ] = config[ key ];
75
+ }
76
+ } );
77
+ if ( config.parser ) {
78
+ baseConfig.languageOptions.parser = typeof config.parser === 'string' ?
79
+ // eslint-disable-next-line security/detect-non-literal-require
80
+ require( config.parser ) :
81
+ config.parser;
82
+ }
83
+ // The RuleTester config may include rules which aren't loaded here,
84
+ // e.g. "rule-tester/validate-ast", and rules aren't needed for fixing.
85
+ const testerConfigs = [].concat( testerConfig || [] ).map( ( testerConfigItem ) => {
86
+ const copy = Object.assign( {}, testerConfigItem );
87
+ delete copy.rules;
88
+ return copy;
89
+ } );
90
+ return [ baseConfig, ...testerConfigs ];
91
+ }
92
+
93
+ /**
94
+ * Get an eslintrc config, for linting with ESLint 8
95
+ *
96
+ * @param {Object} config Normalized config, from #getConfig
97
+ * @param {Object} testerConfig RuleTester config
98
+ * @return {Object} eslintrc config
99
+ */
100
+ function getEslintrcConfig( config, testerConfig ) {
101
+ const baseConfig = {
102
+ rules: config.rules,
103
+ parserOptions: Object.assign( {}, config.parserOptions )
104
+ };
105
+ if ( config.settings ) {
106
+ baseConfig.settings = config.settings;
107
+ }
108
+ if ( config.env ) {
109
+ baseConfig.env = config.env;
110
+ }
111
+ [ 'ecmaVersion', 'sourceType' ].forEach( ( key ) => {
112
+ if ( config[ key ] !== undefined ) {
113
+ baseConfig.parserOptions[ key ] = config[ key ];
114
+ }
115
+ } );
116
+ if ( config.parser ) {
117
+ baseConfig.parser = config.parser;
118
+ }
119
+
120
+ const mergedConfig = mergeOptions( baseConfig, testerConfig );
121
+
122
+ if ( mergedConfig.parser && typeof mergedConfig.parser !== 'string' ) {
123
+ // A parser object, from an ESLint 8.57 flat config
124
+ linter.defineParser( 'eslint-docgen-parser', mergedConfig.parser );
125
+ mergedConfig.parser = 'eslint-docgen-parser';
126
+ } else if ( typeof mergedConfig.parser === 'string' ) {
127
+ // eslint-disable-next-line security/detect-non-literal-require
128
+ linter.defineParser( mergedConfig.parser, require( mergedConfig.parser ) );
129
+ }
130
+
131
+ return mergedConfig;
25
132
  }
26
133
 
27
134
  /**
28
135
  * Lint and fix some code
29
136
  *
30
137
  * @param {string} code Code
31
- * @param {Object} testerConfig Config
138
+ * @param {Object|Object[]} testerConfig RuleTester config
32
139
  * @return {string} Fixed code
33
140
  */
34
141
  async function lintFix( code, testerConfig ) {
35
- const mergedConfig = mergeOptions( await getConfig(), testerConfig );
142
+ const config = await getConfig();
36
143
 
37
- // TODO
38
- // istanbul ignore next
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
- }
144
+ const result = isFlatRuleTester ?
145
+ linter.verifyAndFix( code, getFlatConfig( config, testerConfig ), { filename: 'example.js' } ) :
146
+ linter.verifyAndFix( code, getEslintrcConfig( config, testerConfig ) );
43
147
 
44
- const result = linter.verifyAndFix( code, mergedConfig );
45
148
  const err = result.messages.find( ( message ) => message.fatal );
46
149
  // TODO
47
150
  // istanbul ignore next
@@ -58,7 +161,7 @@ async function lintFix( code, testerConfig ) {
58
161
  * Concatenates the code snippets into one code block to improve performance
59
162
  *
60
163
  * @param {string[]} codeList Code list
61
- * @param {Object} testerConfig Config
164
+ * @param {Object|Object[]} testerConfig RuleTester config
62
165
  * @return {string[]} Fixed code list
63
166
  */
64
167
  async function batchLintFix( codeList, testerConfig ) {
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const packagePath = require( './package-path' );
4
3
  // Use plugin's version of ESLint
5
- // eslint-disable-next-line security/detect-non-literal-require
6
- const ESLintRuleTester = require( packagePath( 'node_modules/eslint' ) ).RuleTester;
4
+ const ESLintRuleTester = require( './eslint' )().RuleTester;
7
5
  const inDocMode = !!process.env.DOCGEN;
8
6
 
9
7
  /**
@@ -11,10 +11,44 @@ rulesWithConfig.forEach( ( rule, name ) => {
11
11
  rulesWithConfig.get( name ).configMap = new Map();
12
12
  } );
13
13
 
14
+ /**
15
+ * Check if a config is a duplicate of another config in a different format
16
+ *
17
+ * Plugins which support both eslintrc and flat config often export each config
18
+ * twice, e.g. `recommended` and `flat/recommended`, or `recommended` and
19
+ * `legacy-recommended`. Only list these once.
20
+ *
21
+ * @param {string} name Config name
22
+ * @return {boolean}
23
+ */
24
+ function isDuplicateConfig( name ) {
25
+ const match = name.match( /^(?:flat\/|legacy-)(.*)$/ );
26
+ return !!match && Object.prototype.hasOwnProperty.call( configs, match[ 1 ] );
27
+ }
28
+
29
+ /**
30
+ * Get the rules set by a config
31
+ *
32
+ * @param {Object|Object[]} config eslintrc config, flat config, or flat config array
33
+ * @return {Object} Rules
34
+ */
35
+ function getRules( config ) {
36
+ return Array.isArray( config ) ?
37
+ Object.assign( {}, ...config.map( getRules ) ) :
38
+ config.rules || {};
39
+ }
40
+
14
41
  // Iterate over configs to add config data to map
15
42
  for ( const name in configs ) {
16
- const rules = configs[ name ].rules || {};
43
+ if ( isDuplicateConfig( name ) ) {
44
+ continue;
45
+ }
46
+ const rules = getRules( configs[ name ] );
17
47
  for ( const fullName in rules ) {
48
+ // Flat config arrays can include rules from other plugins
49
+ if ( !fullName.startsWith( pluginName + '/' ) ) {
50
+ continue;
51
+ }
18
52
  // Configs use the full rule name with the plugin prefix, so remove this
19
53
  const shortName = fullName.slice( pluginName.length + 1 );
20
54
  let options, rawSeverity;