Skip to content
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
47 changes: 40 additions & 7 deletions apps/oxlint/src-js/package/rule_tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,19 @@ function getItOnly(): ItFn {
/**
* Configuration for `RuleTester`.
*/
export type Config = Record<string, unknown>;
export interface Config {
/**
* ESLint compatibility mode.
* If `true`, column offsets in diagnostics are incremented by 1, to match ESLint's behavior.
*/
eslintCompat?: boolean;
[key: string]: unknown;
}

// Default shared config
const DEFAULT_SHARED_CONFIG: Config = {};
const DEFAULT_SHARED_CONFIG: Config = {
eslintCompat: false,
};

// `RuleTester` uses this config as its default. Can be overwritten via `RuleTester.setDefaultConfig()`.
let sharedConfig: Config = DEFAULT_SHARED_CONFIG;
Expand All @@ -134,6 +143,11 @@ interface TestCase {
options?: Options;
before?: (this: this) => void;
after?: (this: this) => void;
/**
* `true` to enable ESLint compatibility mode.
* See `Config` type.
*/
eslintCompat?: boolean;
}

/**
Expand Down Expand Up @@ -429,7 +443,7 @@ function assertInvalidTestCasePasses(test: InvalidTestCase, plugin: Plugin, conf
} else {
// `error` is an error object
assertInvalidTestCaseMessageIsCorrect(diagnostic, error, messages);
assertInvalidTestCaseLocationIsCorrect(diagnostic, error);
assertInvalidTestCaseLocationIsCorrect(diagnostic, error, config);

// TODO: Test suggestions
}
Expand Down Expand Up @@ -521,9 +535,14 @@ function assertInvalidTestCaseMessageIsCorrect(
* Assert that location reported by rule under test matches the expected location.
* @param diagnostic - Diagnostic emitted by rule under test
* @param error - Error object from test case
* @param config - Config for this test case
* @throws {AssertionError} If diagnostic's location does not match expected location
*/
function assertInvalidTestCaseLocationIsCorrect(diagnostic: Diagnostic, error: Error) {
function assertInvalidTestCaseLocationIsCorrect(
diagnostic: Diagnostic,
error: Error,
config: Config,
) {
interface Location {
line?: number;
column?: number;
Expand All @@ -534,13 +553,15 @@ function assertInvalidTestCaseLocationIsCorrect(diagnostic: Diagnostic, error: E
const actualLocation: Location = {};
const expectedLocation: Location = {};

const columnOffset = config.eslintCompat === true ? 1 : 0;

if (hasOwn(error, "line")) {
actualLocation.line = diagnostic.line;
expectedLocation.line = error.line;
}

if (hasOwn(error, "column")) {
actualLocation.column = diagnostic.column;
actualLocation.column = diagnostic.column + columnOffset;
expectedLocation.column = error.column;
}

Expand All @@ -550,7 +571,7 @@ function assertInvalidTestCaseLocationIsCorrect(diagnostic: Diagnostic, error: E
}

if (hasOwn(error, "endColumn")) {
actualLocation.endColumn = diagnostic.endColumn;
actualLocation.endColumn = diagnostic.endColumn + columnOffset;
expectedLocation.endColumn = error.endColumn;
}

Expand Down Expand Up @@ -654,7 +675,19 @@ function createConfigForRun(config: Config | null): Config {
* @returns Merged config
*/
function createConfigForTest(test: TestCase, config: Config): Config {
// TODO: Merge properties of `test` into `config`
let isCloned = false;
function clone(): void {
if (!isCloned) {
config = { ...config };
isCloned = true;
}
}

// TODO: Merge more properties of `test` into `config`
if (hasOwn(test, "eslintCompat")) {
clone();
config.eslintCompat = test.eslintCompat;
}
return config;
}

Expand Down
97 changes: 97 additions & 0 deletions apps/oxlint/test/rule_tester.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,4 +816,101 @@ describe("RuleTester", () => {
expect(test.after.mock.contexts[0]).toBe(test);
}
});

describe("ESLint compat mode", () => {
it("enabled globally", () => {
RuleTester.setDefaultConfig({ eslintCompat: true });

const tester = new RuleTester();
tester.run("no-foo", simpleRule, {
valid: [],
invalid: [
{
code: "foo;",
errors: [
{
message: "No foo!",
line: 1,
column: 1,
endLine: 1,
endColumn: 4,
},
],
},
],
});
expect(runCases()).toEqual([null]);
});

it("enabled in `RuleTester` options", () => {
const tester = new RuleTester({ eslintCompat: true });
tester.run("no-foo", simpleRule, {
valid: [],
invalid: [
{
code: "foo;",
errors: [
{
message: "No foo!",
line: 1,
column: 1,
endLine: 1,
endColumn: 4,
},
],
},
],
});
expect(runCases()).toEqual([null]);
});

it("enabled in in individual test cases", () => {
const tester = new RuleTester();
tester.run("no-foo", simpleRule, {
valid: [],
invalid: [
{
code: "foo = 1;",
// Default: eslintCompat: false
errors: [
{
message: "No foo!",
line: 1,
column: 0,
endLine: 1,
endColumn: 3,
},
],
},
{
code: "foo = 1;",
eslintCompat: false,
errors: [
{
message: "No foo!",
line: 1,
column: 0,
endLine: 1,
endColumn: 3,
},
],
},
{
code: "foo = 1;",
eslintCompat: true,
errors: [
{
message: "No foo!",
line: 1,
column: 1, // 1 not 0
endLine: 1,
endColumn: 4, // 4 not 3
},
],
},
],
});
expect(runCases()).toEqual([null, null, null]);
});
});
});
Loading