Skip to content

chore: enable eslint-plugin-perfectionist on rule-tester package #9847

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

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ export default tseslint.config(
'packages/ast-spec/{src,tests,typings}/**/*.ts',
'packages/integration-tests/{tests,tools,typing}/**/*.ts',
'packages/parser/{src,tests}/**/*.ts',
'packages/rule-tester/{src,tests,typings}/**/*.ts',
'packages/utils/src/**/*.ts',
'packages/visitor-keys/src/**/*.ts',
'packages/website*/src/**/*.ts',
Expand Down
59 changes: 30 additions & 29 deletions packages/rule-tester/src/RuleTester.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
// Forked from https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/lib/rule-tester/rule-tester.js

import assert from 'node:assert';
import path from 'node:path';
import util from 'node:util';

import type * as ParserType from '@typescript-eslint/parser';
import * as parser from '@typescript-eslint/parser';
import type { TSESTree } from '@typescript-eslint/utils';
import { deepMerge } from '@typescript-eslint/utils/eslint-utils';
import type {
AnyRuleCreateFunction,
AnyRuleModule,
ParserOptions,
RuleListener,
RuleModule,
} from '@typescript-eslint/utils/ts-eslint';

import * as parser from '@typescript-eslint/parser';
import { deepMerge } from '@typescript-eslint/utils/eslint-utils';
import { Linter } from '@typescript-eslint/utils/ts-eslint';
import assert from 'node:assert';
import path from 'node:path';
import util from 'node:util';
// we intentionally import from eslint here because we need to use the same class
// that ESLint uses, not our custom override typed version
import { SourceCode } from 'eslint';
import stringify from 'json-stable-stringify-without-jsonify';
import merge from 'lodash.merge';

import { TestFramework } from './TestFramework';
import type {
InvalidTestCase,
NormalizedRunTests,
Expand All @@ -33,6 +32,8 @@ import type {
TesterConfigWithDefaults,
ValidTestCase,
} from './types';

import { TestFramework } from './TestFramework';
import { ajvBuilder } from './utils/ajv';
import { cloneDeeplyExcludesParent } from './utils/cloneDeeplyExcludesParent';
import { validate } from './utils/config-validator';
Expand Down Expand Up @@ -165,9 +166,9 @@ function getUnsubstitutedMessagePlaceholders(
}

export class RuleTester extends TestFramework {
readonly #testerConfig: TesterConfigWithDefaults;
readonly #rules: Record<string, AnyRuleCreateFunction | AnyRuleModule> = {};
readonly #linter: Linter;
readonly #rules: Record<string, AnyRuleCreateFunction | AnyRuleModule> = {};
readonly #testerConfig: TesterConfigWithDefaults;

/**
* Creates a new instance of RuleTester.
Expand Down Expand Up @@ -260,20 +261,6 @@ export class RuleTester extends TestFramework {
/**
* Define a rule for one particular run of tests.
*/
defineRule(name: string, rule: AnyRuleModule): void {
this.#rules[name] = {
...rule,
// Create a wrapper rule that freezes the `context` properties.
create(context): RuleListener {
freezeDeeply(context.options);
freezeDeeply(context.settings);
freezeDeeply(context.parserOptions);

return (typeof rule === 'function' ? rule : rule.create)(context);
},
};
}

#normalizeTests<
MessageIds extends string,
Options extends readonly unknown[],
Expand Down Expand Up @@ -335,6 +322,7 @@ export class RuleTester extends TestFramework {
};

const normalizedTests = {
invalid: rawTests.invalid.map(normalizeTest),
valid: rawTests.valid
.map(test => {
if (typeof test === 'string') {
Expand All @@ -343,7 +331,6 @@ export class RuleTester extends TestFramework {
return test;
})
.map(normalizeTest),
invalid: rawTests.invalid.map(normalizeTest),
};

// convenience iterator to make it easy to loop all tests without a concat
Expand Down Expand Up @@ -409,6 +396,20 @@ export class RuleTester extends TestFramework {
return normalizedTests;
}

defineRule(name: string, rule: AnyRuleModule): void {
this.#rules[name] = {
...rule,
// Create a wrapper rule that freezes the `context` properties.
create(context): RuleListener {
freezeDeeply(context.options);
freezeDeeply(context.settings);
freezeDeeply(context.parserOptions);

return (typeof rule === 'function' ? rule : rule.create)(context);
},
};
}

/**
* Adds a new rule test to execute.
*/
Expand Down Expand Up @@ -543,12 +544,12 @@ export class RuleTester extends TestFramework {
rule: RuleModule<MessageIds, Options>,
item: InvalidTestCase<MessageIds, Options> | ValidTestCase<Options>,
): {
messages: Linter.LintMessage[];
outputs: string[];
beforeAST: TSESTree.Program;
afterAST: TSESTree.Program;
beforeAST: TSESTree.Program;
config: RuleTesterConfig;
filename?: string;
messages: Linter.LintMessage[];
outputs: string[];
} {
this.defineRule(ruleName, rule);

Expand Down Expand Up @@ -692,7 +693,7 @@ export class RuleTester extends TestFramework {
do {
passNumber++;

const { applyLanguageOptions, applyInlineConfig, finalize } =
const { applyInlineConfig, applyLanguageOptions, finalize } =
SourceCode.prototype;

try {
Expand All @@ -704,7 +705,6 @@ export class RuleTester extends TestFramework {
});

const actualConfig = merge(configWithoutCustomKeys, {
linterOptions: { reportUnusedDisableDirectives: 1 },
languageOptions: {
...configWithoutCustomKeys.languageOptions,
parserOptions: {
Expand All @@ -713,6 +713,7 @@ export class RuleTester extends TestFramework {
...configWithoutCustomKeys.languageOptions?.parserOptions,
},
},
linterOptions: { reportUnusedDisableDirectives: 1 },
});
messages = this.#linter.verify(code, actualConfig, filename);
} finally {
Expand Down
34 changes: 16 additions & 18 deletions packages/rule-tester/src/TestFramework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ export type RuleTesterTestFrameworkFunctionBase = (
text: string,
callback: () => void,
) => void;
export type RuleTesterTestFrameworkFunction =
RuleTesterTestFrameworkFunctionBase & {
/**
* Skips running the tests inside this `describe` for the current file
*/
skip?: RuleTesterTestFrameworkFunctionBase;
};
export type RuleTesterTestFrameworkItFunction =
RuleTesterTestFrameworkFunctionBase & {
/**
* Only runs this test in the current file.
*/
only?: RuleTesterTestFrameworkFunctionBase;
/**
* Skips running this test in the current file.
*/
skip?: RuleTesterTestFrameworkFunctionBase;
};
export type RuleTesterTestFrameworkFunction = {
/**
* Skips running the tests inside this `describe` for the current file
*/
skip?: RuleTesterTestFrameworkFunctionBase;
} & RuleTesterTestFrameworkFunctionBase;
export type RuleTesterTestFrameworkItFunction = {
/**
* Only runs this test in the current file.
*/
only?: RuleTesterTestFrameworkFunctionBase;
/**
* Skips running this test in the current file.
*/
skip?: RuleTesterTestFrameworkFunctionBase;
} & RuleTesterTestFrameworkFunctionBase;

type Maybe<T> = T | null | undefined;

Expand Down
2 changes: 1 addition & 1 deletion packages/rule-tester/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { RuleTester } from './RuleTester';
export { noFormat } from './noFormat';
export { RuleTester } from './RuleTester';
export type {
InvalidTestCase,
RuleTesterConfig,
Expand Down
4 changes: 2 additions & 2 deletions packages/rule-tester/src/types/DependencyConstraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export interface RangeOptions {
}

export interface SemverVersionConstraint {
readonly range: string;
readonly options?: RangeOptions | boolean;
readonly range: string;
}
export type AtLeastVersionConstraint =
| `${number}.${number}.${number}-${string}`
| `${number}.${number}.${number}`
| `${number}.${number}.${number}-${string}`
Comment on lines 13 to +14
Copy link
Member

@bradzacher bradzacher Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird that this sorted this way.

Why does - come AFTER "nothing", but . comes BEFORE "nothing".
Is this a bug? What the what?

Copy link
Member Author

@JoshuaKGoldberg JoshuaKGoldberg Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's that ` is being treated as before - alphabetically?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That must be it - it must be considering the quote character as part of the thing.

Would be worth filing a bug for that.
"if the two types are the same literal type (eg both template literal types) then the quote character should be excluded from the sort comparison.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 noted in #8479 (comment)

| `${number}.${number}`
| `${number}`;
export type VersionConstraint =
Expand Down
18 changes: 9 additions & 9 deletions packages/rule-tester/src/types/InvalidTestCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import type { DependencyConstraint } from './DependencyConstraint';
import type { ValidTestCase } from './ValidTestCase';

export interface SuggestionOutput<MessageIds extends string> {
/**
* Reported message ID.
*/
readonly messageId: MessageIds;
/**
* The data used to fill the message template.
*/
readonly data?: ReportDescriptorMessageData;
/**
* Reported message ID.
*/
readonly messageId: MessageIds;
/**
* NOTE: Suggestions will be applied as a stand-alone change, without triggering multi-pass fixes.
* Each individual error has its own suggestion, so you have to show the correct, _isolated_ output for each suggestion.
Expand Down Expand Up @@ -65,16 +65,16 @@ export interface InvalidTestCase<
MessageIds extends string,
Options extends readonly unknown[],
> extends ValidTestCase<Options> {
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
/**
* Expected errors.
*/
readonly errors: readonly TestCaseError<MessageIds>[];
/**
* The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
*/
readonly output?: string | string[] | null;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
readonly output?: string[] | string | null;
}
24 changes: 12 additions & 12 deletions packages/rule-tester/src/types/ValidTestCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export interface TestLanguageOptions {
}

export interface ValidTestCase<Options extends readonly unknown[]> {
/**
* Name for the test case.
*/
readonly name?: string;
/**
* Code for the test case.
*/
readonly code: string;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
/**
* The fake filename for the test case. Useful for rules that make assertion about filenames.
*/
Expand All @@ -43,6 +43,14 @@ export interface ValidTestCase<Options extends readonly unknown[]> {
* Language options for the test case.
*/
readonly languageOptions?: TestLanguageOptions;
/**
* Name for the test case.
*/
readonly name?: string;
/**
* Run this case exclusively for debugging in supported test frameworks.
*/
readonly only?: boolean;
/**
* Options for the test case.
*/
Expand All @@ -51,16 +59,8 @@ export interface ValidTestCase<Options extends readonly unknown[]> {
* Settings for the test case.
*/
readonly settings?: Readonly<SharedConfigurationSettings>;
/**
* Run this case exclusively for debugging in supported test frameworks.
*/
readonly only?: boolean;
/**
* Skip this case in supported test frameworks.
*/
readonly skip?: boolean;
/**
* Constraints that must pass in the current environment for the test to run
*/
readonly dependencyConstraints?: DependencyConstraint;
}
6 changes: 3 additions & 3 deletions packages/rule-tester/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ export interface RunTests<
MessageIds extends string,
Options extends readonly unknown[],
> {
readonly invalid: readonly InvalidTestCase<MessageIds, Options>[];
// RuleTester.run also accepts strings for valid cases
readonly valid: readonly (ValidTestCase<Options> | string)[];
readonly invalid: readonly InvalidTestCase<MessageIds, Options>[];
}

export interface NormalizedRunTests<
MessageIds extends string,
Options extends readonly unknown[],
> {
readonly valid: readonly ValidTestCase<Options>[];
readonly invalid: readonly InvalidTestCase<MessageIds, Options>[];
readonly valid: readonly ValidTestCase<Options>[];
}

export type { ValidTestCase } from './ValidTestCase';
export type {
InvalidTestCase,
SuggestionOutput,
TestCaseError,
} from './InvalidTestCase';
export type { RuleTesterConfig } from './RuleTesterConfig';
export type { ValidTestCase } from './ValidTestCase';
4 changes: 2 additions & 2 deletions packages/rule-tester/src/utils/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import metaSchema from 'ajv/lib/refs/json-schema-draft-04.json';
export function ajvBuilder(additionalOptions = {}): Ajv.Ajv {
const ajv = new Ajv({
meta: false,
missingRefs: 'ignore',
schemaId: 'auto',
useDefaults: true,
validateSchema: false,
missingRefs: 'ignore',
verbose: true,
schemaId: 'auto',
...additionalOptions,
});

Expand Down
7 changes: 4 additions & 3 deletions packages/rule-tester/src/utils/config-validator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Forked from https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/lib/shared/config-validator.js

import util from 'node:util';

import type { AnyRuleModule, Linter } from '@typescript-eslint/utils/ts-eslint';
import type {
AdditionalPropertiesParams,
ErrorObject as AjvErrorObject,
ValidateFunction,
} from 'ajv';

import { builtinRules } from 'eslint/use-at-your-own-risk';
import util from 'node:util';

import type { TesterConfigWithDefaults } from '../types';

import { ajvBuilder } from './ajv';
import { emitDeprecationWarning } from './deprecation-warnings';
import { flatConfigSchema } from './flat-config-schema';
Expand All @@ -25,8 +26,8 @@ const ruleValidators = new WeakMap<AnyRuleModule, ValidateFunction>();
let validateSchema: ValidateFunction | undefined;
const severityMap = {
error: 2,
warn: 1,
off: 0,
warn: 1,
Comment on lines 28 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is another case where the value ordering is better than the prop name ordering

} as const;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/rule-tester/src/utils/deprecation-warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function emitDeprecationWarning(
source: string,
errorCode: keyof typeof deprecationWarningMessages,
): void {
const cacheKey = JSON.stringify({ source, errorCode });
const cacheKey = JSON.stringify({ errorCode, source });

if (sourceFileErrorCache.has(cacheKey)) {
return;
Expand Down
Loading
Loading