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

feat: add types for flat config files #7273

Merged
merged 3 commits into from Nov 13, 2023
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
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