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
534 changes: 523 additions & 11 deletions apps/oxlint/conformance/snapshots/eslint.md

Large diffs are not rendered by default.

4,069 changes: 3,855 additions & 214 deletions apps/oxlint/conformance/snapshots/stylistic.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions apps/oxlint/src-js/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ export declare const enum Severity {
Warning = 'Warning',
Advice = 'Advice'
}
/**
* Apply fixes to source text and return the fixed code.
*
* - `source_text` is the original source code.
* - `fixes_json` is a JSON string containing `Vec<Vec<JsFix>>` — an array of fix groups,
* one group per diagnostic which provides fixes.
* Each inner array should have length of 1 at minimum.
*
* Each group's fixes are merged, then all merged fixes are applied to `source_text`.
*
* Fix ranges are converted from UTF-16 code units to UTF-8 bytes.
*/
export declare function applyFixes(sourceText: string, fixesJson: string): string | null

/**
* Get offset within a `Uint8Array` which is aligned on `BUFFER_ALIGN`.
*
Expand Down
3 changes: 2 additions & 1 deletion apps/oxlint/src-js/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,9 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { Severity, getBufferOffset, lint, parseRawSync, rawTransferSupported } = nativeBinding
const { Severity, applyFixes, getBufferOffset, lint, parseRawSync, rawTransferSupported } = nativeBinding
export { Severity }
export { applyFixes }
export { getBufferOffset }
export { lint }
export { parseRawSync }
Expand Down
36 changes: 35 additions & 1 deletion apps/oxlint/src-js/package/rule_tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { default as assert, AssertionError } from "node:assert";
import { join as pathJoin, isAbsolute as isAbsolutePath, dirname } from "node:path";
import util from "node:util";
import stableJsonStringify from "json-stable-stringify-without-jsonify";
import { applyFixes } from "../bindings.js";
import { ecmaFeaturesOverride, setEcmaVersion, ECMA_VERSION } from "../plugins/context.ts";
import { registerPlugin, registeredRules } from "../plugins/load.ts";
import { lintFileImpl, resetStateAfterError } from "../plugins/lint.ts";
Expand All @@ -20,6 +21,7 @@ import { diagnostics, replacePlaceholders, PLACEHOLDER_REGEX } from "../plugins/
import { parse } from "./parse.ts";

import type { RequireAtLeastOne } from "type-fest";
import type { FixReport } from "../plugins/fix.ts";
import type { Plugin, Rule } from "../plugins/load.ts";
import type { Options } from "../plugins/options.ts";
import type { DiagnosticData, Suggestion } from "../plugins/report.ts";
Expand Down Expand Up @@ -347,6 +349,7 @@ interface Diagnostic {
column: number;
endLine: number;
endColumn: number;
fixes: FixReport[] | null;
suggestions: Suggestion[] | null;
}

Expand Down Expand Up @@ -621,7 +624,37 @@ function assertInvalidTestCasePasses(test: InvalidTestCase, plugin: Plugin, conf
}
}

// TODO: Test output after fixes
// Test output after fixes
const fixGroups: FixReport[][] = [];
for (const diagnostic of diagnostics) {
if (diagnostic.fixes !== null) fixGroups.push(diagnostic.fixes);
}

const { code } = test;

let fixedCode;
if (fixGroups.length > 0) {
fixedCode = applyFixes(code, JSON.stringify(fixGroups));
if (fixedCode === null) throw new Error("Failed to apply fixes");
} else {
fixedCode = code;
}

if (Object.hasOwn(test, "output")) {
const expectedOutput = test.output;
if (expectedOutput === null) {
assert.strictEqual(fixedCode, code, "Expected no autofixes to be suggested");
} else {
assert.strictEqual(fixedCode, expectedOutput, "Output is incorrect");
assert.notStrictEqual(
code,
expectedOutput,
"Test property `output` matches `code`. If no autofix is expected, set output to `null`.",
);
}
} else {
assert.strictEqual(fixedCode, code, "The rule fixed the code. Please add `output` property.");
}
}

/**
Expand Down Expand Up @@ -1068,6 +1101,7 @@ function lint(test: TestCase, plugin: Plugin): Diagnostic[] {
column,
endLine,
endColumn,
fixes: diagnostic.fixes,
suggestions: null, // TODO
};
});
Expand Down
40 changes: 40 additions & 0 deletions apps/oxlint/src/js_plugins/fix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use napi_derive::napi;

use oxc_ast_visit::utf8_to_utf16::Utf8ToUtf16;
use oxc_diagnostics::OxcDiagnostic;
use oxc_linter::{Fixer, JsFix, Message, PossibleFixes, convert_and_merge_js_fixes};

/// Apply fixes to source text and return the fixed code.
///
/// - `source_text` is the original source code.
/// - `fixes_json` is a JSON string containing `Vec<Vec<JsFix>>` — an array of fix groups,
/// one group per diagnostic which provides fixes.
/// Each inner array should have length of 1 at minimum.
///
/// Each group's fixes are merged, then all merged fixes are applied to `source_text`.
///
/// Fix ranges are converted from UTF-16 code units to UTF-8 bytes.
#[napi]
#[allow(dead_code, clippy::needless_pass_by_value, clippy::allow_attributes)]
pub fn apply_fixes(source_text: String, fixes_json: String) -> Option<String> {
// Deserialize fixes JSON
let fix_groups: Vec<Vec<JsFix>> = serde_json::from_str(&fixes_json).ok()?;

// Create `Utf8ToUtf16` converter
let span_converter = Utf8ToUtf16::new(&source_text);

// Merge fix groups into a single fix per group
let messages = fix_groups
.into_iter()
.map(|group| {
convert_and_merge_js_fixes(group, &source_text, &span_converter)
.ok()
.map(|fix| Message::new(OxcDiagnostic::error(""), PossibleFixes::Single(fix)))
})
.collect::<Option<Vec<_>>>()?;

// Apply all the fixes
let fixed_code = Fixer::new(&source_text, messages, None).fix().fixed_code.into_owned();

Some(fixed_code)
}
3 changes: 3 additions & 0 deletions apps/oxlint/src/js_plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod external_linter;

#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
pub mod fix;

#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
pub mod parse;

Expand Down
Loading
Loading