Skip to content

Commit

Permalink
chore: enable type-aware ts linting
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Dec 15, 2022
1 parent 0bc0da4 commit 0bf5ac5
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 37 deletions.
11 changes: 10 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ module.exports = {
overrides: [
{
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
files: ['*.ts'],
extends: ['plugin:@typescript-eslint/recommended'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
rules: {
'node/no-unsupported-features/es-syntax': [
'error',
{ ignores: ['dynamicImport', 'modules'] },
],

'@typescript-eslint/require-array-sort-compare': 'error',
},
},
],
Expand Down
34 changes: 18 additions & 16 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,21 @@ async function loadConfigFileOptions(): Promise<GenerateOptions> {
);
}

const config = explorerResults.config;
const config = explorerResults.config; // eslint-disable-line @typescript-eslint/no-unsafe-assignment -- Rules are disabled because we haven't applied the GenerateOptions type until after we finish validating/normalizing.

// Additional validation that couldn't be handled by ajv.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (config.postprocess && typeof config.postprocess !== 'function') {
throw new Error('postprocess must be a function');
}

// Perform any normalization.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (typeof config.pathRuleList === 'string') {
config.pathRuleList = [config.pathRuleList];
config.pathRuleList = [config.pathRuleList]; // eslint-disable-line @typescript-eslint/no-unsafe-member-access
}

return explorerResults.config;
return explorerResults.config as GenerateOptions;
}
return {};
}
Expand All @@ -164,9 +166,9 @@ export async function run(
)
.option(
'--check [boolean]',
`(optional) Whether to check for and fail if there is a diff. No output will be written. Typically used during CI. (default: ${
`(optional) Whether to check for and fail if there is a diff. No output will be written. Typically used during CI. (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.CHECK]
})`,
)})`,
parseBoolean
)
.option(
Expand All @@ -183,16 +185,16 @@ export async function run(
)
.option(
'--ignore-deprecated-rules [boolean]',
`(optional) Whether to ignore deprecated rules from being checked, displayed, or updated. (default: ${
`(optional) Whether to ignore deprecated rules from being checked, displayed, or updated. (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.IGNORE_DEPRECATED_RULES]
})`,
)})`,
parseBoolean
)
.option(
'--init-rule-docs [boolean]',
`(optional) Whether to create rule doc files if they don't yet exist. (default: ${
`(optional) Whether to create rule doc files if they don't yet exist. (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.INIT_RULE_DOCS]
})`,
)})`,
parseBoolean
)
.option(
Expand All @@ -213,9 +215,9 @@ export async function run(
'--rule-doc-notices <notices>',
`(optional) Ordered, comma-separated list of notices to display in rule doc. Non-applicable notices will be hidden. (choices: "${Object.values(
NOTICE_TYPE
).join('", "')}") (default: ${
).join('", "')}") (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.RULE_DOC_NOTICES]
})`,
)})`,
collectCSV,
[]
)
Expand All @@ -233,9 +235,9 @@ export async function run(
)
.option(
'--rule-doc-section-options [boolean]',
`(optional) Whether to require an "Options" or "Config" rule doc section and mention of any named options for rules with options. (default: ${
`(optional) Whether to require an "Options" or "Config" rule doc section and mention of any named options for rules with options. (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.RULE_DOC_SECTION_OPTIONS]
})`,
)})`,
parseBoolean
)
.addOption(
Expand All @@ -250,9 +252,9 @@ export async function run(
'--rule-list-columns <columns>',
`(optional) Ordered, comma-separated list of columns to display in rule list. Empty columns will be hidden. (choices: "${Object.values(
COLUMN_TYPE
).join('", "')})" (default: ${
).join('", "')})" (default: ${String(
OPTION_DEFAULTS[OPTION_TYPE.RULE_LIST_COLUMNS]
})`,
)})`,
collectCSV,
[]
)
Expand All @@ -268,7 +270,7 @@ export async function run(
'--url-rule-doc <url>',
'(optional) Link to documentation for each rule. Useful when it differs from the rule doc path on disk (e.g. custom documentation site in use). Use `{name}` placeholder for the rule name.'
)
.action(async function (path, options: GenerateOptions) {
.action(async function (path: string, options: GenerateOptions) {
// Load config file options and merge with CLI options.
// CLI options take precedence.
// For this to work, we can't have any default values from the CLI options that will override the config file options (except empty arrays, as arrays will be merged).
Expand Down
7 changes: 5 additions & 2 deletions lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,18 @@ export async function generate(path: string, options?: GenerateOptions) {
);
}

for (const pathRuleListItem of Array.isArray(pathRuleList)
// eslint-disable-next-line unicorn/no-instanceof-array -- using Array.isArray() loses type information about the array.
for (const pathRuleListItem of pathRuleList instanceof Array
? pathRuleList
: [pathRuleList]) {
// Find the exact filename.
const pathToFile = getPathWithExactFileNameCasing(
join(path, pathRuleListItem)
);
if (!pathToFile || !existsSync(pathToFile)) {
throw new Error(`Could not find ${pathRuleList} in ESLint plugin.`);
throw new Error(
`Could not find ${String(pathRuleList)} in ESLint plugin.`
);
}

// Update the rules list in this file.
Expand Down
4 changes: 3 additions & 1 deletion lib/option-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export function parseConfigEmojiOptions(

if (!config || !emoji || extra.length > 0) {
throw new Error(
`Invalid configEmoji option: ${configEmojiItem}. Expected format: config,emoji`
`Invalid configEmoji option: ${String(
configEmojiItem
)}. Expected format: config,emoji`
);
}

Expand Down
15 changes: 9 additions & 6 deletions lib/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ function loadPackageJson(path: string): PackageJson {
if (!existsSync(pluginPackageJsonPath)) {
throw new Error('Could not find package.json of ESLint plugin.');
}
const pluginPackageJson: PackageJson = JSON.parse(
const pluginPackageJson = JSON.parse(
readFileSync(join(pluginRoot, 'package.json'), 'utf8')
);
) as PackageJson;

return pluginPackageJson;
}
Expand All @@ -28,7 +28,7 @@ export async function loadPlugin(path: string): Promise<Plugin> {
const pluginRoot = getPluginRoot(path);
try {
// Try require first which should work for CJS plugins.
return require(pluginRoot); // eslint-disable-line import/no-dynamic-require
return require(pluginRoot) as Plugin; // eslint-disable-line import/no-dynamic-require
} catch {
// Otherwise, for ESM plugins, we'll have to try to resolve the exact plugin entry point and import it.
const pluginPackageJson = loadPackageJson(path);
Expand All @@ -47,6 +47,7 @@ export async function loadPlugin(path: string): Promise<Plugin> {
['.', 'node', 'import', 'require', 'default'];
for (const prop of propertiesToCheck) {
// @ts-expect-error -- The union type for the object is causing trouble.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = exports[prop];
if (typeof value === 'string') {
pluginEntryPoint = value;
Expand All @@ -66,7 +67,9 @@ export async function loadPlugin(path: string): Promise<Plugin> {
);
}

const { default: plugin } = await importAbs(pluginEntryPointAbs);
const { default: plugin } = (await importAbs(pluginEntryPointAbs)) as {
default: Plugin;
};
return plugin;
}
}
Expand Down Expand Up @@ -108,9 +111,9 @@ export function getCurrentPackageVersion(): string {
? '../package.json'
: /* istanbul ignore next -- can't test the compiled version in test */
'../../package.json';
const packageJson: PackageJson = JSON.parse(
const packageJson = JSON.parse(
readFileSync(new URL(pathToPackageJson, import.meta.url), 'utf8')
);
) as PackageJson;
if (!packageJson.version) {
throw new Error('Could not find package.json `version`.');
}
Expand Down
7 changes: 5 additions & 2 deletions lib/plugin-config-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ async function resolveConfigExtends(
extendItems: readonly string[] | string
): Promise<Rules> {
const rules: Rules = {};
for (const extend of Array.isArray(extendItems)
// eslint-disable-next-line unicorn/no-instanceof-array -- using Array.isArray() loses type information about the array.
for (const extend of extendItems instanceof Array
? extendItems
: [extendItems]) {
if (
Expand All @@ -47,7 +48,9 @@ async function resolveConfigExtends(
continue;
}

const { default: config } = await importAbs(extend);
const { default: config } = (await importAbs(extend)) as {
default: Config;
};
const extendedRules = await resolveConfigRules(config);
Object.assign(rules, extendedRules);
}
Expand Down
24 changes: 20 additions & 4 deletions lib/rule-doc-notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function severityToTerminology(severity: SEVERITY_TYPE) {
return 'is _disabled_';
/* istanbul ignore next -- this shouldn't happen */
default:
throw new Error(`Unknown severity: ${severity}`);
throw new Error(`Unknown severity: ${String(severity)}`);
}
}

Expand Down Expand Up @@ -187,7 +187,7 @@ const RULE_NOTICES: {
);
return `${EMOJI_DEPRECATED} This rule is deprecated.${
replacedBy && replacedBy.length > 0
? ` It was replaced by ${replacementRuleList}.`
? ` It was replaced by ${String(replacementRuleList)}.`
: ''
}`;
},
Expand Down Expand Up @@ -391,18 +391,32 @@ function makeRuleDocTitle(
/* istanbul ignore next -- this shouldn't happen */
default:
throw new Error(
`Unhandled rule doc title format fallback: ${ruleDocTitleFormatWithFallback}`
`Unhandled rule doc title format fallback: ${String(
ruleDocTitleFormatWithFallback
)}`
);
}
}

switch (ruleDocTitleFormatWithFallback) {
// Backticks (code-style) only used around rule name to differentiate it when the rule description is also present.
case 'desc':
/* istanbul ignore next -- this shouldn't happen */
if (!descriptionFormatted) {
throw new Error('Attempting to display description when none exists');
}
return `# ${descriptionFormatted}`;
case 'desc-parens-name':
/* istanbul ignore next -- this shouldn't happen */
if (!descriptionFormatted) {
throw new Error('Attempting to display description when none exists');
}
return `# ${descriptionFormatted} (\`${name}\`)`;
case 'desc-parens-prefix-name':
/* istanbul ignore next -- this shouldn't happen */
if (!descriptionFormatted) {
throw new Error('Attempting to display description when none exists');
}
return `# ${descriptionFormatted} (\`${pluginPrefix}/${name}\`)`;
case 'name':
return `# ${name}`;
Expand All @@ -411,7 +425,9 @@ function makeRuleDocTitle(
/* istanbul ignore next -- this shouldn't happen */
default:
throw new Error(
`Unhandled rule doc title format: ${ruleDocTitleFormatWithFallback}`
`Unhandled rule doc title format: ${String(
ruleDocTitleFormatWithFallback
)}`
);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/rule-list-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ export function getColumns(
),
// Show type column only if we found at least one rule with a standard type.
[COLUMN_TYPE.TYPE]: ruleDetails.some(
(ruleDetail) =>
ruleDetail.type && RULE_TYPES.includes(ruleDetail.type as any) // eslint-disable-line @typescript-eslint/no-explicit-any
(ruleDetail) => ruleDetail.type && RULE_TYPES.includes(ruleDetail.type)
),
};

Expand Down
4 changes: 2 additions & 2 deletions lib/rule-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function getPropertyFromRule(
}

const rule = plugin.rules[ruleName];
return getProperty(rule, property) as any; // eslint-disable-line @typescript-eslint/no-explicit-any -- This could be any type, not just undefined (https://github.com/sindresorhus/dot-prop/issues/95).
return getProperty(rule, property) as unknown;
}

function getConfigurationColumnValueForRule(
Expand Down Expand Up @@ -307,7 +307,7 @@ function generateRulesListMarkdownWithRuleListSplit(

parts.push(
`${'#'.repeat(headerLevel)} ${
isBooleanableTrue(value) ? ruleListSplitTitle : value
isBooleanableTrue(value) ? ruleListSplitTitle : value // eslint-disable-line @typescript-eslint/restrict-template-expressions -- TODO: better handling to ensure value is a string.
}`,
generateRulesListMarkdown(
columns,
Expand Down
3 changes: 2 additions & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RuleDocTitleFormat } from './rule-doc-title-format.js';
import type { TSESLint, JSONSchema } from '@typescript-eslint/utils';
import type { RULE_TYPE } from './rule-type.js';

// Standard ESLint types.

Expand Down Expand Up @@ -43,7 +44,7 @@ export interface RuleDetails {
requiresTypeChecking: boolean;
deprecated: boolean;
schema: JSONSchema.JSONSchema4;
type?: string; // Rule might not have a type.
type?: `${RULE_TYPE}`; // Rule might not have a type.
}

/**
Expand Down

0 comments on commit 0bf5ac5

Please sign in to comment.