mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(data_structures): add rope (#7764)
This commit is contained in:
parent
3d5f0a1a0c
commit
cf2ee06825
9 changed files with 57 additions and 62 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -1588,6 +1588,7 @@ name = "oxc_data_structures"
|
|||
version = "0.39.0"
|
||||
dependencies = [
|
||||
"assert-unchecked",
|
||||
"ropey",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1663,12 +1664,12 @@ dependencies = [
|
|||
"ignore",
|
||||
"log",
|
||||
"oxc_allocator",
|
||||
"oxc_data_structures",
|
||||
"oxc_diagnostics",
|
||||
"oxc_linter",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"ropey",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2081,7 +2082,6 @@ dependencies = [
|
|||
"oxc_syntax",
|
||||
"oxc_traverse",
|
||||
"pico-args",
|
||||
"ropey",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -22,3 +22,4 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
assert-unchecked = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
//! Data structures used across other oxc crates.
|
||||
#![warn(missing_docs)]
|
||||
pub mod rope;
|
||||
pub mod stack;
|
||||
|
|
|
|||
|
|
@ -1,96 +1,93 @@
|
|||
use ropey::Rope;
|
||||
//! Rope
|
||||
|
||||
/// Get line and column from offset and source text.
|
||||
///
|
||||
/// Line number starts at 1.
|
||||
/// Column number is in UTF-16 characters, and starts at 1.
|
||||
///
|
||||
/// This matches Babel's output.
|
||||
pub fn get_line_column(rope: &Rope, offset: u32, source_text: &str) -> (usize, usize) {
|
||||
pub use ropey::Rope;
|
||||
|
||||
/// Get UTF16 line and column from UTF8 offset and source text.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
pub fn get_line_column(rope: &Rope, offset: u32, source_text: &str) -> (u32, u32) {
|
||||
let offset = offset as usize;
|
||||
// Get line number and byte offset of start of line
|
||||
let line_index = rope.byte_to_line(offset);
|
||||
let line_offset = rope.line_to_byte(line_index);
|
||||
// Get column number
|
||||
let column_index = source_text[line_offset..offset].encode_utf16().count();
|
||||
// line and column are zero-indexed, but we want 1-indexed
|
||||
(line_index + 1, column_index + 1)
|
||||
(line_index as u32, column_index as u32)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use ropey::Rope;
|
||||
|
||||
fn test_line_column(offset: u32, source_text: &str) -> (usize, usize) {
|
||||
fn test_line_column(offset: u32, source_text: &str) -> (u32, u32) {
|
||||
let rope = Rope::from_str(source_text);
|
||||
super::get_line_column(&rope, offset, source_text)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_file() {
|
||||
assert_eq!(test_line_column(0, ""), (1, 1));
|
||||
assert_eq!(test_line_column(0, ""), (0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_line_start() {
|
||||
assert_eq!(test_line_column(0, "foo\nbar\n"), (1, 1));
|
||||
assert_eq!(test_line_column(0, "foo\nbar\n"), (0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_line_middle() {
|
||||
assert_eq!(test_line_column(5, "blahblahblah\noops\n"), (1, 6));
|
||||
assert_eq!(test_line_column(5, "blahblahblah\noops\n"), (0, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_line_start() {
|
||||
assert_eq!(test_line_column(8, "foo\nbar\nblahblahblah"), (3, 1));
|
||||
assert_eq!(test_line_column(8, "foo\nbar\nblahblahblah"), (2, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_line_middle() {
|
||||
assert_eq!(test_line_column(12, "foo\nbar\nblahblahblah"), (3, 5));
|
||||
assert_eq!(test_line_column(12, "foo\nbar\nblahblahblah"), (2, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_2_byte_unicode() {
|
||||
assert_eq!("£".len(), 2);
|
||||
assert_eq!(utf16_len("£"), 1);
|
||||
assert_eq!(test_line_column(4, "£abc"), (1, 4));
|
||||
assert_eq!(test_line_column(4, "£abc"), (0, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_3_byte_unicode() {
|
||||
assert_eq!("अ".len(), 3);
|
||||
assert_eq!(utf16_len("अ"), 1);
|
||||
assert_eq!(test_line_column(5, "अabc"), (1, 4));
|
||||
assert_eq!(test_line_column(5, "अabc"), (0, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_4_byte_unicode() {
|
||||
assert_eq!("🍄".len(), 4);
|
||||
assert_eq!(utf16_len("🍄"), 2);
|
||||
assert_eq!(test_line_column(6, "🍄abc"), (1, 5));
|
||||
assert_eq!(test_line_column(6, "🍄abc"), (0, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_2_byte_unicode_on_previous_line() {
|
||||
assert_eq!("£".len(), 2);
|
||||
assert_eq!(utf16_len("£"), 1);
|
||||
assert_eq!(test_line_column(4, "£\nabc"), (2, 2));
|
||||
assert_eq!(test_line_column(4, "£\nabc"), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_3_byte_unicode_on_previous_line() {
|
||||
assert_eq!("अ".len(), 3);
|
||||
assert_eq!(utf16_len("अ"), 1);
|
||||
assert_eq!(test_line_column(5, "अ\nabc"), (2, 2));
|
||||
assert_eq!(test_line_column(5, "अ\nabc"), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_4_byte_unicode_on_previous_line() {
|
||||
assert_eq!("🍄".len(), 4);
|
||||
assert_eq!(utf16_len("🍄"), 2);
|
||||
assert_eq!(test_line_column(6, "🍄\nabc"), (2, 2));
|
||||
assert_eq!(test_line_column(6, "🍄\nabc"), (1, 1));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -23,6 +23,7 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
oxc_allocator = { workspace = true }
|
||||
oxc_data_structures = { workspace = true }
|
||||
oxc_diagnostics = { workspace = true }
|
||||
oxc_linter = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
|
|
@ -36,7 +37,6 @@ futures = { workspace = true }
|
|||
globset = { workspace = true }
|
||||
ignore = { workspace = true, features = ["simd-accel"] }
|
||||
log = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use cow_utils::CowUtils;
|
||||
use oxc_linter::loader::LINT_PARTIAL_LOADER_EXT;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
|
|
@ -8,9 +6,18 @@ use std::{
|
|||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use cow_utils::CowUtils;
|
||||
use log::debug;
|
||||
use rustc_hash::FxHashSet;
|
||||
use tower_lsp::lsp_types::{
|
||||
self, CodeDescription, DiagnosticRelatedInformation, DiagnosticSeverity, NumberOrString,
|
||||
Position, Range, Url,
|
||||
};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_data_structures::rope::{get_line_column, Rope};
|
||||
use oxc_diagnostics::{Error, NamedSource, Severity};
|
||||
use oxc_linter::loader::LINT_PARTIAL_LOADER_EXT;
|
||||
use oxc_linter::{
|
||||
loader::{JavaScriptSource, Loader},
|
||||
FixKind, Linter, ModuleRecord,
|
||||
|
|
@ -18,12 +25,6 @@ use oxc_linter::{
|
|||
use oxc_parser::{ParseOptions, Parser};
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::VALID_EXTENSIONS;
|
||||
use ropey::Rope;
|
||||
use rustc_hash::FxHashSet;
|
||||
use tower_lsp::lsp_types::{
|
||||
self, CodeDescription, DiagnosticRelatedInformation, DiagnosticSeverity, NumberOrString,
|
||||
Position, Range, Url,
|
||||
};
|
||||
|
||||
const LINT_DOC_LINK_PREFIX: &str = "https://oxc.rs/docs/guide/usage/linter/rules";
|
||||
#[derive(Debug)]
|
||||
|
|
@ -53,13 +54,11 @@ impl ErrorWithPosition {
|
|||
let labels_with_pos: Vec<LabeledSpanWithPosition> = labels
|
||||
.iter()
|
||||
.map(|labeled_span| LabeledSpanWithPosition {
|
||||
start_pos: offset_to_position(labeled_span.offset() + start, text)
|
||||
.unwrap_or_default(),
|
||||
start_pos: offset_to_position(labeled_span.offset() + start, text),
|
||||
end_pos: offset_to_position(
|
||||
labeled_span.offset() + start + labeled_span.len(),
|
||||
text,
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
message: labeled_span.label().map(ToString::to_string),
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -304,13 +303,11 @@ impl IsolatedLintHandler {
|
|||
start: offset_to_position(
|
||||
(f.span.start + start) as usize,
|
||||
source_text.as_str(),
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
end: offset_to_position(
|
||||
(f.span.end + start) as usize,
|
||||
source_text.as_str(),
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -359,16 +356,11 @@ impl IsolatedLintHandler {
|
|||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn offset_to_position(offset: usize, source_text: &str) -> Option<Position> {
|
||||
fn offset_to_position(offset: usize, source_text: &str) -> Position {
|
||||
// TODO(perf): share a single instance of `Rope`
|
||||
let rope = Rope::from_str(source_text);
|
||||
// Get line number and byte offset of start of line
|
||||
let line_index = rope.try_byte_to_line(offset).ok()?;
|
||||
let line_offset = rope.try_line_to_byte(line_index).ok()?;
|
||||
|
||||
// Get column number
|
||||
let column_index = source_text[line_offset..offset].encode_utf16().count();
|
||||
|
||||
Some(Position::new(line_index as u32, column_index as u32))
|
||||
let (line, column) = get_line_column(&rope, offset as u32, source_text);
|
||||
Position::new(line, column)
|
||||
}
|
||||
|
||||
pub struct ServerLinter {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ dashmap = { workspace = true }
|
|||
indexmap = { workspace = true }
|
||||
itoa = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
//!
|
||||
//! * Babel plugin implementation: <https://github.com/babel/babel/blob/v7.26.2/packages/babel-plugin-transform-react-jsx-source/src/index.ts>
|
||||
|
||||
use ropey::Rope;
|
||||
use oxc_data_structures::rope::{get_line_column, Rope};
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
|
|
@ -43,8 +43,6 @@ use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx};
|
|||
|
||||
use crate::TransformCtx;
|
||||
|
||||
use super::utils::get_line_column;
|
||||
|
||||
const SOURCE: &str = "__source";
|
||||
const FILE_NAME_VAR: &str = "jsxFileName";
|
||||
|
||||
|
|
@ -77,16 +75,24 @@ impl<'a, 'ctx> Traverse<'a> for JsxSource<'a, 'ctx> {
|
|||
}
|
||||
|
||||
impl<'a, 'ctx> JsxSource<'a, 'ctx> {
|
||||
pub fn get_line_column(&mut self, offset: u32) -> (usize, usize) {
|
||||
/// Get line and column from offset and source text.
|
||||
///
|
||||
/// Line number starts at 1.
|
||||
/// Column number is in UTF-16 characters, and starts at 1.
|
||||
///
|
||||
/// This matches Babel's output.
|
||||
pub fn get_line_column(&mut self, offset: u32) -> (u32, u32) {
|
||||
let source_rope =
|
||||
self.source_rope.get_or_insert_with(|| Rope::from_str(self.ctx.source_text));
|
||||
get_line_column(source_rope, offset, self.ctx.source_text)
|
||||
let (line, column) = get_line_column(source_rope, offset, self.ctx.source_text);
|
||||
// line and column are zero-indexed, but we want 1-indexed
|
||||
(line + 1, column + 1)
|
||||
}
|
||||
|
||||
pub fn get_object_property_kind_for_jsx_plugin(
|
||||
&mut self,
|
||||
line: usize,
|
||||
column: usize,
|
||||
line: u32,
|
||||
column: u32,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> ObjectPropertyKind<'a> {
|
||||
let kind = PropertyKind::Init;
|
||||
|
|
@ -136,11 +142,11 @@ impl<'a, 'ctx> JsxSource<'a, 'ctx> {
|
|||
elem.attributes.push(attribute_item);
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
#[expect(clippy::cast_lossless)]
|
||||
pub fn get_source_object(
|
||||
&mut self,
|
||||
line: usize,
|
||||
column: usize,
|
||||
line: u32,
|
||||
column: u32,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let kind = PropertyKind::Init;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ mod jsx_self;
|
|||
mod jsx_source;
|
||||
mod options;
|
||||
mod refresh;
|
||||
mod utils;
|
||||
use refresh::ReactRefresh;
|
||||
|
||||
pub use display_name::ReactDisplayName;
|
||||
|
|
|
|||
Loading…
Reference in a new issue