Fix non‑BMP code point handling in quick‑fixes and markers (#20526)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Dan Parizher 2025-09-24 04:08:00 -04:00 committed by GitHub
parent 09f570af92
commit 3e1e02e9b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 70 additions and 33 deletions

View file

@ -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);

View file

@ -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<Workspace, Error> {
pub fn new(options: JsValue, position_encoding: PositionEncoding) -> Result<Workspace, Error> {
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,14 +232,16 @@ impl Workspace {
let messages: Vec<ExpandedMessage> = diagnostics
.into_iter()
.map(|msg| ExpandedMessage {
.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
.line_column(msg.range().unwrap_or_default().start())
.source_location(range.start(), self.position_encoding)
.into(),
end_location: source_code
.line_column(msg.range().unwrap_or_default().end())
.source_location(range.end(), self.position_encoding)
.into(),
fix: msg.fix().map(|fix| ExpandedFix {
message: msg.first_help_text().map(ToString::to_string),
@ -243,12 +249,17 @@ impl Workspace {
.edits()
.iter()
.map(|edit| ExpandedEdit {
location: source_code.line_column(edit.start()).into(),
end_location: source_code.line_column(edit.end()).into(),
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<LineColumn> for Location {
fn from(value: LineColumn) -> Self {
impl From<SourceLocation> 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<PositionEncoding> for SourcePositionEncoding {
fn from(value: PositionEncoding) -> Self {
match value {
PositionEncoding::Utf8 => Self::Utf8,
PositionEncoding::Utf16 => Self::Utf16,
PositionEncoding::Utf32 => Self::Utf32,
}
}
}

View file

@ -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<ExpandedMessage> = serde_wasm_bindgen::from_value(output).unwrap();
assert_eq!(result, $expected);

View file

@ -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;