Skip to content

Commit

Permalink
address feedback and add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Oct 7, 2022
1 parent 74e5ba7 commit 6bb23c3
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 376 deletions.
40 changes: 18 additions & 22 deletions packages/utils/src/eslint-utils/rule-tester/RuleTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { DependencyConstraint } from './dependencyConstraints';
import { satisfiesAllDependencyConstraints } from './dependencyConstraints';

const TS_ESLINT_PARSER = '@typescript-eslint/parser';
const ERROR_MESSAGE = `Do not set the parser at the test level unless you want to use a parser other than ${TS_ESLINT_PARSER}`;

type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & {
parser: typeof TS_ESLINT_PARSER;
Expand Down Expand Up @@ -109,8 +110,6 @@ class RuleTester extends TSESLint.RuleTester {
rule: TSESLint.RuleModule<TMessageIds, TOptions>,
testsReadonly: RunTests<TMessageIds, TOptions>,
): void {
const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${TS_ESLINT_PARSER}`;

const tests = {
// standardize the valid tests as objects
valid: testsReadonly.valid.map(test => {
Expand All @@ -124,6 +123,18 @@ class RuleTester extends TSESLint.RuleTester {
invalid: testsReadonly.invalid,
};

// convenience iterator to make it easy to loop all tests without a concat
const allTestsIterator = {
*[Symbol.iterator](): Generator<ValidTestCase<TOptions>, void, unknown> {
for (const test of tests.valid) {
yield test;
}
for (const test of tests.invalid) {
yield test;
}
},
};

/*
Automatically add a filename to the tests to enable type-aware tests to "just work".
This saves users having to verbosely and manually add the filename to every
Expand All @@ -139,7 +150,7 @@ class RuleTester extends TSESLint.RuleTester {
test: T,
): T => {
if (test.parser === TS_ESLINT_PARSER) {
throw new Error(errorMessage);
throw new Error(ERROR_MESSAGE);
}
if (!test.filename) {
return {
Expand All @@ -153,12 +164,7 @@ class RuleTester extends TSESLint.RuleTester {
tests.invalid = tests.invalid.map(addFilename);

const hasOnly = ((): boolean => {
for (const test of tests.valid) {
if (test.only) {
return true;
}
}
for (const test of tests.invalid) {
for (const test of allTestsIterator) {
if (test.only) {
return true;
}
Expand All @@ -172,15 +178,7 @@ class RuleTester extends TSESLint.RuleTester {
Automatically skip tests that don't satisfy the dependency constraints.
*/
const hasConstraints = ((): boolean => {
for (const test of tests.valid) {
if (
test.dependencyConstraints &&
Object.keys(test.dependencyConstraints).length > 0
) {
return true;
}
}
for (const test of tests.invalid) {
for (const test of allTestsIterator) {
if (
test.dependencyConstraints &&
Object.keys(test.dependencyConstraints).length > 0
Expand All @@ -191,6 +189,7 @@ class RuleTester extends TSESLint.RuleTester {
return false;
})();
if (hasConstraints) {
// The `only: boolean` test property was only added in ESLint v7.29.0.
if (semver.satisfies(eslintVersion, '>=7.29.0')) {
/*
Mark all satisfactory tests as `only: true`, and all other tests as
Expand Down Expand Up @@ -225,10 +224,7 @@ class RuleTester extends TSESLint.RuleTester {
tests.valid = tests.valid.map(maybeMarkAsOnly);
tests.invalid = tests.invalid.map(maybeMarkAsOnly);
} else {
/*
The `only: boolean` test property was only added in ESLint v7.29.0.
on older versions we just fallback to raw filtering like savages
*/
// On older versions we just fallback to raw array filtering like SAVAGES
tests.valid = tests.valid.filter(test =>
satisfiesAllDependencyConstraints(test.dependencyConstraints),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import * as semver from 'semver';

interface SemverVersionConstraint {
readonly type: 'semver';
readonly range: string;
readonly options?: Parameters<typeof semver.satisfies>[2];
}
interface AtLeastVersionConstraint {
readonly type: 'at-least';
readonly version:
| `${number}`
| `${number}.${number}`
| `${number}.${number}.${number}`
| `${number}.${number}.${number}-${string}`;
}
type AtLeastVersionConstraint =
| `${number}`
| `${number}.${number}`
| `${number}.${number}.${number}`
| `${number}.${number}.${number}-${string}`;
type VersionConstraint = SemverVersionConstraint | AtLeastVersionConstraint;
interface DependencyConstraint {
/**
* Passing a string for the value is shorthand for the 'at-least' constraint
*/
readonly [packageName: string]:
| VersionConstraint
| AtLeastVersionConstraint['version'];
readonly [packageName: string]: VersionConstraint;
}

const BASE_SATISFIES_OPTIONS: semver.RangeOptions = {
Expand All @@ -31,27 +25,19 @@ function satisfiesDependencyConstraint(
packageName: string,
constraintIn: DependencyConstraint[string],
): boolean {
const constraint: VersionConstraint =
const constraint: SemverVersionConstraint =
typeof constraintIn === 'string'
? {
type: 'at-least',
version: constraintIn,
range: `>=${constraintIn}`,
}
: constraintIn;

const satisfiesArguments: [string, SemverVersionConstraint['options']] =
constraint.type === 'at-least'
? [`>=${constraint.version}`, BASE_SATISFIES_OPTIONS]
: [
constraint.range,
typeof constraint.options === 'object'
? { ...BASE_SATISFIES_OPTIONS, ...constraint.options }
: constraint.options,
];

return semver.satisfies(
(require(`${packageName}/package.json`) as { version: string }).version,
...satisfiesArguments,
constraint.range,
typeof constraint.options === 'object'
? { ...BASE_SATISFIES_OPTIONS, ...constraint.options }
: constraint.options,
);
}

Expand Down

0 comments on commit 6bb23c3

Please sign in to comment.