-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathflat.ts
More file actions
116 lines (105 loc) · 3.37 KB
/
Copy pathflat.ts
File metadata and controls
116 lines (105 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import type { Linter, Rule } from 'eslint';
import { builtinRules } from 'eslint/use-at-your-own-risk';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { exists, findNearestFile, logger, toArray } from '@code-pushup/utils';
import type { ESLintTarget } from '../../config.js';
import { jsonHash } from '../hash.js';
import {
type RuleData,
isRuleOff,
optionsFromRuleEntry,
parseRuleId,
} from '../parse.js';
export async function loadRulesForFlatConfig({
eslintrc,
}: Pick<ESLintTarget, 'eslintrc'>): Promise<RuleData[]> {
const config = eslintrc
? await loadConfigByPath(eslintrc)
: await loadConfigByDefaultLocation();
const configs = toArray(config);
const rules = findEnabledRulesWithOptions(configs);
return rules
.map(rule => {
const meta = findRuleMeta(rule.id, configs);
if (!meta) {
logger.warn(`Cannot find metadata for rule ${rule.id}`);
return null;
}
return { ...rule, meta };
})
.filter(exists);
}
type FlatConfig = Linter.Config | Linter.Config[];
async function loadConfigByDefaultLocation(): Promise<FlatConfig> {
const flatConfigFileNames = [
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.cjs',
];
const configPath = await findNearestFile(flatConfigFileNames);
if (configPath) {
return loadConfigByPath(configPath);
}
throw new Error(
[
`ESLint config file not found - expected ${flatConfigFileNames.join('/')} in ${process.cwd()} or some parent directory`,
'If your ESLint config is in a non-standard location, use the `eslintrc` parameter to specify the path.',
].join('\n'),
);
}
async function loadConfigByPath(configPath: string): Promise<FlatConfig> {
const absolutePath = path.isAbsolute(configPath)
? configPath
: path.join(process.cwd(), configPath);
const url = pathToFileURL(absolutePath).toString();
const mod = (await import(url)) as FlatConfig | { default: FlatConfig };
return 'default' in mod ? mod.default : mod;
}
function findEnabledRulesWithOptions(
configs: Linter.Config[],
): Omit<RuleData, 'meta'>[] {
const enabledRules = configs
.flatMap(({ rules }) => Object.entries(rules ?? {}))
.filter(([, entry]) => entry != null && !isRuleOff(entry))
.map(([id, entry]) => ({
id,
options: entry ? optionsFromRuleEntry(entry) : [],
}));
const uniqueRulesMap = new Map(
enabledRules.map(({ id, options }) => [
`${id}::${jsonHash(options)}`,
{ id, options },
]),
);
return [...uniqueRulesMap.values()];
}
function findRuleMeta(
ruleId: string,
configs: Linter.Config[],
): Rule.RuleMetaData | undefined {
const { plugin, name } = parseRuleId(ruleId);
if (!plugin) {
return findBuiltinRuleMeta(name);
}
return findPluginRuleMeta(plugin, name, configs);
}
function findBuiltinRuleMeta(name: string): Rule.RuleMetaData | undefined {
const rule = builtinRules.get(name);
return rule?.meta;
}
function findPluginRuleMeta(
plugin: string,
name: string,
configs: Linter.Config[],
): Rule.RuleMetaData | undefined {
const config = configs.find(({ plugins = {} }) => plugin in plugins);
const rule = config?.plugins?.[plugin]?.rules?.[name];
if (typeof rule === 'function') {
logger.warn(
`Cannot parse metadata for rule ${plugin}/${name}, plugin registers it as a function`,
);
return undefined;
}
return rule?.meta;
}