Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TypeScript readonly modifier for external/array types #303

Merged
merged 1 commit into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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