22
33import { ESLint } from 'eslint' ;
44import fs from 'node:fs/promises' ;
5- import path , { dirname } from 'node:path' ;
5+ import path from 'node:path' ;
66import { fileURLToPath } from 'node:url' ;
77import { TEST_FILE_PATTERNS } from '../src/lib/patterns.js' ;
88import {
@@ -19,7 +19,7 @@ import {
1919 ruleLevelFromEntry ,
2020} from './helpers/rules.js' ;
2121
22- const currentDir = fileURLToPath ( dirname ( import . meta. url ) ) ;
22+ const currentDir = fileURLToPath ( path . dirname ( import . meta. url ) ) ;
2323const readmePath = path . join ( currentDir , '..' , 'README.md' ) ;
2424const docsDir = path . join ( currentDir , '..' , 'docs' ) ;
2525
@@ -31,16 +31,16 @@ async function generateDocs() {
3131
3232 await fs . mkdir ( docsDir , { recursive : true } ) ;
3333
34- for ( const config of configs ) {
35- await generateConfigDocs ( config , configs , peerDeps ) ;
36- }
34+ await Promise . all (
35+ configs . map ( config => generateConfigDocs ( config , configs , peerDeps ) ) ,
36+ ) ;
3737
3838 await generateReadmeDocs ( configs , peerDeps ) ;
3939}
4040
4141/**
4242 * @param {string[] } names
43- * @returns {Promise<import('./helpers/types').ExportedConfig[]> }
43+ * @returns {Promise<import('./helpers/types.js ').ExportedConfig[]> }
4444 */
4545function loadConfigs ( names ) {
4646 return Promise . all (
@@ -52,37 +52,42 @@ function loadConfigs(names) {
5252}
5353
5454/**
55- * @param {import('./helpers/types').ExportedConfig[] } configs
56- * @returns {Promise<import('./helpers/types').PeerDep[]> }
55+ * @param {import('./helpers/types.js ').ExportedConfig[] } configs
56+ * @returns {Promise<import('./helpers/types.js ').PeerDep[]> }
5757 */
5858async function loadPeerDependencies ( configs ) {
5959 const packageJson = await import ( '../package.json' , {
6060 with : { type : 'json' } ,
6161 } ) . then ( m => m . default ) ;
6262
63- /** @type {Record<string, string[]> } */
64- const pkgConfigs = { } ;
65-
66- for ( const config of configs ) {
67- const plugins = config . flatConfig . flatMap ( ( { plugins = { } } ) =>
68- Object . keys ( plugins ) ,
69- ) ;
70-
71- for ( const pkg in packageJson . peerDependencies ) {
72- if ( pkg === 'eslint' ) {
73- continue ;
74- }
75- const alias = pkg . replace ( / e s l i n t - p l u g i n - ? / , '' ) . replace ( / \/ $ / , '' ) ;
76- if (
77- plugins . includes ( pkg ) ||
78- plugins . includes ( alias ) ||
79- plugins . map ( plugin => plugin . replace ( / ^ @ / , '' ) ) . includes ( alias )
80- ) {
81- pkgConfigs [ pkg ] ??= [ ] ;
82- pkgConfigs [ pkg ] . push ( config . name ) ;
83- }
84- }
85- }
63+ const pkgConfigs = configs . reduce (
64+ /** @param {Record<string, string[]> } acc */
65+ ( acc , config ) => {
66+ const plugins = config . flatConfig . flatMap ( cfg =>
67+ Object . keys ( cfg . plugins ?? { } ) ,
68+ ) ;
69+ const usedPackages = Object . keys ( packageJson . peerDependencies ) . filter (
70+ pkg => {
71+ if ( pkg === 'eslint' ) {
72+ return false ;
73+ }
74+ const alias = pkg . replace ( / e s l i n t - p l u g i n - ? / , '' ) . replace ( / \/ $ / , '' ) ;
75+ return (
76+ plugins . includes ( pkg ) ||
77+ plugins . includes ( alias ) ||
78+ plugins . map ( plugin => plugin . replace ( / ^ @ / , '' ) ) . includes ( alias )
79+ ) ;
80+ } ,
81+ ) ;
82+ return {
83+ ...acc ,
84+ ...Object . fromEntries (
85+ usedPackages . map ( pkg => [ pkg , [ ...( acc [ pkg ] ?? [ ] ) , config . name ] ] ) ,
86+ ) ,
87+ } ;
88+ } ,
89+ { } ,
90+ ) ;
8691
8792 return Object . entries ( packageJson . peerDependencies ) . map ( ( [ pkg , version ] ) => ( {
8893 pkg,
@@ -94,8 +99,8 @@ async function loadPeerDependencies(configs) {
9499
95100/**
96101 * Update auto-generated part of README.md
97- * @param {import('./helpers/types').ExportedConfig[] } configs Exported configs
98- * @param {import('./helpers/types').PeerDep[] } peerDeps Peer dependencies
102+ * @param {import('./helpers/types.js ').ExportedConfig[] } configs Exported configs
103+ * @param {import('./helpers/types.js ').PeerDep[] } peerDeps Peer dependencies
99104 */
100105async function generateReadmeDocs ( configs , peerDeps ) {
101106 const extended = Object . fromEntries (
@@ -137,9 +142,9 @@ async function generateReadmeDocs(configs, peerDeps) {
137142
138143/**
139144 * Generate Markdown file for specified ESLint config.
140- * @param {import('./helpers/types').ExportedConfig } config Exported config
141- * @param {import('./helpers/types').ExportedConfig[] } allConfigs All exported configs
142- * @param {import('./helpers/types').PeerDep[] } peerDeps Peer dependencies
145+ * @param {import('./helpers/types.js ').ExportedConfig } config Exported config
146+ * @param {import('./helpers/types.js ').ExportedConfig[] } allConfigs All exported configs
147+ * @param {import('./helpers/types.js ').PeerDep[] } peerDeps Peer dependencies
143148 */
144149async function generateConfigDocs ( config , allConfigs , peerDeps ) {
145150 const extendedConfigs = Object . fromEntries (
@@ -159,59 +164,16 @@ async function generateConfigDocs(config, allConfigs, peerDeps) {
159164 const dummyFile = 'eslint.config.js' ;
160165 await eslint . lintFiles ( dummyFile ) ;
161166
162- const rules = getRulesMetadata ( config . flatConfig , ruleIds , eslint , dummyFile ) ;
167+ const rulesMeta = getRulesMetadata (
168+ config . flatConfig ,
169+ ruleIds ,
170+ eslint ,
171+ dummyFile ,
172+ ) ;
163173
164174 const markdown = configRulesToMarkdown (
165175 config . name ,
166- ruleIds . map ( id => {
167- const entry =
168- findRuleEntry (
169- config . flatConfig . filter (
170- ( { name } ) =>
171- name ?. startsWith ( 'code-pushup/' ) &&
172- ( name . endsWith ( '/customized' ) || name . endsWith ( '/additional' ) ) ,
173- ) ,
174- id ,
175- ) ??
176- findRuleEntry (
177- config . flatConfig . filter ( ( { files } ) => files !== TEST_FILE_PATTERNS ) ,
178- id ,
179- ) ;
180- if ( entry == null ) {
181- throw new Error (
182- `Internal logic error - no entry found for rule ${ id } in ${ config . name } config` ,
183- ) ;
184- }
185- const level = ruleLevelFromEntry ( entry ) ;
186- if ( level === 'off' ) {
187- throw new Error (
188- `Internal logic error - rule ${ id } turned off in ${ config . name } config` ,
189- ) ;
190- }
191-
192- const testEntry = findRuleEntry (
193- config . flatConfig . filter ( ( { files } ) => files === TEST_FILE_PATTERNS ) ,
194- id ,
195- ) ;
196- const testLevel =
197- testEntry == null ? null : ruleLevelFromEntry ( testEntry ) ;
198-
199- return {
200- id,
201- meta : rules [ id ] ,
202- level,
203- ...( Array . isArray ( entry ) &&
204- entry . length > 1 && {
205- options : entry . slice ( 1 ) ,
206- } ) ,
207- ...( testLevel &&
208- testLevel !== level && {
209- testOverride : {
210- level : testLevel ,
211- } ,
212- } ) ,
213- } ;
214- } ) ,
176+ ruleIds . map ( id => findRuleData ( id , config , rulesMeta ) ) ,
215177 Object . entries ( extendedConfigs ) . map ( ( [ alias , rules ] ) => ( {
216178 alias,
217179 rulesCount : rules . length ,
@@ -228,11 +190,67 @@ async function generateConfigDocs(config, allConfigs, peerDeps) {
228190 console . info ( `Generated Markdown docs in ${ filePath } ` ) ;
229191}
230192
193+ /**
194+ * Look up rule's metadata, level, custom options and overrides for given config.
195+ * @param {string } id Rule ID
196+ * @param {import('./helpers/types.js').ExportedConfig } config Configuration
197+ * @param {Record<string, import('eslint').Rule.RuleMetaData> } rules Rules metadata
198+ * @returns {import('./helpers/types.js').RuleData } Rule data
199+ */
200+ function findRuleData ( id , config , rules ) {
201+ const entry =
202+ findRuleEntry (
203+ config . flatConfig . filter (
204+ ( { name } ) =>
205+ name ?. startsWith ( 'code-pushup/' ) &&
206+ ( name . endsWith ( '/customized' ) || name . endsWith ( '/additional' ) ) ,
207+ ) ,
208+ id ,
209+ ) ??
210+ findRuleEntry (
211+ config . flatConfig . filter ( ( { files } ) => files !== TEST_FILE_PATTERNS ) ,
212+ id ,
213+ ) ;
214+ if ( entry == null ) {
215+ throw new Error (
216+ `Internal logic error - no entry found for rule ${ id } in ${ config . name } config` ,
217+ ) ;
218+ }
219+ const level = ruleLevelFromEntry ( entry ) ;
220+ if ( level === 'off' ) {
221+ throw new Error (
222+ `Internal logic error - rule ${ id } turned off in ${ config . name } config` ,
223+ ) ;
224+ }
225+
226+ const testEntry = findRuleEntry (
227+ config . flatConfig . filter ( ( { files } ) => files === TEST_FILE_PATTERNS ) ,
228+ id ,
229+ ) ;
230+ const testLevel = testEntry == null ? null : ruleLevelFromEntry ( testEntry ) ;
231+
232+ return {
233+ id,
234+ meta : rules [ id ] ,
235+ level,
236+ ...( Array . isArray ( entry ) &&
237+ entry . length > 1 && {
238+ options : entry . slice ( 1 ) ,
239+ } ) ,
240+ ...( testLevel &&
241+ testLevel !== level && {
242+ testOverride : {
243+ level : testLevel ,
244+ } ,
245+ } ) ,
246+ } ;
247+ }
248+
231249/**
232250 * Get all extended code-pushup configs from flat config.
233- * @param {import('./helpers/types').ExportedConfig } config Exported config
251+ * @param {import('./helpers/types.js ').ExportedConfig } config Exported config
234252 */
235- export function getExtendedConfigs ( config ) {
253+ function getExtendedConfigs ( config ) {
236254 const allExtended = [
237255 ...new Set (
238256 config . flatConfig
0 commit comments