Skip to content

Commit

Permalink
feat(data_structures): add rope
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Dec 10, 2024
1 parent a222f2b commit 52a4180
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 63 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/oxc_data_structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ doctest = false

[dependencies]
assert-unchecked = { workspace = true }
ropey = { workspace = true }
1 change: 1 addition & 0 deletions crates/oxc_data_structures/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Data structures used across other oxc crates.
#![warn(missing_docs)]
pub mod rope;
pub mod stack;
Original file line number Diff line number Diff line change
@@ -1,96 +1,93 @@
use ropey::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) {
//! Rope
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)]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_language_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 }
Expand Down
42 changes: 17 additions & 25 deletions crates/oxc_language_server/src/linter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use cow_utils::CowUtils;
use oxc_linter::loader::LINT_PARTIAL_LOADER_EXT;
use std::{
fs,
path::{Path, PathBuf},
Expand All @@ -8,22 +6,25 @@ 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,
};
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)]
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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(),
),
},
});

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion crates/oxc_transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
26 changes: 16 additions & 10 deletions crates/oxc_transformer/src/jsx/jsx_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion crates/oxc_transformer/src/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ mod jsx_self;
mod jsx_source;
mod options;
mod refresh;
mod utils;
use refresh::ReactRefresh;

pub use display_name::ReactDisplayName;
Expand Down

0 comments on commit 52a4180

Please sign in to comment.