Skip to content

Commit 0d95150

Browse files
authored
refactor: Start to use ResolvedGenerateOptions (#824)
* more-path-refactoring * refactor: Consolidate options and context The `ConfigFormat` enum is no longer needed, and its functionality has been integrated into the `ResolvedGenerateOptions`. The `Context` interface has been updated to include `ResolvedGenerateOptions`, and the `getContext` function now accepts these options. This change simplifies option handling and ensures that all necessary configuration is available within the context object. (This commit message was automatically generated by an LLM.) * fix: Improve comment for unresolved options and remove redundant getContext calls The comment in `lib/options.ts` explaining the `@ts-expect-error` was improved for clarity. Several redundant `await getContext(cwd, options)` calls were removed from the `test/lib/markdown-test.ts` file, as the context is now obtained once before the tests. This simplifies the test setup and improves performance. (This commit message was automatically generated by an LLM.) * refactor: configEmoji so that it is clearer what is going on * feat(tests): add option parsers tests Adds tests for the `parseConfigEmojiOptions`, `parseRuleListColumnsOption`, and `parseRuleDocNoticesOption` functions. These tests cover the default behavior and handling of undefined inputs for each parser. (This commit message was automatically generated by an LLM.) * refactor: ignoreConfig into context
1 parent 752b4be commit 0d95150

17 files changed

+253
-180
lines changed

eslint.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import tseslint from 'typescript-eslint';
1111
export default tseslint.config(
1212
// Configs:
1313
js.configs.recommended,
14+
// @ts-expect-error This config is not properly typed. In the future, we might want to use
15+
// `eslint-plugin-import-x` instead of `eslint-pluginimport`, as it is better maintained.
1416
eslintPluginImport.flatConfigs.typescript,
1517
eslintPluginJest.configs['flat/recommended'],
1618
eslintPluginN.configs['flat/recommended'],

lib/config-format.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Context } from './context.js';
2+
13
export const CONFIG_FORMATS = [
24
'name',
35
'plugin-colon-prefix-name',
@@ -7,10 +9,13 @@ export const CONFIG_FORMATS = [
79
export type ConfigFormat = (typeof CONFIG_FORMATS)[number];
810

911
export function configNameToDisplay(
12+
context: Context,
1013
configName: string,
11-
configFormat: ConfigFormat,
1214
pluginPrefix: string,
1315
) {
16+
const { options } = context;
17+
const { configFormat } = options;
18+
1419
switch (configFormat) {
1520
case 'name': {
1621
return configName;

lib/config-list.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
} from './comment-markers.js';
55
import { markdownTable } from 'markdown-table';
66
import type { ConfigsToRules, ConfigEmojis, Plugin, Config } from './types.js';
7-
import { ConfigFormat, configNameToDisplay } from './config-format.js';
7+
import { configNameToDisplay } from './config-format.js';
88
import { sanitizeMarkdownTable } from './string.js';
99
import { Context } from './context.js';
1010

@@ -35,9 +35,10 @@ function generateConfigListMarkdown(
3535
configsToRules: ConfigsToRules,
3636
pluginPrefix: string,
3737
configEmojis: ConfigEmojis,
38-
configFormat: ConfigFormat,
39-
ignoreConfig: readonly string[],
4038
): string {
39+
const { options } = context;
40+
const { ignoreConfig } = options;
41+
4142
/* istanbul ignore next -- configs are sure to exist at this point */
4243
const configs = Object.values(plugin.configs || {});
4344
const hasDescription = configs.some((config) => configToDescription(config));
@@ -57,7 +58,7 @@ function generateConfigListMarkdown(
5758
const description = config ? configToDescription(config) : undefined;
5859
return [
5960
configEmojis.find((obj) => obj.config === configName)?.emoji || '',
60-
`\`${configNameToDisplay(configName, configFormat, pluginPrefix)}\``,
61+
`\`${configNameToDisplay(context, configName, pluginPrefix)}\``,
6162
hasDescription ? description || '' : undefined,
6263
].filter((col) => col !== undefined);
6364
}),
@@ -76,10 +77,9 @@ export function updateConfigsList(
7677
configsToRules: ConfigsToRules,
7778
pluginPrefix: string,
7879
configEmojis: ConfigEmojis,
79-
configFormat: ConfigFormat,
80-
ignoreConfig: readonly string[],
8180
): string {
82-
const { endOfLine } = context;
81+
const { endOfLine, options } = context;
82+
const { ignoreConfig } = options;
8383

8484
const listStartIndex = markdown.indexOf(BEGIN_CONFIG_LIST_MARKER);
8585
let listEndIndex = markdown.indexOf(END_CONFIG_LIST_MARKER);
@@ -111,8 +111,6 @@ export function updateConfigsList(
111111
configsToRules,
112112
pluginPrefix,
113113
configEmojis,
114-
configFormat,
115-
ignoreConfig,
116114
);
117115

118116
return `${preList}${BEGIN_CONFIG_LIST_MARKER}${endOfLine}${endOfLine}${list}${endOfLine}${endOfLine}${END_CONFIG_LIST_MARKER}${postList}`;

lib/context.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getEndOfLine } from './eol.js';
2+
import { ResolvedGenerateOptions } from './types.js';
23

34
/**
45
* Context about the current invocation of the program, like what end-of-line
@@ -7,11 +8,16 @@ import { getEndOfLine } from './eol.js';
78
export interface Context {
89
endOfLine: string;
910
path: string;
11+
options: ResolvedGenerateOptions;
1012
}
1113

12-
export async function getContext(path: string): Promise<Context> {
14+
export async function getContext(
15+
path: string,
16+
options: ResolvedGenerateOptions,
17+
): Promise<Context> {
1318
return {
1419
endOfLine: await getEndOfLine(),
1520
path,
21+
options,
1622
};
1723
}

lib/eol.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ export async function getEndOfLine(): Promise<'\n' | '\r\n'> {
1212
async function getEndOfLineFromEditorConfig(): Promise<
1313
'\n' | '\r\n' | undefined
1414
> {
15-
// The passed `markdown.md` argument is used as an example of a Markdown file
16-
// in the plugin root folder in order to check for any specific Markdown
17-
// configurations.
15+
// The passed `markdown.md` argument is used as an example of a Markdown file in the plugin root
16+
// folder in order to check for any specific Markdown configurations.
1817
const editorConfigProps = await editorconfig.parse('markdown.md');
1918

2019
if (editorConfigProps.end_of_line === 'lf') {
@@ -39,9 +38,8 @@ async function getEndOfLineFromPrettierConfig(): Promise<
3938
return undefined;
4039
}
4140

42-
// The passed `markdown.md` argument is used as an example of a Markdown file
43-
// in the plugin root folder in order to check for any specific Markdown
44-
// configurations.
41+
// The passed `markdown.md` argument is used as an example of a Markdown file in the plugin root
42+
// folder in order to check for any specific Markdown configurations.
4543
const prettierOptions = await prettier.resolveConfig('markdown.md');
4644

4745
if (prettierOptions === null) {

lib/generator.ts

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
expectSectionHeaderOrFail,
2828
} from './markdown.js';
2929
import { resolveConfigsToRules } from './plugin-config-resolution.js';
30-
import { OPTION_DEFAULTS } from './options.js';
30+
import { getResolvedOptions, OPTION_DEFAULTS } from './options.js';
3131
import { diff } from 'jest-diff';
3232
import type { GenerateOptions } from './types.js';
3333
import { OPTION_TYPE, RuleModule } from './types.js';
@@ -64,7 +64,10 @@ function stringOrArrayToArrayWithFallback(
6464

6565
// eslint-disable-next-line complexity
6666
export async function generate(path: string, options?: GenerateOptions) {
67-
const context = await getContext(path);
67+
const resolvedOptions = getResolvedOptions(options);
68+
const { check, configEmoji } = resolvedOptions;
69+
70+
const context = await getContext(path, resolvedOptions);
6871
const { endOfLine } = context;
6972

7073
const plugin = await loadPlugin(path);
@@ -78,14 +81,6 @@ export async function generate(path: string, options?: GenerateOptions) {
7881
}
7982

8083
// Options. Add default values as needed.
81-
const check = options?.check ?? OPTION_DEFAULTS[OPTION_TYPE.CHECK];
82-
const configEmojis = parseConfigEmojiOptions(plugin, options?.configEmoji);
83-
const configFormat =
84-
options?.configFormat ?? OPTION_DEFAULTS[OPTION_TYPE.CONFIG_FORMAT];
85-
const ignoreConfig = stringOrArrayWithFallback(
86-
options?.ignoreConfig,
87-
OPTION_DEFAULTS[OPTION_TYPE.IGNORE_CONFIG],
88-
);
8984
const ignoreDeprecatedRules =
9085
options?.ignoreDeprecatedRules ??
9186
OPTION_DEFAULTS[OPTION_TYPE.IGNORE_DEPRECATED_RULES];
@@ -127,7 +122,10 @@ export async function generate(path: string, options?: GenerateOptions) {
127122
const urlRuleDoc =
128123
options?.urlRuleDoc ?? OPTION_DEFAULTS[OPTION_TYPE.URL_RULE_DOC];
129124

130-
// Gather normalized list of rules.
125+
// Create some new data structures based on the options.
126+
const configEmojis = parseConfigEmojiOptions(plugin, configEmoji);
127+
128+
// Gather the normalized list of rules.
131129
const ruleNamesAndRules = Object.entries(plugin.rules)
132130
.map(([name, ruleModule]) => {
133131
// Convert deprecated function-style rules to object-style rules so that we don't have to handle function-style rules everywhere throughout the codebase.
@@ -205,8 +203,6 @@ export async function generate(path: string, options?: GenerateOptions) {
205203
pluginPrefix,
206204
pathRuleDoc,
207205
configEmojis,
208-
configFormat,
209-
ignoreConfig,
210206
ruleDocNotices,
211207
ruleDocTitleFormat,
212208
urlConfigs,
@@ -307,32 +303,29 @@ export async function generate(path: string, options?: GenerateOptions) {
307303

308304
// Update the rules list in this file.
309305
const fileContents = await readFile(pathToFile, 'utf8');
306+
const rulesList = updateRulesList(
307+
context,
308+
ruleNamesAndRules,
309+
fileContents,
310+
plugin,
311+
configsToRules,
312+
pluginPrefix,
313+
pathRuleDoc,
314+
pathToFile,
315+
configEmojis,
316+
ruleListColumns,
317+
ruleListSplit,
318+
urlConfigs,
319+
urlRuleDoc,
320+
);
310321
const fileContentsNew = await postprocess(
311322
updateConfigsList(
312323
context,
313-
updateRulesList(
314-
context,
315-
ruleNamesAndRules,
316-
fileContents,
317-
plugin,
318-
configsToRules,
319-
pluginPrefix,
320-
pathRuleDoc,
321-
pathToFile,
322-
configEmojis,
323-
configFormat,
324-
ignoreConfig,
325-
ruleListColumns,
326-
ruleListSplit,
327-
urlConfigs,
328-
urlRuleDoc,
329-
),
324+
rulesList,
330325
plugin,
331326
configsToRules,
332327
pluginPrefix,
333328
configEmojis,
334-
configFormat,
335-
ignoreConfig,
336329
),
337330
resolve(pathToFile),
338331
);

lib/option-parsers.ts

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,62 @@ import {
88
EMOJI_CONFIG_ERROR,
99
RESERVED_EMOJIS,
1010
} from './emojis.js';
11-
import type { Plugin, ConfigEmojis } from './types.js';
11+
import type { Plugin, ConfigEmojis, GenerateOptions } from './types.js';
1212

1313
/**
1414
* Parse the options, check for errors, and set defaults.
1515
*/
1616
export function parseConfigEmojiOptions(
1717
plugin: Plugin,
18-
configEmoji?: readonly (
19-
| [configName: string, emoji: string]
20-
| [configName: string]
21-
)[],
18+
configEmoji: GenerateOptions['configEmoji'],
2219
): ConfigEmojis {
2320
const configsSeen = new Set<string>();
2421
const configsWithDefaultEmojiRemoved: string[] = [];
2522
const configEmojis =
26-
configEmoji?.flatMap((configEmojiItem) => {
27-
const [config, emoji, ...extra] = configEmojiItem as
28-
| typeof configEmojiItem
29-
| [configName: string, emoji: string, extra: string[]];
30-
31-
// Check for duplicate configs.
32-
if (configsSeen.has(config)) {
33-
throw new Error(
34-
`Duplicate config name in configEmoji options: ${config}`,
35-
);
36-
} else {
37-
configsSeen.add(config);
38-
}
39-
40-
if (config && !emoji && Object.keys(EMOJI_CONFIGS).includes(config)) {
41-
// User wants to remove the default emoji for this config.
42-
configsWithDefaultEmojiRemoved.push(config);
43-
return [];
44-
}
45-
46-
if (!config || !emoji || extra.length > 0) {
47-
throw new Error(
48-
`Invalid configEmoji option: ${String(
49-
configEmojiItem,
50-
)}. Expected format: config,emoji`,
51-
);
52-
}
53-
54-
if (plugin.configs?.[config] === undefined) {
55-
throw new Error(
56-
`Invalid configEmoji option: ${config} config not found.`,
57-
);
58-
}
59-
60-
if (RESERVED_EMOJIS.includes(emoji)) {
61-
throw new Error(`Cannot specify reserved emoji ${EMOJI_CONFIG_ERROR}.`);
62-
}
63-
64-
return [{ config, emoji }];
65-
}) || [];
23+
configEmoji === undefined
24+
? []
25+
: configEmoji.flatMap((configEmojiItem) => {
26+
const [config, emoji, ...extra] = configEmojiItem as
27+
| typeof configEmojiItem
28+
| [configName: string, emoji: string, extra: string[]];
29+
30+
// Check for duplicate configs.
31+
if (configsSeen.has(config)) {
32+
throw new Error(
33+
`Duplicate config name in configEmoji options: ${config}`,
34+
);
35+
} else {
36+
configsSeen.add(config);
37+
}
38+
39+
if (config && !emoji && Object.keys(EMOJI_CONFIGS).includes(config)) {
40+
// User wants to remove the default emoji for this config.
41+
configsWithDefaultEmojiRemoved.push(config);
42+
return [];
43+
}
44+
45+
if (!config || !emoji || extra.length > 0) {
46+
throw new Error(
47+
`Invalid configEmoji option: ${String(
48+
configEmojiItem,
49+
)}. Expected format: config,emoji`,
50+
);
51+
}
52+
53+
if (plugin.configs?.[config] === undefined) {
54+
throw new Error(
55+
`Invalid configEmoji option: ${config} config not found.`,
56+
);
57+
}
58+
59+
if (RESERVED_EMOJIS.includes(emoji)) {
60+
throw new Error(
61+
`Cannot specify reserved emoji ${EMOJI_CONFIG_ERROR}.`,
62+
);
63+
}
64+
65+
return [{ config, emoji }];
66+
});
6667

6768
// Add default emojis for the common configs for which the user hasn't already specified an emoji.
6869
for (const [config, emoji] of Object.entries(EMOJI_CONFIGS)) {

lib/options.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { join } from 'node:path';
22
import { ConfigFormat } from './config-format.js';
33
import { RuleDocTitleFormat } from './rule-doc-title-format.js';
4-
import { COLUMN_TYPE, NOTICE_TYPE, OPTION_TYPE } from './types.js';
4+
import {
5+
COLUMN_TYPE,
6+
GenerateOptions,
7+
NOTICE_TYPE,
8+
OPTION_TYPE,
9+
ResolvedGenerateOptions,
10+
} from './types.js';
511

612
export const COLUMN_TYPE_DEFAULT_PRESENCE_AND_ORDERING: {
713
[key in COLUMN_TYPE]: boolean;
@@ -71,3 +77,35 @@ export const OPTION_DEFAULTS = {
7177
[OPTION_TYPE.URL_CONFIGS]: undefined,
7278
[OPTION_TYPE.URL_RULE_DOC]: undefined,
7379
} satisfies Record<OPTION_TYPE, unknown>; // Satisfies is used to ensure all options are included, but without losing type information.
80+
81+
/** Combines provided options with default options. */
82+
export function getResolvedOptions(
83+
options: GenerateOptions = {},
84+
): ResolvedGenerateOptions {
85+
const check = options.check ?? OPTION_DEFAULTS[OPTION_TYPE.CHECK];
86+
const configEmoji =
87+
options.configEmoji ?? OPTION_DEFAULTS[OPTION_TYPE.CONFIG_EMOJI];
88+
const configFormat =
89+
options.configFormat ?? OPTION_DEFAULTS[OPTION_TYPE.CONFIG_FORMAT];
90+
const ignoreConfig = stringOrArrayWithFallback(
91+
options?.ignoreConfig,
92+
OPTION_DEFAULTS[OPTION_TYPE.IGNORE_CONFIG],
93+
);
94+
95+
// @ts-expect-error This will be filled in later with all the remaining options. This being
96+
// unfinished will not affect anything at runtime, because the options that are not yet present
97+
// here are still being calculated in the old way.
98+
return {
99+
check,
100+
configEmoji,
101+
configFormat,
102+
ignoreConfig,
103+
};
104+
}
105+
106+
function stringOrArrayWithFallback<T extends string | readonly string[]>(
107+
stringOrArray: undefined | T,
108+
fallback: T,
109+
): T {
110+
return stringOrArray && stringOrArray.length > 0 ? stringOrArray : fallback;
111+
}

0 commit comments

Comments
 (0)