Skip to content

Commit 1a4cc7a

Browse files
committed
Fix remaining lint problems
1 parent b3f14c5 commit 1a4cc7a

File tree

12 files changed

+389
-274
lines changed

12 files changed

+389
-274
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"scripts": {
1919
"prepare": "husky install",
2020
"test": "vitest run",
21+
"lint": "eslint .",
2122
"docs": "node scripts/docs.js",
2223
"release": "release-it"
2324
},

scripts/docs.js

Lines changed: 106 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { ESLint } from 'eslint';
44
import fs from 'node:fs/promises';
5-
import path, { dirname } from 'node:path';
5+
import path from 'node:path';
66
import { fileURLToPath } from 'node:url';
77
import { TEST_FILE_PATTERNS } from '../src/lib/patterns.js';
88
import {
@@ -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));
2323
const readmePath = path.join(currentDir, '..', 'README.md');
2424
const 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
*/
4545
function 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
*/
5858
async 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(/eslint-plugin-?/, '').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(/eslint-plugin-?/, '').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
*/
100105
async 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
*/
144149
async 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

scripts/helpers/configs.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @ts-check
2+
/* eslint-disable sonarjs/no-nested-template-literals */
23

34
import { md } from 'build-md';
45
import { getEnabledRuleIds } from './rules.js';
@@ -23,7 +24,7 @@ const configDescriptions = {
2324
// @ts-expect-error keys won't be any string
2425
export const configNames = Object.keys(configDescriptions);
2526

26-
/** @type {Record<keyof typeof configDescriptions, import('./types').Icon>} */
27+
/** @type {Record<keyof typeof configDescriptions, import('./types.js').Icon>} */
2728
const configIcons = {
2829
javascript: 'material/javascript',
2930
typescript: 'material/typescript',
@@ -57,7 +58,7 @@ const configExtraPatterns = {
5758
storybook: '.storybook/main.ts',
5859
};
5960

60-
/** @type {(keyof typeof configDescriptions)[]} */
61+
/** @type {Set<(keyof typeof configDescriptions)>} */
6162
const testConfigs = new Set([
6263
'jest',
6364
'vitest',
@@ -66,6 +67,8 @@ const testConfigs = new Set([
6667
'react-testing-library',
6768
]);
6869

70+
const eslintConfig = 'eslint.config.js';
71+
6972
const tsConfigDocsReference = md`Refer to ${md.link('./typescript.md#🏗️-setup', "step 3 in TypeScript config's setup docs")} for how to set up tsconfig properly.`;
7073

7174
/** @type {Partial<Record<keyof typeof configDescriptions, import('build-md').FormattedText>>} */
@@ -99,7 +102,7 @@ export default tseslint.config(
99102
md`Similarly, you may need to ${md.link('https://www.npmjs.com/package/eslint-plugin-import#typescript', md`configure a tsconfig file for ${md.code('eslint-plugin-import')} rules`)} (e.g. if using path aliases in ${md.code('.ts')} files):${md.list(
100103
[
101104
md`Install additional import resolver:${md.codeBlock('sh', 'npm i -D eslint-import-resolver-typescript')}`,
102-
md`Example ${md.code('eslint.config.js')} for Nx monorepo:${md.codeBlock(
105+
md`Example ${md.code(eslintConfig)} for Nx monorepo:${md.codeBlock(
103106
'js',
104107
`export default tseslint.config(
105108
// ...
@@ -133,7 +136,7 @@ export default tseslint.config(
133136
}
134137
`,
135138
)}`,
136-
md`${md.code('settings.node.version')} in ${md.code('eslint.config.js')}: ${md.codeBlock(
139+
md`${md.code('settings.node.version')} in ${md.code(eslintConfig)}: ${md.codeBlock(
137140
'js',
138141
`export default tseslint.config({
139142
// ...
@@ -245,7 +248,7 @@ export function configDescription(name) {
245248
/**
246249
* Get icon name for given config.
247250
* @param {string} name Config file name without extension
248-
* @returns {import('./types').Icon}
251+
* @returns {import('./types.js').Icon}
249252
*/
250253
export function configIcon(name) {
251254
if (!(name in configIcons)) {

0 commit comments

Comments
 (0)