Skip to content

Commit

Permalink
feat: add types for flat config files (#7273)
Browse files Browse the repository at this point in the history
* feat: add types for flat config files

* Update packages/utils/src/ts-eslint/Parser.ts

---------

Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
  • Loading branch information
bradzacher and JoshuaKGoldberg committed Nov 13, 2023
1 parent b73d8b2 commit 66cd0c0
Show file tree
Hide file tree
Showing 22 changed files with 500 additions and 206 deletions.
4 changes: 3 additions & 1 deletion packages/eslint-plugin-tslint/tests/index.spec.ts
Expand Up @@ -3,6 +3,8 @@ import { TSESLint } from '@typescript-eslint/utils';
import { readFileSync } from 'fs';
import path = require('path');

import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';

import type { Options } from '../src/rules/config';
import rule from '../src/rules/config';

Expand Down Expand Up @@ -165,7 +167,7 @@ ruleTester.run('tslint/config', rule, {
});

describe('tslint/error', () => {
function testOutput(code: string, config: TSESLint.Linter.Config): void {
function testOutput(code: string, config: ClassicConfig.Config): void {
const linter = new TSESLint.Linter();
linter.defineRule('tslint/config', rule);
linter.defineParser('@typescript-eslint/parser', parser);
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin/index.d.ts
@@ -1,9 +1,9 @@
import type { TSESLint } from '@typescript-eslint/utils';
import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';

import type rules from './rules';

declare const cjsExport: {
configs: Record<string, TSESLint.Linter.Config>;
configs: Record<string, ClassicConfig.Config>;
rules: typeof rules;
};
export = cjsExport;
53 changes: 30 additions & 23 deletions packages/eslint-plugin/tools/generate-configs.ts
@@ -1,7 +1,12 @@
import prettier from '@prettier/sync';
import type { TSESLint } from '@typescript-eslint/utils';
import type {
ClassicConfig,
Linter,
RuleModule,
RuleRecommendation,
} from '@typescript-eslint/utils/ts-eslint';
import * as fs from 'fs';
import * as path from 'path';
import prettier from 'prettier';
import * as url from 'url';

import type RulesFile from '../src/rules';
Expand Down Expand Up @@ -45,11 +50,11 @@ async function main(): Promise<void> {
].join('\n');
}

const prettierConfig = prettier.resolveConfig(__dirname);
const prettierConfig = await prettier.resolveConfig(__dirname);

type LinterConfigRules = Record<string, TSESLint.Linter.RuleLevel>;
type LinterConfigRules = Record<string, ClassicConfig.RuleLevel>;

interface LinterConfig extends TSESLint.Linter.Config {
interface LinterConfig extends ClassicConfig.Config {
extends?: string[] | string;
plugins?: string[];
}
Expand All @@ -74,8 +79,7 @@ async function main(): Promise<void> {
);
const EXTENDS = ['./configs/base', './configs/eslint-recommended'];

// TODO: migrate to import { RuleModule } from '@typescript-eslint/utils/ts-eslint'
type RuleEntry = [string, TSESLint.RuleModule<string, readonly unknown[]>];
type RuleEntry = [string, RuleModule<string, readonly unknown[]>];

const allRuleEntries: RuleEntry[] = Object.entries(rules).sort((a, b) =>
a[0].localeCompare(b[0]),
Expand All @@ -85,7 +89,7 @@ async function main(): Promise<void> {
deprecated?: 'exclude';
typeChecked?: 'exclude' | 'include-only';
baseRuleForExtensionRule?: 'exclude';
forcedRuleLevel?: TSESLint.Linter.RuleLevel;
forcedRuleLevel?: Linter.RuleLevel;
}

/**
Expand Down Expand Up @@ -144,13 +148,16 @@ async function main(): Promise<void> {
/**
* Helper function writes configuration.
*/
function writeConfig(getConfig: () => LinterConfig, name: string): void {
async function writeConfig(
getConfig: () => LinterConfig,
name: string,
): Promise<void> {
const hyphens = '-'.repeat(35 - Math.ceil(name.length / 2));
console.log(chalk.blueBright(`\n${hyphens} ${name}.ts ${hyphens}`));

// note: we use `export =` because ESLint will import these configs via a commonjs import
const code = `export = ${JSON.stringify(getConfig())};`;
const configStr = prettier.format(addAutoGeneratedComment(code), {
const configStr = await prettier.format(addAutoGeneratedComment(code), {
parser: 'typescript',
...prettierConfig,
});
Expand All @@ -167,13 +174,13 @@ async function main(): Promise<void> {
ruleEntries: readonly RuleEntry[];
}

function writeExtendedConfig({
async function writeExtendedConfig({
extraExtends = [],
filters: ruleFilter,
name,
ruleEntries,
}: ExtendedConfigSettings): void {
writeConfig(
}: ExtendedConfigSettings): Promise<void> {
await writeConfig(
() => ({
extends: [...EXTENDS, ...extraExtends],
rules: ruleEntries.reduce(
Expand All @@ -186,14 +193,14 @@ async function main(): Promise<void> {
}

function filterRuleEntriesTo(
...recommendations: (TSESLint.RuleRecommendation | undefined)[]
...recommendations: (RuleRecommendation | undefined)[]
): RuleEntry[] {
return allRuleEntries.filter(([, rule]) =>
recommendations.includes(rule.meta.docs?.recommended),
);
}

writeConfig((): LinterConfig => {
await writeConfig((): LinterConfig => {
const baseConfig: LinterConfig = {
parser: '@typescript-eslint/parser',
parserOptions: {
Expand All @@ -215,54 +222,54 @@ async function main(): Promise<void> {
return baseConfig;
}, 'base');

writeExtendedConfig({
await writeExtendedConfig({
name: 'all',
filters: {
deprecated: 'exclude',
},
ruleEntries: allRuleEntries,
});

writeExtendedConfig({
await writeExtendedConfig({
filters: {
typeChecked: 'exclude',
},
name: 'recommended',
ruleEntries: filterRuleEntriesTo('recommended'),
});

writeExtendedConfig({
await writeExtendedConfig({
name: 'recommended-type-checked',
ruleEntries: filterRuleEntriesTo('recommended'),
});

writeExtendedConfig({
await writeExtendedConfig({
filters: {
typeChecked: 'exclude',
},
name: 'strict',
ruleEntries: filterRuleEntriesTo('recommended', 'strict'),
});

writeExtendedConfig({
await writeExtendedConfig({
name: 'strict-type-checked',
ruleEntries: filterRuleEntriesTo('recommended', 'strict'),
});

writeExtendedConfig({
await writeExtendedConfig({
filters: {
typeChecked: 'exclude',
},
name: 'stylistic',
ruleEntries: filterRuleEntriesTo('stylistic'),
});

writeExtendedConfig({
await writeExtendedConfig({
name: 'stylistic-type-checked',
ruleEntries: filterRuleEntriesTo('stylistic'),
});

writeConfig(
await writeConfig(
() => ({
parserOptions: {
project: null,
Expand Down
3 changes: 2 additions & 1 deletion packages/rule-tester/src/RuleTester.ts
Expand Up @@ -10,6 +10,7 @@ import { deepMerge } from '@typescript-eslint/utils/eslint-utils';
import type {
AnyRuleCreateFunction,
AnyRuleModule,
Parser,
ParserOptions,
RuleContext,
RuleModule,
Expand Down Expand Up @@ -525,7 +526,7 @@ export class RuleTester extends TestFramework {

this.#linter.defineParser(
config.parser,
wrapParser(require(config.parser) as Linter.ParserModule),
wrapParser(require(config.parser) as Parser.ParserModule),
);

if (schema) {
Expand Down
7 changes: 5 additions & 2 deletions packages/rule-tester/src/types/RuleTesterConfig.ts
@@ -1,8 +1,11 @@
import type { Linter, ParserOptions } from '@typescript-eslint/utils/ts-eslint';
import type {
ClassicConfig,
ParserOptions,
} from '@typescript-eslint/utils/ts-eslint';

import type { DependencyConstraint } from './DependencyConstraint';

export interface RuleTesterConfig extends Linter.Config {
export interface RuleTesterConfig extends ClassicConfig.Config {
/**
* The default parser to use for tests.
* @default '@typescript-eslint/parser'
Expand Down
5 changes: 3 additions & 2 deletions packages/rule-tester/src/types/ValidTestCase.ts
@@ -1,4 +1,5 @@
import type {
Linter,
ParserOptions,
SharedConfigurationSettings,
} from '@typescript-eslint/utils/ts-eslint';
Expand All @@ -17,15 +18,15 @@ export interface ValidTestCase<TOptions extends Readonly<unknown[]>> {
/**
* Environments for the test case.
*/
readonly env?: Readonly<Record<string, boolean>>;
readonly env?: Readonly<Linter.EnvironmentConfig>;
/**
* The fake filename for the test case. Useful for rules that make assertion about filenames.
*/
readonly filename?: string;
/**
* The additional global variables.
*/
readonly globals?: Record<string, 'off' | 'readonly' | 'writable' | true>;
readonly globals?: Readonly<Linter.GlobalsConfig>;
/**
* Options for the test case.
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/rule-tester/src/utils/validationHelpers.ts
@@ -1,6 +1,6 @@
import { simpleTraverse } from '@typescript-eslint/typescript-estree';
import type { TSESTree } from '@typescript-eslint/utils';
import type { Linter, SourceCode } from '@typescript-eslint/utils/ts-eslint';
import type { Parser, SourceCode } from '@typescript-eslint/utils/ts-eslint';

/*
* List every parameters possible on a test case that are not related to eslint
Expand Down Expand Up @@ -75,7 +75,7 @@ const parserSymbol = Symbol.for('eslint.RuleTester.parser');
* Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes.
* In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties.
*/
export function wrapParser(parser: Linter.ParserModule): Linter.ParserModule {
export function wrapParser(parser: Parser.ParserModule): Parser.ParserModule {
/**
* Define `start`/`end` properties of all nodes of the given AST as throwing error.
*/
Expand Down Expand Up @@ -121,7 +121,7 @@ export function wrapParser(parser: Linter.ParserModule): Linter.ParserModule {
return {
// @ts-expect-error -- see above
[parserSymbol]: parser,
parseForESLint(...args): Linter.ESLintParseResult {
parseForESLint(...args): Parser.ParseResult {
const ret = parser.parseForESLint(...args);

defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys);
Expand Down
4 changes: 2 additions & 2 deletions packages/scope-manager/src/ScopeManager.ts
@@ -1,4 +1,4 @@
import type { TSESTree } from '@typescript-eslint/types';
import type { SourceType, TSESTree } from '@typescript-eslint/types';

import { assert } from './assert';
import type { Scope } from './scope';
Expand Down Expand Up @@ -27,7 +27,7 @@ import type { Variable } from './variable';

interface ScopeManagerOptions {
globalReturn?: boolean;
sourceType?: 'module' | 'script';
sourceType?: SourceType;
impliedStrict?: boolean;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/scope-manager/src/analyze.ts
@@ -1,4 +1,4 @@
import type { Lib, TSESTree } from '@typescript-eslint/types';
import type { Lib, SourceType, TSESTree } from '@typescript-eslint/types';
import { visitorKeys } from '@typescript-eslint/visitor-keys';

import type { ReferencerOptions } from './referencer';
Expand Down Expand Up @@ -55,7 +55,7 @@ interface AnalyzeOptions {
/**
* The source type of the script.
*/
sourceType?: 'module' | 'script';
sourceType?: SourceType;

/**
* Emit design-type metadata for decorated declarations in source.
Expand Down
10 changes: 8 additions & 2 deletions packages/types/src/parser-options.ts
Expand Up @@ -16,21 +16,27 @@ type EcmaVersion =
| 11
| 12
| 13
| 14
| 15
| 2015
| 2016
| 2017
| 2018
| 2019
| 2020
| 2021
| 2022;
| 2022
| 2023
| 2024;

type SourceType = 'module' | 'script';
type SourceTypeClassic = 'module' | 'script';
type SourceType = SourceTypeClassic | 'commonjs';

interface ParserOptions {
ecmaFeatures?: {
globalReturn?: boolean;
jsx?: boolean;
[key: string]: unknown;
};
ecmaVersion?: EcmaVersion | 'latest';

Expand Down

0 comments on commit 66cd0c0

Please sign in to comment.