diff --git a/crates/ruff_wasm/README.md b/crates/ruff_wasm/README.md index 9456b931d8..973f157a08 100644 --- a/crates/ruff_wasm/README.md +++ b/crates/ruff_wasm/README.md @@ -19,7 +19,7 @@ There are multiple versions for the different wasm-pack targets. See [here](http This example uses the wasm-pack web target and is known to work with Vite. ```ts -import init, { Workspace, type Diagnostic } from '@astral-sh/ruff-wasm-web'; +import init, { Workspace, type Diagnostic, PositionEncoding } from '@astral-sh/ruff-wasm-web'; const exampleDocument = `print('hello'); print("world")` @@ -42,7 +42,7 @@ const workspace = new Workspace({ 'F' ], }, -}); +}, PositionEncoding.UTF16); // Will contain 1 diagnostic code for E702: Multiple statements on one line const diagnostics: Diagnostic[] = workspace.check(exampleDocument); diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 11bde87b7a..8c18111d16 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -19,7 +19,7 @@ use ruff_python_formatter::{PyFormatContext, QuoteStyle, format_module_ast, pret use ruff_python_index::Indexer; use ruff_python_parser::{Mode, ParseOptions, Parsed, parse, parse_unchecked}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::{LineColumn, OneIndexed}; +use ruff_source_file::{OneIndexed, PositionEncoding as SourcePositionEncoding, SourceLocation}; use ruff_text_size::Ranged; use ruff_workspace::Settings; use ruff_workspace::configuration::Configuration; @@ -117,6 +117,7 @@ pub fn run() { #[wasm_bindgen] pub struct Workspace { settings: Settings, + position_encoding: SourcePositionEncoding, } #[wasm_bindgen] @@ -126,7 +127,7 @@ impl Workspace { } #[wasm_bindgen(constructor)] - pub fn new(options: JsValue) -> Result { + pub fn new(options: JsValue, position_encoding: PositionEncoding) -> Result { let options: Options = serde_wasm_bindgen::from_value(options).map_err(into_error)?; let configuration = Configuration::from_options(options, Some(Path::new(".")), Path::new(".")) @@ -135,7 +136,10 @@ impl Workspace { .into_settings(Path::new(".")) .map_err(into_error)?; - Ok(Workspace { settings }) + Ok(Workspace { + settings, + position_encoding: position_encoding.into(), + }) } #[wasm_bindgen(js_name = defaultSettings)] @@ -228,27 +232,34 @@ impl Workspace { let messages: Vec = diagnostics .into_iter() - .map(|msg| ExpandedMessage { - code: msg.secondary_code_or_id().to_string(), - message: msg.body().to_string(), - start_location: source_code - .line_column(msg.range().unwrap_or_default().start()) - .into(), - end_location: source_code - .line_column(msg.range().unwrap_or_default().end()) - .into(), - fix: msg.fix().map(|fix| ExpandedFix { - message: msg.first_help_text().map(ToString::to_string), - edits: fix - .edits() - .iter() - .map(|edit| ExpandedEdit { - location: source_code.line_column(edit.start()).into(), - end_location: source_code.line_column(edit.end()).into(), - content: edit.content().map(ToString::to_string), - }) - .collect(), - }), + .map(|msg| { + let range = msg.range().unwrap_or_default(); + ExpandedMessage { + code: msg.secondary_code_or_id().to_string(), + message: msg.body().to_string(), + start_location: source_code + .source_location(range.start(), self.position_encoding) + .into(), + end_location: source_code + .source_location(range.end(), self.position_encoding) + .into(), + fix: msg.fix().map(|fix| ExpandedFix { + message: msg.first_help_text().map(ToString::to_string), + edits: fix + .edits() + .iter() + .map(|edit| ExpandedEdit { + location: source_code + .source_location(edit.start(), self.position_encoding) + .into(), + end_location: source_code + .source_location(edit.end(), self.position_encoding) + .into(), + content: edit.content().map(ToString::to_string), + }) + .collect(), + }), + } }) .collect(); @@ -331,14 +342,37 @@ impl<'a> ParsedModule<'a> { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] pub struct Location { pub row: OneIndexed, + /// The character offset from the start of the line. + /// + /// The semantic of the offset depends on the [`PositionEncoding`] used when creating + /// the [`Workspace`]. pub column: OneIndexed, } -impl From for Location { - fn from(value: LineColumn) -> Self { +impl From for Location { + fn from(value: SourceLocation) -> Self { Self { row: value.line, - column: value.column, + column: value.character_offset, + } + } +} + +#[derive(Default, Copy, Clone)] +#[wasm_bindgen] +pub enum PositionEncoding { + #[default] + Utf8, + Utf16, + Utf32, +} + +impl From for SourcePositionEncoding { + fn from(value: PositionEncoding) -> Self { + match value { + PositionEncoding::Utf8 => Self::Utf8, + PositionEncoding::Utf16 => Self::Utf16, + PositionEncoding::Utf32 => Self::Utf32, } } } diff --git a/crates/ruff_wasm/tests/api.rs b/crates/ruff_wasm/tests/api.rs index 8dada44a5b..22a1c98c38 100644 --- a/crates/ruff_wasm/tests/api.rs +++ b/crates/ruff_wasm/tests/api.rs @@ -4,12 +4,15 @@ use wasm_bindgen_test::wasm_bindgen_test; use ruff_linter::registry::Rule; use ruff_source_file::OneIndexed; -use ruff_wasm::{ExpandedMessage, Location, Workspace}; +use ruff_wasm::{ExpandedMessage, Location, PositionEncoding, Workspace}; macro_rules! check { ($source:expr, $config:expr, $expected:expr) => {{ let config = js_sys::JSON::parse($config).unwrap(); - match Workspace::new(config).unwrap().check($source) { + match Workspace::new(config, PositionEncoding::Utf8) + .unwrap() + .check($source) + { Ok(output) => { let result: Vec = serde_wasm_bindgen::from_value(output).unwrap(); assert_eq!(result, $expected); diff --git a/playground/ruff/src/Editor/Editor.tsx b/playground/ruff/src/Editor/Editor.tsx index de085185a7..6aa707c533 100644 --- a/playground/ruff/src/Editor/Editor.tsx +++ b/playground/ruff/src/Editor/Editor.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; import { Panel, PanelGroup } from "react-resizable-panels"; -import { Diagnostic, Workspace } from "ruff_wasm"; +import { Diagnostic, Workspace, PositionEncoding } from "ruff_wasm"; import { ErrorMessage, Theme, @@ -173,7 +173,7 @@ export default function Editor({ try { const config = JSON.parse(settingsSource); - const workspace = new Workspace(config); + const workspace = new Workspace(config, PositionEncoding.Utf16); const diagnostics = workspace.check(pythonSource); let secondary: SecondaryPanelResult = null;