From 338f86fb978c5df79e1350f07b639c8d54927b9c Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Wed, 14 May 2025 14:44:43 -0600 Subject: [PATCH] use TLA in ESM scripts --- .../tools/generate-breaking-changes.mts | 118 ++- packages/types/tools/copy-ast-spec.mts | 19 +- packages/website-eslint/build.mts | 13 +- .../website/tools/generate-website-dts.mts | 39 +- tools/scripts/generate-configs.mts | 714 +++++++++--------- tools/scripts/generate-contributors.mts | 61 +- tools/scripts/generate-lib.mts | 322 ++++---- tools/scripts/postinstall.mts | 30 +- 8 files changed, 624 insertions(+), 692 deletions(-) diff --git a/packages/eslint-plugin/tools/generate-breaking-changes.mts b/packages/eslint-plugin/tools/generate-breaking-changes.mts index be38bd7b1022..0ddb3563b976 100644 --- a/packages/eslint-plugin/tools/generate-breaking-changes.mts +++ b/packages/eslint-plugin/tools/generate-breaking-changes.mts @@ -1,57 +1,42 @@ -import type { - ESLintPluginRuleModule, - TypeScriptESLintRules, -} from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules'; +import type { TypeScriptESLintRules } from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules'; import { fetch } from 'cross-fetch'; -// markdown-table is ESM, hence this file needs to be `.mts` import { markdownTable } from 'markdown-table'; -async function main(): Promise { - const rulesImport = await import('../src/rules/index.js'); - /* - weird TS resolution which adds an additional default layer in the type like: - { default: { default: Rules }} - instead of just - { default: Rules } - */ - const rules = rulesImport.default as unknown as Record< - string, - ESLintPluginRuleModule - >; +import rulesImport from '../src/rules/index.js'; - // Annotate which rules are new since the last version - async function getNewRulesAsOfMajorVersion( - oldVersion: string, - ): Promise> { - // 1. Get the current list of rules (already done) - const newRuleNames = Object.keys(rules); +// Annotate which rules are new since the last version +async function getNewRulesAsOfMajorVersion( + oldVersion: string, +): Promise> { + // 1. Get the current list of rules (already done) + const newRuleNames = Object.keys(rulesImport); - // 2. Retrieve the old version of typescript-eslint from unpkg - const oldUrl = `https://unpkg.com/@typescript-eslint/eslint-plugin@${oldVersion}/dist/configs/all.js`; - const oldFileText = await (await fetch(oldUrl)).text(); - const oldObjectText = oldFileText.substring( - oldFileText.indexOf('{'), - oldFileText.lastIndexOf('}') + 1, - ); - // Normally we wouldn't condone using the 'eval' API... - // But this is an internal-only script and it's the easiest way to convert - // the JS raw text into a runtime object. 🤷 - let oldRulesObject!: { rules: TypeScriptESLintRules }; - eval(`oldRulesObject = ${oldObjectText}`); - const oldRuleNames = new Set(Object.keys(oldRulesObject.rules)); + // 2. Retrieve the old version of typescript-eslint from unpkg + const oldUrl = `https://unpkg.com/@typescript-eslint/eslint-plugin@${oldVersion}/dist/configs/all.js`; + const oldFileText = await (await fetch(oldUrl)).text(); + const oldObjectText = oldFileText.substring( + oldFileText.indexOf('{'), + oldFileText.lastIndexOf('}') + 1, + ); + // Normally we wouldn't condone using the 'eval' API... + // But this is an internal-only script and it's the easiest way to convert + // the JS raw text into a runtime object. 🤷 + let oldRulesObject!: { rules: TypeScriptESLintRules }; + eval(`oldRulesObject = ${oldObjectText}`); + const oldRuleNames = new Set(Object.keys(oldRulesObject.rules)); - // 3. Get the keys that exist in (1) (new version) and not (2) (old version) - return new Set( - newRuleNames.filter( - newRuleName => !oldRuleNames.has(`@typescript-eslint/${newRuleName}`), - ), - ); - } + // 3. Get the keys that exist in (1) (new version) and not (2) (old version) + return new Set( + newRuleNames.filter( + newRuleName => !oldRuleNames.has(`@typescript-eslint/${newRuleName}`), + ), + ); +} - const newRuleNames = await getNewRulesAsOfMajorVersion('5.0.0'); +const newRuleNames = await getNewRulesAsOfMajorVersion('5.0.0'); - console.log(`## Table Key +console.log(`## Table Key @@ -132,28 +117,23 @@ async function main(): Promise { > Hint: search for 🆕 to find newly added rules, and ➕ or ➖ to see config changes. `); - console.log( - markdownTable([ - ['Rule', 'Status', 'TC', 'Ext', "Rec'd", 'Strict', 'Style'], - ...Object.entries(rules).map(([ruleName, { meta }]) => { - const { deprecated } = meta; - const { extendsBaseRule, recommended, requiresTypeChecking } = - meta.docs; - - return [ - `[\`${ruleName}\`](https://typescript-eslint.io/rules/${ruleName})`, - newRuleNames.has(ruleName) ? '🆕' : deprecated ? '💀' : '', - requiresTypeChecking ? '💭' : '', - extendsBaseRule ? '🧱' : '', - recommended === 'recommended' ? '🟩' : '', - recommended === 'strict' ? '🔵' : '', - recommended === 'stylistic' ? '🔸' : '', - ]; - }), - ]), - ); -} +console.log( + markdownTable([ + ['Rule', 'Status', 'TC', 'Ext', "Rec'd", 'Strict', 'Style'], + ...Object.entries(rulesImport).map(([ruleName, { meta }]) => { + const { deprecated } = meta; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- all of our rules have meta.docs + const { extendsBaseRule, recommended, requiresTypeChecking } = meta.docs!; -main().catch((error: unknown) => { - console.error(error); -}); + return [ + `[\`${ruleName}\`](https://typescript-eslint.io/rules/${ruleName})`, + newRuleNames.has(ruleName) ? '🆕' : deprecated ? '💀' : '', + requiresTypeChecking ? '💭' : '', + extendsBaseRule ? '🧱' : '', + recommended === 'recommended' ? '🟩' : '', + recommended === 'strict' ? '🔵' : '', + recommended === 'stylistic' ? '🔸' : '', + ]; + }), + ]), +); diff --git a/packages/types/tools/copy-ast-spec.mts b/packages/types/tools/copy-ast-spec.mts index f3ab39fdba7d..110a92854422 100644 --- a/packages/types/tools/copy-ast-spec.mts +++ b/packages/types/tools/copy-ast-spec.mts @@ -72,18 +72,11 @@ async function copyFile( console.log('Copied', fileName); } -async function main(): Promise { - if (process.env.SKIP_AST_SPEC_REBUILD) { - // ensure the package is built - await execAsync('yarn', ['build'], { cwd: AST_SPEC_PATH }); - } - - await copyFile('dist', 'ast-spec.ts', code => - code.replaceAll('export declare enum', 'export enum'), - ); +if (process.env.SKIP_AST_SPEC_REBUILD) { + // ensure the package is built + await execAsync('yarn', ['build'], { cwd: AST_SPEC_PATH }); } -main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); +await copyFile('dist', 'ast-spec.ts', code => + code.replaceAll('export declare enum', 'export enum'), +); diff --git a/packages/website-eslint/build.mts b/packages/website-eslint/build.mts index 4c445e809ff5..6a98bbb0556f 100644 --- a/packages/website-eslint/build.mts +++ b/packages/website-eslint/build.mts @@ -1,4 +1,4 @@ -/* eslint-disable no-process-exit, no-console */ +/* eslint-disable no-console */ import * as esbuild from 'esbuild'; import * as fs from 'node:fs/promises'; @@ -170,12 +170,5 @@ async function buildPackage(name: string, file: string): Promise { } console.time('building eslint for web'); - -buildPackage('index', './src/index.js') - .then(() => { - console.timeEnd('building eslint for web'); - }) - .catch((e: unknown) => { - console.error(String(e)); - process.exit(1); - }); +await buildPackage('index', './src/index.js'); +console.timeEnd('building eslint for web'); diff --git a/packages/website/tools/generate-website-dts.mts b/packages/website/tools/generate-website-dts.mts index 9a6071ac4e7e..92e3e40bb534 100644 --- a/packages/website/tools/generate-website-dts.mts +++ b/packages/website/tools/generate-website-dts.mts @@ -79,29 +79,22 @@ function processFiles(text: string): string { return result; } -async function main(): Promise { - const vendor = path.join(__dirname, '..', 'src', 'vendor'); +const vendor = path.join(__dirname, '..', 'src', 'vendor'); - console.log('Cleaning...'); - await rimraf(vendor); - await makeDirectory(vendor); +console.log('Cleaning...'); +await rimraf(vendor); +await makeDirectory(vendor); - // TS-VFS - await getFileAndStoreLocally( - '/js/sandbox/vendor/typescript-vfs.d.ts', - path.join(vendor, 'typescript-vfs.d.ts'), - processFiles, - ); - - // Sandbox - await getFileAndStoreLocally( - '/js/sandbox/index.d.ts', - path.join(vendor, 'sandbox.d.ts'), - processFiles, - ); -} +// TS-VFS +await getFileAndStoreLocally( + '/js/sandbox/vendor/typescript-vfs.d.ts', + path.join(vendor, 'typescript-vfs.d.ts'), + processFiles, +); -main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); +// Sandbox +await getFileAndStoreLocally( + '/js/sandbox/index.d.ts', + path.join(vendor, 'sandbox.d.ts'), + processFiles, +); diff --git a/tools/scripts/generate-configs.mts b/tools/scripts/generate-configs.mts index 6b0971a7104d..99a428fdea85 100644 --- a/tools/scripts/generate-configs.mts +++ b/tools/scripts/generate-configs.mts @@ -48,413 +48,407 @@ const CLASSIC_EXTENDS: readonly string[] = EXTENDS_MODULES.map( mod => mod.packageRelativePath, ); -async function main(): Promise { - function addAutoGeneratedComment(code?: string): string { - return [...AUTO_GENERATED_COMMENT_LINES, code].join('\n'); - } +function addAutoGeneratedComment(code?: string): string { + return [...AUTO_GENERATED_COMMENT_LINES, code].join('\n'); +} - const prettierConfig = await prettier.resolveConfig('file.ts', { - config: PRETTIER_CONFIG_PATH, - }); +const prettierConfig = await prettier.resolveConfig('file.ts', { + config: PRETTIER_CONFIG_PATH, +}); - type LinterConfigRules = Record< - string, - [ClassicConfig.RuleLevel, ...unknown[]] | ClassicConfig.RuleLevel - >; +type LinterConfigRules = Record< + string, + [ClassicConfig.RuleLevel, ...unknown[]] | ClassicConfig.RuleLevel +>; - interface LinterConfig extends ClassicConfig.Config { - extends?: string | string[]; - plugins?: string[]; - } +interface LinterConfig extends ClassicConfig.Config { + extends?: string | string[]; + plugins?: string[]; +} - const RULE_NAME_PREFIX = '@typescript-eslint/'; - const MAX_RULE_NAME_LENGTH = Math.max( - ...Object.keys(eslintPlugin.rules).map(name => name.length), - ); - const BASE_RULES_TO_BE_OVERRIDDEN = new Map( - Object.entries(eslintPlugin.rules) - .filter(([, rule]) => rule.meta.docs.extendsBaseRule) - .map( - ([ruleName, rule]) => - [ - ruleName, - typeof rule.meta.docs.extendsBaseRule === 'string' - ? rule.meta.docs.extendsBaseRule - : ruleName, - ] as const, - ), - ); +const RULE_NAME_PREFIX = '@typescript-eslint/'; +const MAX_RULE_NAME_LENGTH = Math.max( + ...Object.keys(eslintPlugin.rules).map(name => name.length), +); +const BASE_RULES_TO_BE_OVERRIDDEN = new Map( + Object.entries(eslintPlugin.rules) + .filter(([, rule]) => rule.meta.docs.extendsBaseRule) + .map( + ([ruleName, rule]) => + [ + ruleName, + typeof rule.meta.docs.extendsBaseRule === 'string' + ? rule.meta.docs.extendsBaseRule + : ruleName, + ] as const, + ), +); - // special case - return-await used to be an extension, but no longer is. - // See https://github.com/typescript-eslint/typescript-eslint/issues/9517 - BASE_RULES_TO_BE_OVERRIDDEN.set('return-await', 'no-return-await'); +// special case - return-await used to be an extension, but no longer is. +// See https://github.com/typescript-eslint/typescript-eslint/issues/9517 +BASE_RULES_TO_BE_OVERRIDDEN.set('return-await', 'no-return-await'); - type RuleEntry = [string, ESLintPluginRuleModule]; +type RuleEntry = [string, ESLintPluginRuleModule]; - const allRuleEntries: RuleEntry[] = Object.entries(eslintPlugin.rules).sort( - (a, b) => a[0].localeCompare(b[0]), - ); +const allRuleEntries: RuleEntry[] = Object.entries(eslintPlugin.rules).sort( + (a, b) => a[0].localeCompare(b[0]), +); - type GetRuleOptions = ( - rule: ESLintPluginRuleModule, - ) => readonly unknown[] | true | undefined; +type GetRuleOptions = ( + rule: ESLintPluginRuleModule, +) => readonly unknown[] | true | undefined; - interface ConfigRuleSettings { - baseRuleForExtensionRule?: 'exclude'; - deprecated?: 'exclude'; - forcedRuleLevel?: Linter.RuleLevel; - getOptions?: GetRuleOptions | undefined; - typeChecked?: 'exclude' | 'include-only'; - } +interface ConfigRuleSettings { + baseRuleForExtensionRule?: 'exclude'; + deprecated?: 'exclude'; + forcedRuleLevel?: Linter.RuleLevel; + getOptions?: GetRuleOptions | undefined; + typeChecked?: 'exclude' | 'include-only'; +} - /** - * Helper function reduces records to key - value pairs. - */ - function reducer( - config: LinterConfigRules, - [key, value]: RuleEntry, - settings: ConfigRuleSettings = {}, - ): LinterConfigRules { - if (value.meta.deprecated) { - if (value.meta.docs.recommended) { - throw new Error(`${key} is both deprecated and recommended.`); - } - if (settings.deprecated) { - return config; - } +/** + * Helper function reduces records to key - value pairs. + */ +function reducer( + config: LinterConfigRules, + [key, value]: RuleEntry, + settings: ConfigRuleSettings = {}, +): LinterConfigRules { + if (value.meta.deprecated) { + if (value.meta.docs.recommended) { + throw new Error(`${key} is both deprecated and recommended.`); } - - // Explicitly exclude rules requiring type-checking - if ( - settings.typeChecked === 'exclude' && - value.meta.docs.requiresTypeChecking === true - ) { + if (settings.deprecated) { return config; } + } - if ( - settings.typeChecked === 'include-only' && - value.meta.docs.requiresTypeChecking !== true - ) { - return config; - } + // Explicitly exclude rules requiring type-checking + if ( + settings.typeChecked === 'exclude' && + value.meta.docs.requiresTypeChecking === true + ) { + return config; + } - const ruleName = `${RULE_NAME_PREFIX}${key}`; - - if ( - settings.baseRuleForExtensionRule !== 'exclude' && - BASE_RULES_TO_BE_OVERRIDDEN.has(key) - ) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const baseRuleName = BASE_RULES_TO_BE_OVERRIDDEN.get(key)!; - console.log( - baseRuleName - .padStart(RULE_NAME_PREFIX.length + baseRuleName.length) - .padEnd(RULE_NAME_PREFIX.length + MAX_RULE_NAME_LENGTH), - '=', - chalk.green('off'), - ); - config[baseRuleName] = 'off'; - } + if ( + settings.typeChecked === 'include-only' && + value.meta.docs.requiresTypeChecking !== true + ) { + return config; + } + + const ruleName = `${RULE_NAME_PREFIX}${key}`; + + if ( + settings.baseRuleForExtensionRule !== 'exclude' && + BASE_RULES_TO_BE_OVERRIDDEN.has(key) + ) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const baseRuleName = BASE_RULES_TO_BE_OVERRIDDEN.get(key)!; console.log( - `${chalk.dim(RULE_NAME_PREFIX)}${key.padEnd(MAX_RULE_NAME_LENGTH)}`, + baseRuleName + .padStart(RULE_NAME_PREFIX.length + baseRuleName.length) + .padEnd(RULE_NAME_PREFIX.length + MAX_RULE_NAME_LENGTH), '=', - chalk.red('error'), + chalk.green('off'), ); + config[baseRuleName] = 'off'; + } + console.log( + `${chalk.dim(RULE_NAME_PREFIX)}${key.padEnd(MAX_RULE_NAME_LENGTH)}`, + '=', + chalk.red('error'), + ); - const ruleLevel = settings.forcedRuleLevel ?? 'error'; - const ruleOptions = settings.getOptions?.(value); + const ruleLevel = settings.forcedRuleLevel ?? 'error'; + const ruleOptions = settings.getOptions?.(value); - config[ruleName] = - ruleOptions && ruleOptions !== true - ? [ruleLevel, ...ruleOptions] - : ruleLevel; + config[ruleName] = + ruleOptions && ruleOptions !== true + ? [ruleLevel, ...ruleOptions] + : ruleLevel; - return config; - } + return config; +} - interface WriteConfigSettings { - description: string; - getConfig: () => LinterConfig; - name: string; - } +interface WriteConfigSettings { + description: string; + getConfig: () => LinterConfig; + name: string; +} - /** - * Helper function that writes configuration. - */ - async function writeConfig({ - description, - getConfig, - name, - }: WriteConfigSettings): Promise { - const hyphens = '-'.repeat(35 - Math.ceil(name.length / 2)); - console.log(chalk.blueBright(`\n${hyphens} ${name}.ts ${hyphens}`)); - - const config = getConfig(); - - // - // 1. Classic Config - written to eslint-plugin/src/configs/eslintrc - // These configs are just JSON blobs that we write as TS files - // - - // note: we use `export =` because ESLint will import these configs via a commonjs import - const classicCode = [ - "import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';", - '', - `export = ${JSON.stringify(config)} satisfies ClassicConfig.Config;`, - ].join('\n'); - const classicConfigStr = await prettier.format( - addAutoGeneratedComment(classicCode), - { - parser: 'typescript', - ...prettierConfig, - }, - ); - fs.writeFileSync( - path.join( - PACKAGES_ESLINT_PLUGIN, - 'src', - 'configs', - 'eslintrc', - `${name}.ts`, - ), - classicConfigStr, - ); +/** + * Helper function that writes configuration. + */ +async function writeConfig({ + description, + getConfig, + name, +}: WriteConfigSettings): Promise { + const hyphens = '-'.repeat(35 - Math.ceil(name.length / 2)); + console.log(chalk.blueBright(`\n${hyphens} ${name}.ts ${hyphens}`)); + + const config = getConfig(); + + // + // 1. Classic Config - written to eslint-plugin/src/configs/eslintrc + // These configs are just JSON blobs that we write as TS files + // + + // note: we use `export =` because ESLint will import these configs via a commonjs import + const classicCode = [ + "import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';", + '', + `export = ${JSON.stringify(config)} satisfies ClassicConfig.Config;`, + ].join('\n'); + const classicConfigStr = await prettier.format( + addAutoGeneratedComment(classicCode), + { + parser: 'typescript', + ...prettierConfig, + }, + ); + fs.writeFileSync( + path.join( + PACKAGES_ESLINT_PLUGIN, + 'src', + 'configs', + 'eslintrc', + `${name}.ts`, + ), + classicConfigStr, + ); - // - // 2. Flat Config - written to eslint-plugin/src/configs/flat - // These configs are actual TS modules that import other configs - // - const flatCode: string[] = [ - ...AUTO_GENERATED_COMMENT_LINES, - "import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';", - '', - ]; - const flatExtends: string[] = []; - const flatConfig: FlatConfig.Config = { - name: `typescript-eslint/${name}`, - rules: config.rules, - }; - if (config.extends) { - for (const extendPath of config.extends) { - const config = EXTENDS_MODULES.find( - mod => mod.packageRelativePath === extendPath, - ); - if (config == null) { - throw new Error("Couldn't find config"); - } - flatCode.push( - `import ${config.name} from '${config.moduleRelativePath}';`, - ); - flatExtends.push(config.name); + // + // 2. Flat Config - written to eslint-plugin/src/configs/flat + // These configs are actual TS modules that import other configs + // + const flatCode: string[] = [ + ...AUTO_GENERATED_COMMENT_LINES, + "import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';", + '', + ]; + const flatExtends: string[] = []; + const flatConfig: FlatConfig.Config = { + name: `typescript-eslint/${name}`, + rules: config.rules, + }; + if (config.extends) { + for (const extendPath of config.extends) { + const config = EXTENDS_MODULES.find( + mod => mod.packageRelativePath === extendPath, + ); + if (config == null) { + throw new Error("Couldn't find config"); } - flatCode.push(''); - } - if (config.parserOptions) { - flatConfig.languageOptions ??= {}; - flatConfig.languageOptions.parserOptions = config.parserOptions; + flatCode.push( + `import ${config.name} from '${config.moduleRelativePath}';`, + ); + flatExtends.push(config.name); } + flatCode.push(''); + } + if (config.parserOptions) { + flatConfig.languageOptions ??= {}; + flatConfig.languageOptions.parserOptions = config.parserOptions; + } - const docComment = `/** + const docComment = `/** * ${description} * @see {@link https://typescript-eslint.io/users/configs#${name}} */`; - const flatConfigJson = JSON.stringify(flatConfig); - if (flatExtends.length > 0) { - flatCode.push( - docComment, - 'export default (plugin: FlatConfig.Plugin, parser: FlatConfig.Parser): FlatConfig.ConfigArray => [', - ...flatExtends.map(ext => `${ext}(plugin, parser),`), - flatConfigJson, - '];', - ); - } else { - flatCode.push( - docComment, - `export default (_plugin: FlatConfig.Plugin, _parser: FlatConfig.Parser): FlatConfig.Config => (${flatConfigJson});`, - ); - } - const flatConfigStr = await prettier.format(flatCode.join('\n'), { - parser: 'typescript', - ...prettierConfig, - }); - fs.writeFileSync( - path.join(PACKAGES_ESLINT_PLUGIN, 'src', 'configs', 'flat', `${name}.ts`), - flatConfigStr, + const flatConfigJson = JSON.stringify(flatConfig); + if (flatExtends.length > 0) { + flatCode.push( + docComment, + 'export default (plugin: FlatConfig.Plugin, parser: FlatConfig.Parser): FlatConfig.ConfigArray => [', + ...flatExtends.map(ext => `${ext}(plugin, parser),`), + flatConfigJson, + '];', + ); + } else { + flatCode.push( + docComment, + `export default (_plugin: FlatConfig.Plugin, _parser: FlatConfig.Parser): FlatConfig.Config => (${flatConfigJson});`, ); } + const flatConfigStr = await prettier.format(flatCode.join('\n'), { + parser: 'typescript', + ...prettierConfig, + }); + fs.writeFileSync( + path.join(PACKAGES_ESLINT_PLUGIN, 'src', 'configs', 'flat', `${name}.ts`), + flatConfigStr, + ); +} - interface ExtendedConfigSettings { - description: string; - name: string; - ruleEntries: readonly RuleEntry[]; - settings?: ConfigRuleSettings; - } +interface ExtendedConfigSettings { + description: string; + name: string; + ruleEntries: readonly RuleEntry[]; + settings?: ConfigRuleSettings; +} - async function writeExtendedConfig({ +async function writeExtendedConfig({ + description, + name, + ruleEntries, + settings, +}: ExtendedConfigSettings): Promise { + await writeConfig({ description, + getConfig: () => ({ + extends: [...CLASSIC_EXTENDS], + rules: ruleEntries.reduce( + (config, entry) => reducer(config, entry, settings), + {}, + ), + }), name, - ruleEntries, - settings, - }: ExtendedConfigSettings): Promise { - await writeConfig({ - description, - getConfig: () => ({ - extends: [...CLASSIC_EXTENDS], - rules: ruleEntries.reduce( - (config, entry) => reducer(config, entry, settings), - {}, - ), - }), - name, - }); - } - - function filterRuleEntriesTo( - ...recommendations: (RuleRecommendation | undefined)[] - ): RuleEntry[] { - return allRuleEntries.filter(([, rule]) => - typeof rule.meta.docs.recommended === 'object' - ? Object.keys(rule.meta.docs.recommended).some(level => - recommendations.includes(level as RuleRecommendation), - ) - : recommendations.includes(rule.meta.docs.recommended), - ); - } + }); +} - function createGetOptionsForLevel( - level: 'recommended' | 'strict', - ): GetRuleOptions { - return rule => - typeof rule.meta.docs.recommended === 'object' - ? rule.meta.docs.recommended[level] - : undefined; - } +function filterRuleEntriesTo( + ...recommendations: (RuleRecommendation | undefined)[] +): RuleEntry[] { + return allRuleEntries.filter(([, rule]) => + typeof rule.meta.docs.recommended === 'object' + ? Object.keys(rule.meta.docs.recommended).some(level => + recommendations.includes(level as RuleRecommendation), + ) + : recommendations.includes(rule.meta.docs.recommended), + ); +} - await writeExtendedConfig({ - description: - 'Enables each the rules provided as a part of typescript-eslint. Note that many rules are not applicable in all codebases, or are meant to be configured.', - name: 'all', - ruleEntries: allRuleEntries, - settings: { - deprecated: 'exclude', - }, - }); +function createGetOptionsForLevel( + level: 'recommended' | 'strict', +): GetRuleOptions { + return rule => + typeof rule.meta.docs.recommended === 'object' + ? rule.meta.docs.recommended[level] + : undefined; +} - await writeExtendedConfig({ - description: - 'Recommended rules for code correctness that you can drop in without additional configuration.', - name: 'recommended', - ruleEntries: filterRuleEntriesTo('recommended'), - settings: { - getOptions: createGetOptionsForLevel('recommended'), - typeChecked: 'exclude', - }, - }); +await writeExtendedConfig({ + description: + 'Enables each the rules provided as a part of typescript-eslint. Note that many rules are not applicable in all codebases, or are meant to be configured.', + name: 'all', + ruleEntries: allRuleEntries, + settings: { + deprecated: 'exclude', + }, +}); - await writeExtendedConfig({ - description: - 'Contains all of `recommended` along with additional recommended rules that require type information.', - name: 'recommended-type-checked', - ruleEntries: filterRuleEntriesTo('recommended'), - settings: { - getOptions: createGetOptionsForLevel('recommended'), - }, - }); +await writeExtendedConfig({ + description: + 'Recommended rules for code correctness that you can drop in without additional configuration.', + name: 'recommended', + ruleEntries: filterRuleEntriesTo('recommended'), + settings: { + getOptions: createGetOptionsForLevel('recommended'), + typeChecked: 'exclude', + }, +}); - await writeExtendedConfig({ - description: - 'A version of `recommended` that only contains type-checked rules and disables of any corresponding core ESLint rules.', - name: 'recommended-type-checked-only', - ruleEntries: filterRuleEntriesTo('recommended'), - settings: { - getOptions: createGetOptionsForLevel('recommended'), - typeChecked: 'include-only', - }, - }); +await writeExtendedConfig({ + description: + 'Contains all of `recommended` along with additional recommended rules that require type information.', + name: 'recommended-type-checked', + ruleEntries: filterRuleEntriesTo('recommended'), + settings: { + getOptions: createGetOptionsForLevel('recommended'), + }, +}); - await writeExtendedConfig({ - description: - 'Contains all of `recommended`, as well as additional strict rules that can also catch bugs. ', - name: 'strict', - ruleEntries: filterRuleEntriesTo('recommended', 'strict'), - settings: { - getOptions: createGetOptionsForLevel('strict'), - typeChecked: 'exclude', - }, - }); +await writeExtendedConfig({ + description: + 'A version of `recommended` that only contains type-checked rules and disables of any corresponding core ESLint rules.', + name: 'recommended-type-checked-only', + ruleEntries: filterRuleEntriesTo('recommended'), + settings: { + getOptions: createGetOptionsForLevel('recommended'), + typeChecked: 'include-only', + }, +}); - await writeExtendedConfig({ - description: - 'Contains all of `recommended`, `recommended-type-checked`, and `strict`, along with additional strict rules that require type information.', - name: 'strict-type-checked', - ruleEntries: filterRuleEntriesTo('recommended', 'strict'), - settings: { - getOptions: createGetOptionsForLevel('strict'), - }, - }); +await writeExtendedConfig({ + description: + 'Contains all of `recommended`, as well as additional strict rules that can also catch bugs. ', + name: 'strict', + ruleEntries: filterRuleEntriesTo('recommended', 'strict'), + settings: { + getOptions: createGetOptionsForLevel('strict'), + typeChecked: 'exclude', + }, +}); - await writeExtendedConfig({ - description: - 'A version of `strict` that only contains type-checked rules and disables of any corresponding core ESLint rules.', - name: 'strict-type-checked-only', - ruleEntries: filterRuleEntriesTo('recommended', 'strict'), - settings: { - getOptions: createGetOptionsForLevel('strict'), - typeChecked: 'include-only', - }, - }); +await writeExtendedConfig({ + description: + 'Contains all of `recommended`, `recommended-type-checked`, and `strict`, along with additional strict rules that require type information.', + name: 'strict-type-checked', + ruleEntries: filterRuleEntriesTo('recommended', 'strict'), + settings: { + getOptions: createGetOptionsForLevel('strict'), + }, +}); - await writeExtendedConfig({ - description: - 'Rules considered to be best practice for modern TypeScript codebases, but that do not impact program logic.', - name: 'stylistic', - ruleEntries: filterRuleEntriesTo('stylistic'), - settings: { - typeChecked: 'exclude', - }, - }); +await writeExtendedConfig({ + description: + 'A version of `strict` that only contains type-checked rules and disables of any corresponding core ESLint rules.', + name: 'strict-type-checked-only', + ruleEntries: filterRuleEntriesTo('recommended', 'strict'), + settings: { + getOptions: createGetOptionsForLevel('strict'), + typeChecked: 'include-only', + }, +}); - await writeExtendedConfig({ - description: - 'Contains all of `stylistic`, along with additional stylistic rules that require type information.', - name: 'stylistic-type-checked', - ruleEntries: filterRuleEntriesTo('stylistic'), - }); +await writeExtendedConfig({ + description: + 'Rules considered to be best practice for modern TypeScript codebases, but that do not impact program logic.', + name: 'stylistic', + ruleEntries: filterRuleEntriesTo('stylistic'), + settings: { + typeChecked: 'exclude', + }, +}); - await writeExtendedConfig({ - description: - 'A version of `stylistic` that only contains type-checked rules and disables of any corresponding core ESLint rules.', - name: 'stylistic-type-checked-only', - ruleEntries: filterRuleEntriesTo('stylistic'), - settings: { - typeChecked: 'include-only', - }, - }); +await writeExtendedConfig({ + description: + 'Contains all of `stylistic`, along with additional stylistic rules that require type information.', + name: 'stylistic-type-checked', + ruleEntries: filterRuleEntriesTo('stylistic'), +}); - await writeConfig({ - description: - 'A utility ruleset that will disable type-aware linting and all type-aware rules available in our project.', - getConfig: () => ({ - parserOptions: { - program: null, - project: false, - projectService: false, - }, - rules: allRuleEntries.reduce( - (config, entry) => - reducer(config, entry, { - baseRuleForExtensionRule: 'exclude', - forcedRuleLevel: 'off', - typeChecked: 'include-only', - }), - {}, - ), - }), - name: 'disable-type-checked', - }); -} +await writeExtendedConfig({ + description: + 'A version of `stylistic` that only contains type-checked rules and disables of any corresponding core ESLint rules.', + name: 'stylistic-type-checked-only', + ruleEntries: filterRuleEntriesTo('stylistic'), + settings: { + typeChecked: 'include-only', + }, +}); -main().catch((error: unknown) => { - console.error(error); +await writeConfig({ + description: + 'A utility ruleset that will disable type-aware linting and all type-aware rules available in our project.', + getConfig: () => ({ + parserOptions: { + program: null, + project: false, + projectService: false, + }, + rules: allRuleEntries.reduce( + (config, entry) => + reducer(config, entry, { + baseRuleForExtensionRule: 'exclude', + forcedRuleLevel: 'off', + typeChecked: 'include-only', + }), + {}, + ), + }), + name: 'disable-type-checked', }); diff --git a/tools/scripts/generate-contributors.mts b/tools/scripts/generate-contributors.mts index d380f0382012..9a72aa4030a5 100644 --- a/tools/scripts/generate-contributors.mts +++ b/tools/scripts/generate-contributors.mts @@ -154,40 +154,33 @@ function writeTable(contributors: User[], perLine = 5): void { fs.writeFileSync(path.join(REPO_ROOT, 'CONTRIBUTORS.md'), lines.join('\n')); } -async function main(): Promise { - const githubContributors: Contributor[] = []; +const githubContributors: Contributor[] = []; - // fetch all of the contributor info - for await (const lastUsers of fetchUsers()) { - githubContributors.push(...lastUsers); - } - - // fetch the user info - console.log(`Fetching user information...`); - const users = await Promise.allSettled( - githubContributors - // remove ignored users and bots - .filter( - usr => usr.login && usr.type !== 'Bot' && !IGNORED_USERS.has(usr.login), - ) - // fetch the in-depth information for each user - .map(c => getData(c.url)), - ); - - writeTable( - users - .map(result => { - if (result.status === 'fulfilled') { - return result.value?.body; - } - return null; - }) - .filter((c): c is User => c?.login != null), - 5, - ); +// fetch all of the contributor info +for await (const lastUsers of fetchUsers()) { + githubContributors.push(...lastUsers); } -main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; -}); +// fetch the user info +console.log(`Fetching user information...`); +const users = await Promise.allSettled( + githubContributors + // remove ignored users and bots + .filter( + usr => usr.login && usr.type !== 'Bot' && !IGNORED_USERS.has(usr.login), + ) + // fetch the in-depth information for each user + .map(c => getData(c.url)), +); + +writeTable( + users + .map(result => { + if (result.status === 'fulfilled') { + return result.value?.body; + } + return null; + }) + .filter((c): c is User => c?.login != null), + 5, +); diff --git a/tools/scripts/generate-lib.mts b/tools/scripts/generate-lib.mts index 599313dbdeb6..817d25c2a2f8 100644 --- a/tools/scripts/generate-lib.mts +++ b/tools/scripts/generate-lib.mts @@ -123,187 +123,175 @@ function getReferences( return references; } -async function main(): Promise { - try { - rimraf.sync(OUTPUT_FOLDER); - } catch { - // ignored - } - try { - fs.mkdirSync(OUTPUT_FOLDER); - } catch { - // ignored - } +try { + rimraf.sync(OUTPUT_FOLDER); +} catch { + // ignored +} +try { + fs.mkdirSync(OUTPUT_FOLDER); +} catch { + // ignored +} - const filesWritten: string[] = [ - SHARED_CONFIG_MODULE, - TYPES_FILE, - BARREL_PATH, - ]; - - // the shared - fs.writeFileSync( - SHARED_CONFIG_MODULE, - await formatCode([ - `export const TYPE = Object.freeze(${JSON.stringify({ - eslintImplicitGlobalSetting: 'readonly', - isTypeVariable: true, - isValueVariable: false, - })});`, - `export const VALUE = Object.freeze(${JSON.stringify({ - eslintImplicitGlobalSetting: 'readonly', - isTypeVariable: false, - isValueVariable: true, - })});`, - `export const TYPE_VALUE = Object.freeze(${JSON.stringify({ - eslintImplicitGlobalSetting: 'readonly', - isTypeVariable: true, - isValueVariable: true, - })});`, - '', - ]), - ); +const filesWritten: string[] = [SHARED_CONFIG_MODULE, TYPES_FILE, BARREL_PATH]; + +// the shared +fs.writeFileSync( + SHARED_CONFIG_MODULE, + await formatCode([ + `export const TYPE = Object.freeze(${JSON.stringify({ + eslintImplicitGlobalSetting: 'readonly', + isTypeVariable: true, + isValueVariable: false, + })});`, + `export const VALUE = Object.freeze(${JSON.stringify({ + eslintImplicitGlobalSetting: 'readonly', + isTypeVariable: false, + isValueVariable: true, + })});`, + `export const TYPE_VALUE = Object.freeze(${JSON.stringify({ + eslintImplicitGlobalSetting: 'readonly', + isTypeVariable: true, + isValueVariable: true, + })});`, + '', + ]), +); - for (const [libName, filename] of libMap) { - const libPath = path.join(TS_LIB_FOLDER, filename); - const { ast, scopeManager } = parseAndAnalyze( - fs.readFileSync(libPath, 'utf8'), - { - // we don't want any libs - lib: [], - sourceType: 'module', - }, - { - comment: true, - loc: true, - range: true, - }, - ) as { - // https://github.com/typescript-eslint/typescript-eslint/issues/8347 - ast: { comments: TSESTree.Comment[] } & TSESTree.Program; - } & ReturnType; - - const code = [`export const ${sanitize(libName)}: LibDefinition = {`]; - - const references = getReferences(ast); - if (references.size > 0) { - // add a newline before the export - code.unshift(''); - } +for (const [libName, filename] of libMap) { + const libPath = path.join(TS_LIB_FOLDER, filename); + const { ast, scopeManager } = parseAndAnalyze( + fs.readFileSync(libPath, 'utf8'), + { + // we don't want any libs + lib: [], + sourceType: 'module', + }, + { + comment: true, + loc: true, + range: true, + }, + ) as { + // https://github.com/typescript-eslint/typescript-eslint/issues/8347 + ast: { comments: TSESTree.Comment[] } & TSESTree.Program; + } & ReturnType; + + const code = [`export const ${sanitize(libName)}: LibDefinition = {`]; + + const references = getReferences(ast); + if (references.size > 0) { + // add a newline before the export + code.unshift(''); + } - // import and spread all of the references - const imports = ["import type { LibDefinition } from '../variable';"]; - code.push('libs: ['); - for (const reference of references) { - const name = sanitize(reference); - imports.push(`import { ${name} } from './${reference}'`); - code.push(`${name},`); - } - code.push('],'); - - const requiredBaseImports = new Set(); - - // add a declaration for each variable - const variables = getVariablesFromScope(scopeManager); - code.push('variables: ['); - for (const variable of variables) { - const importName = ((): BASE_CONFIG_EXPORT_NAMES => { - if (variable.isTypeVariable && variable.isValueVariable) { - return 'TYPE_VALUE'; - } - if (variable.isTypeVariable) { - return 'TYPE'; - } - if (variable.isValueVariable) { - return 'VALUE'; - } - // shouldn't happen - throw new Error( - "Unexpected variable that's is not a type or value variable", - ); - })(); - requiredBaseImports.add(importName); - - code.push(`['${variable.name}', ${importName}],`); - } - code.push('],'); - code.push('};'); - - if (requiredBaseImports.size > 0) { - imports.push( - `import {${[...requiredBaseImports] - .sort() - .join(',')}} from './${BASE_CONFIG_MODULE_NAME}';`, + // import and spread all of the references + const imports = ["import type { LibDefinition } from '../variable';"]; + code.push('libs: ['); + for (const reference of references) { + const name = sanitize(reference); + imports.push(`import { ${name} } from './${reference}'`); + code.push(`${name},`); + } + code.push('],'); + + const requiredBaseImports = new Set(); + + // add a declaration for each variable + const variables = getVariablesFromScope(scopeManager); + code.push('variables: ['); + for (const variable of variables) { + const importName = ((): BASE_CONFIG_EXPORT_NAMES => { + if (variable.isTypeVariable && variable.isValueVariable) { + return 'TYPE_VALUE'; + } + if (variable.isTypeVariable) { + return 'TYPE'; + } + if (variable.isValueVariable) { + return 'VALUE'; + } + // shouldn't happen + throw new Error( + "Unexpected variable that's is not a type or value variable", ); - } + })(); + requiredBaseImports.add(importName); - if (imports.length > 0) { - code.unshift(...imports, ''); - } - - const formattedCode = await formatCode(code); - const writePath = path.join(OUTPUT_FOLDER, `${libName}.ts`); - fs.writeFileSync(writePath, formattedCode); - filesWritten.push(writePath); - - console.log( - 'Wrote', - variables.length, - 'variables, and', - references.size, - 'references for', - libName, + code.push(`['${variable.name}', ${importName}],`); + } + code.push('],'); + code.push('};'); + + if (requiredBaseImports.size > 0) { + imports.push( + `import {${[...requiredBaseImports] + .sort() + .join(',')}} from './${BASE_CONFIG_MODULE_NAME}';`, ); } - // generate and write a barrel file - const barrelImports = []; // use a separate way so everything is in the same order - const barrelCode = [ - '', - `export const lib: ReadonlyMap =`, - `new Map([`, - ]; - // Call `Object.entries` during barrel construction to avoid redundantly calling - // and allocating a new array on every reference - for (const lib of libMap.keys()) { - const name = sanitize(lib); - if (name === 'lib') { - barrelImports.push(`import { lib as libBase } from './${lib}'`); - barrelCode.push(`['${lib}', libBase],`); - } else { - barrelImports.push(`import { ${name} } from './${lib}'`); - barrelCode.push(`['${lib}', ${name}],`); - } + if (imports.length > 0) { + code.unshift(...imports, ''); } - barrelCode.unshift(...barrelImports); - barrelCode.unshift('', `import type { LibDefinition } from '../variable';`); - barrelCode.push(']);'); - const formattedBarrelCode = await formatCode(barrelCode); + const formattedCode = await formatCode(code); + const writePath = path.join(OUTPUT_FOLDER, `${libName}.ts`); + fs.writeFileSync(writePath, formattedCode); + filesWritten.push(writePath); + + console.log( + 'Wrote', + variables.length, + 'variables, and', + references.size, + 'references for', + libName, + ); +} + +// generate and write a barrel file +const barrelImports = []; // use a separate way so everything is in the same order +const barrelCode = [ + '', + `export const lib: ReadonlyMap =`, + `new Map([`, +]; +// Call `Object.entries` during barrel construction to avoid redundantly calling +// and allocating a new array on every reference +for (const lib of libMap.keys()) { + const name = sanitize(lib); + if (name === 'lib') { + barrelImports.push(`import { lib as libBase } from './${lib}'`); + barrelCode.push(`['${lib}', libBase],`); + } else { + barrelImports.push(`import { ${name} } from './${lib}'`); + barrelCode.push(`['${lib}', ${name}],`); + } +} +barrelCode.unshift(...barrelImports); +barrelCode.unshift('', `import type { LibDefinition } from '../variable';`); +barrelCode.push(']);'); - fs.writeFileSync(BARREL_PATH, formattedBarrelCode); - console.log('Wrote barrel file'); +const formattedBarrelCode = await formatCode(barrelCode); - // generate a string union type for the lib names +fs.writeFileSync(BARREL_PATH, formattedBarrelCode); +console.log('Wrote barrel file'); - const libUnionCode = [ - `export type Lib = ${[...libMap.keys()].map(k => `'${k}'`).join(' | ')};`, - ]; - const formattedLibUnionCode = await formatCode(libUnionCode); +// generate a string union type for the lib names - fs.writeFileSync(TYPES_FILE, formattedLibUnionCode); - console.log('Wrote Lib union type file'); +const libUnionCode = [ + `export type Lib = ${[...libMap.keys()].map(k => `'${k}'`).join(' | ')};`, +]; +const formattedLibUnionCode = await formatCode(libUnionCode); - const lint = new FlatESLint({ - fix: true, - }); - const results = await lint.lintFiles(filesWritten); - await FlatESLint.outputFixes(results); - console.log('Autofixed lint errors'); -} +fs.writeFileSync(TYPES_FILE, formattedLibUnionCode); +console.log('Wrote Lib union type file'); -main().catch((e: unknown) => { - console.error(e); - // eslint-disable-next-line no-process-exit - process.exit(1); +const lint = new FlatESLint({ + fix: true, }); +const results = await lint.lintFiles(filesWritten); +await FlatESLint.outputFixes(results); +console.log('Autofixed lint errors'); diff --git a/tools/scripts/postinstall.mts b/tools/scripts/postinstall.mts index 5d123b1991d5..c6bfcd2f3a97 100644 --- a/tools/scripts/postinstall.mts +++ b/tools/scripts/postinstall.mts @@ -28,22 +28,20 @@ if (process.env.SKIP_POSTINSTALL) { process.exit(0); } -void (async function (): Promise { - // make sure we're running from the workspace root - const { - default: { workspaceRoot }, - } = await import('@nx/devkit'); - process.chdir(workspaceRoot); +// make sure we're running from the workspace root +const { + default: { workspaceRoot }, +} = await import('@nx/devkit'); +process.chdir(workspaceRoot); - // Install git hooks - await $`yarn husky`; +// Install git hooks +await $`yarn husky`; - if (!process.env.SKIP_POSTINSTALL_BUILD) { - // Clean any caches that may be invalid now - await $`yarn clean`; +if (!process.env.SKIP_POSTINSTALL_BUILD) { + // Clean any caches that may be invalid now + await $`yarn clean`; - // Build all the packages ready for use - await $`yarn build`; - await $`yarn nx typecheck ast-spec`; - } -})(); + // Build all the packages ready for use + await $`yarn build`; + await $`yarn nx typecheck ast-spec`; +}