Skip to content

Commit

Permalink
Use readonly for external/array types (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Nov 28, 2022
1 parent e625e30 commit 05687c0
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 128 deletions.
35 changes: 22 additions & 13 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,32 @@ import { getCurrentPackageVersion } from './package-json.js';
* Used for collecting repeated CLI options into an array.
* Example: --foo bar --foo baz => ['bar', 'baz']
*/
function collect(value: string, previous: string[]): string[] {
function collect(
value: string,
previous: readonly string[]
): readonly string[] {
return [...previous, value];
}

/**
* Used for collecting CSV CLI options into an array.
* Example: --foo bar,baz,buz => ['bar', 'baz', 'buz']
* */
function collectCSV(value: string, previous: string[]): string[] {
function collectCSV(
value: string,
previous: readonly string[]
): readonly string[] {
return [...previous, ...value.split(',')];
}

/**
* Used for collecting repeated, nested CSV CLI options into an array of arrays.
* Example: --foo baz,bar --foo biz,buz => [['baz', 'bar'], ['biz', 'buz']]
* */
function collectCSVNested(value: string, previous: string[][]): string[][] {
function collectCSVNested(
value: string,
previous: readonly string[][]
): readonly string[][] {
return [...previous, value.split(',')];
}

Expand Down Expand Up @@ -116,13 +125,18 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
);
}

if (
explorerResults.config.postprocess &&
typeof explorerResults.config.postprocess !== 'function'
) {
const config = explorerResults.config;

// Additional validation that couldn't be handled by ajv.
if (config.postprocess && typeof config.postprocess !== 'function') {
throw new Error('postprocess must be a function');
}

// Perform any normalization.
if (typeof config.pathRuleList === 'string') {
config.pathRuleList = [config.pathRuleList];
}

return explorerResults.config;
}
return {};
Expand All @@ -135,7 +149,7 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
* Note: Does not introduce default values. Default values should be handled in the callback function.
*/
export async function run(
argv: string[],
argv: readonly string[],
cb: (path: string, options: GenerateOptions) => Promise<void>
) {
const program = new Command();
Expand Down Expand Up @@ -259,11 +273,6 @@ export async function run(
// Default values should be handled in the callback function.
const configFileOptions = await loadConfigFileOptions();

// Perform any normalization needed ahead of merging.
if (typeof configFileOptions.pathRuleList === 'string') {
configFileOptions.pathRuleList = [configFileOptions.pathRuleList];
}

const generateOptions = merge(configFileOptions, options); // Recursive merge.

// Invoke callback.
Expand Down
9 changes: 6 additions & 3 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function expectContent(
function expectSectionHeader(
ruleName: string,
contents: string,
possibleHeaders: string[],
possibleHeaders: readonly string[],
expected: boolean
) {
const found = possibleHeaders.some((header) =>
Expand All @@ -84,7 +84,7 @@ function expectSectionHeader(
}
}

function stringOrArrayWithFallback<T extends string | string[]>(
function stringOrArrayWithFallback<T extends string | readonly string[]>(
stringOrArray: undefined | T,
fallback: T
): T {
Expand Down Expand Up @@ -145,7 +145,7 @@ export async function generate(path: string, options?: GenerateOptions) {
options?.urlRuleDoc ?? OPTION_DEFAULTS[OPTION_TYPE.URL_RULE_DOC];

// Gather details about rules.
const details: RuleDetails[] = Object.entries(plugin.rules)
const details: readonly RuleDetails[] = Object.entries(plugin.rules)
.map(([name, rule]): RuleDetails => {
return typeof rule === 'object'
? // Object-style rule.
Expand Down Expand Up @@ -177,6 +177,9 @@ export async function generate(path: string, options?: GenerateOptions) {
.filter(
// Filter out deprecated rules from being checked, displayed, or updated if the option is set.
(details) => !ignoreDeprecatedRules || !details.deprecated
)
.sort(({ name: a }, { name: b }) =>
a.toLowerCase().localeCompare(b.toLowerCase())
);

// Update rule doc for each rule.
Expand Down
18 changes: 9 additions & 9 deletions lib/option-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { Plugin, ConfigEmojis } from './types.js';
*/
export function parseConfigEmojiOptions(
plugin: Plugin,
configEmoji?: string[][]
configEmoji?: readonly string[][]
): ConfigEmojis {
const configsSeen = new Set<string>();
const configsWithDefaultEmojiRemoved: string[] = [];
Expand Down Expand Up @@ -75,9 +75,9 @@ export function parseConfigEmojiOptions(
* Parse the option, check for errors, and set defaults.
*/
export function parseRuleListColumnsOption(
ruleListColumns: string[] | undefined
): COLUMN_TYPE[] {
const values = ruleListColumns ?? [];
ruleListColumns: readonly string[] | undefined
): readonly COLUMN_TYPE[] {
const values = [...(ruleListColumns ?? [])];
const VALUES_OF_TYPE = new Set(Object.values(COLUMN_TYPE).map(String));

// Check for invalid.
Expand All @@ -98,16 +98,16 @@ export function parseRuleListColumnsOption(
);
}

return values as COLUMN_TYPE[];
return values as readonly COLUMN_TYPE[];
}

/**
* Parse the option, check for errors, and set defaults.
*/
export function parseRuleDocNoticesOption(
ruleDocNotices: string[] | undefined
): NOTICE_TYPE[] {
const values = ruleDocNotices ?? [];
ruleDocNotices: readonly string[] | undefined
): readonly NOTICE_TYPE[] {
const values = [...(ruleDocNotices ?? [])];
const VALUES_OF_TYPE = new Set(Object.values(NOTICE_TYPE).map(String));

// Check for invalid.
Expand All @@ -128,5 +128,5 @@ export function parseRuleDocNoticesOption(
);
}

return values as NOTICE_TYPE[];
return values as readonly NOTICE_TYPE[];
}
9 changes: 2 additions & 7 deletions lib/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,8 @@ export async function loadPlugin(path: string): Promise<Plugin> {
) {
// Check various properties on the `exports` object.
// https://nodejs.org/api/packages.html#conditional-exports
const propertiesToCheck: (keyof PackageJson.ExportConditions)[] = [
'.',
'node',
'import',
'require',
'default',
];
const propertiesToCheck: readonly (keyof PackageJson.ExportConditions)[] =
['.', 'node', 'import', 'require', 'default'];
for (const prop of propertiesToCheck) {
// @ts-expect-error -- The union type for the object is causing trouble.
const value = exports[prop];
Expand Down
2 changes: 1 addition & 1 deletion lib/plugin-config-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function resolveConfigRules(config: Config): Promise<Rules> {
}

async function resolveConfigExtends(
extendItems: string[] | string
extendItems: readonly string[] | string
): Promise<Rules> {
const rules: Rules = {};
for (const extend of Array.isArray(extendItems)
Expand Down
2 changes: 1 addition & 1 deletion lib/plugin-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function getConfigsThatSetARule(
plugin: Plugin,
configsToRules: ConfigsToRules,
pluginPrefix: string,
ignoreConfig: string[],
ignoreConfig: readonly string[],
severityType?: SEVERITY_TYPE
) {
/* istanbul ignore next -- this shouldn't happen */
Expand Down
24 changes: 12 additions & 12 deletions lib/rule-doc-notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function severityToTerminology(severity: SEVERITY_TYPE) {
}

function configsToNoticeSentence(
configs: string[],
configs: readonly string[],
severity: SEVERITY_TYPE,
configsLinkOrWord: string,
configLinkOrWord: string,
Expand Down Expand Up @@ -75,9 +75,9 @@ const RULE_NOTICES: {
| undefined
| ((data: {
ruleName: string;
configsError: string[];
configsWarn: string[];
configsOff: string[];
configsError: readonly string[];
configsWarn: readonly string[];
configsOff: readonly string[];
configEmojis: ConfigEmojis;
fixable: boolean;
hasSuggestions: boolean;
Expand Down Expand Up @@ -228,10 +228,10 @@ const RULE_NOTICES: {
*/
function getNoticesForRule(
rule: RuleModule,
configsError: string[],
configsWarn: string[],
configsOff: string[],
ruleDocNotices: NOTICE_TYPE[]
configsError: readonly string[],
configsWarn: readonly string[],
configsOff: readonly string[],
ruleDocNotices: readonly NOTICE_TYPE[]
) {
const notices: {
[key in NOTICE_TYPE]: boolean;
Expand Down Expand Up @@ -272,8 +272,8 @@ function getRuleNoticeLines(
pathPlugin: string,
pathRuleDoc: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
ruleDocNotices: NOTICE_TYPE[],
ignoreConfig: readonly string[],
ruleDocNotices: readonly NOTICE_TYPE[],
urlConfigs?: string,
urlRuleDoc?: string
) {
Expand Down Expand Up @@ -429,8 +429,8 @@ export function generateRuleHeaderLines(
pathPlugin: string,
pathRuleDoc: string,
configEmojis: ConfigEmojis,
ignoreConfig: string[],
ruleDocNotices: NOTICE_TYPE[],
ignoreConfig: readonly string[],
ruleDocNotices: readonly NOTICE_TYPE[],
ruleDocTitleFormat: RuleDocTitleFormat,
urlConfigs?: string,
urlRuleDoc?: string
Expand Down
10 changes: 6 additions & 4 deletions lib/rule-list-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import type { RuleDetails, ConfigsToRules, Plugin } from './types.js';
* An object containing the column header for each column (as a string or function to generate the string).
*/
export const COLUMN_HEADER: {
[key in COLUMN_TYPE]: string | ((data: { details: RuleDetails[] }) => string);
[key in COLUMN_TYPE]:
| string
| ((data: { details: readonly RuleDetails[] }) => string);
} = {
[COLUMN_TYPE.NAME]: ({ details }) => {
const ruleNames = details.map((detail) => detail.name);
Expand Down Expand Up @@ -65,11 +67,11 @@ export const COLUMN_HEADER: {
*/
export function getColumns(
plugin: Plugin,
details: RuleDetails[],
details: readonly RuleDetails[],
configsToRules: ConfigsToRules,
ruleListColumns: COLUMN_TYPE[],
ruleListColumns: readonly COLUMN_TYPE[],
pluginPrefix: string,
ignoreConfig: string[]
ignoreConfig: readonly string[]
): Record<COLUMN_TYPE, boolean> {
const columns: {
[key in COLUMN_TYPE]: boolean;
Expand Down
58 changes: 29 additions & 29 deletions lib/rule-list-legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ const LEGEND_HAS_SUGGESTIONS = `${EMOJI_HAS_SUGGESTIONS} Manually fixable by [ed
*/
const LEGENDS: {
[key in COLUMN_TYPE]:
| string[]
| readonly string[]
| undefined // For no legend.
| ((data: {
plugin: Plugin;
configsToRules: ConfigsToRules;
configEmojis: ConfigEmojis;
pluginPrefix: string;
ignoreConfig: string[];
ignoreConfig: readonly string[];
urlConfigs?: string;
}) => string[]);
}) => readonly string[]);
} = {
[COLUMN_TYPE.CONFIGS_ERROR]: ({
plugin,
Expand Down Expand Up @@ -155,7 +155,7 @@ function getLegendForConfigColumnOfSeverity({
configsToRules: ConfigsToRules;
configEmojis: ConfigEmojis;
pluginPrefix: string;
ignoreConfig: string[];
ignoreConfig: readonly string[];
severityType: SEVERITY_TYPE;
urlConfigs?: string;
}): string {
Expand Down Expand Up @@ -186,9 +186,9 @@ function getLegendsForIndividualConfigs({
configsToRules: ConfigsToRules;
configEmojis: ConfigEmojis;
pluginPrefix: string;
ignoreConfig: string[];
ignoreConfig: readonly string[];
urlConfigs?: string;
}): string[] {
}): readonly string[] {
/* istanbul ignore next -- this shouldn't happen */
if (!plugin.configs || !plugin.rules) {
throw new Error(
Expand Down Expand Up @@ -225,32 +225,32 @@ export function generateLegend(
configsToRules: ConfigsToRules,
configEmojis: ConfigEmojis,
pluginPrefix: string,
ignoreConfig: string[],
ignoreConfig: readonly string[],
urlConfigs?: string
) {
const legends = (Object.entries(columns) as [COLUMN_TYPE, boolean][]).flatMap(
([columnType, enabled]) => {
if (!enabled) {
// This column is turned off.
return [];
}
const legendArrayOrFn = LEGENDS[columnType];
if (!legendArrayOrFn) {
// No legend specified for this column.
return [];
}
return typeof legendArrayOrFn === 'function'
? legendArrayOrFn({
plugin,
configsToRules,
configEmojis,
pluginPrefix,
urlConfigs,
ignoreConfig,
})
: legendArrayOrFn;
const legends = (
Object.entries(columns) as readonly [COLUMN_TYPE, boolean][]
).flatMap(([columnType, enabled]) => {
if (!enabled) {
// This column is turned off.
return [];
}
);
const legendArrayOrFn = LEGENDS[columnType];
if (!legendArrayOrFn) {
// No legend specified for this column.
return [];
}
return typeof legendArrayOrFn === 'function'
? legendArrayOrFn({
plugin,
configsToRules,
configEmojis,
pluginPrefix,
urlConfigs,
ignoreConfig,
})
: legendArrayOrFn;
});

if (legends.some((legend) => legend.includes('Configurations'))) {
// Add legends for individual configs after the config column legend(s).
Expand Down

0 comments on commit 05687c0

Please sign in to comment.