Skip to content

Commit

Permalink
Merge remote-tracking branch 'oxc/main' into refactor-language-server…
Browse files Browse the repository at this point in the history
…-move-struct-into-own-files
  • Loading branch information
Sysix committed Dec 19, 2024
2 parents 4e9cdff + 6344b93 commit 73dbbf6
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 37 deletions.
1 change: 1 addition & 0 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_allocator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ doctest = false
[dependencies]
allocator-api2 = { workspace = true }
bumpalo = { workspace = true, features = ["allocator-api2", "collections"] }
simdutf8 = { workspace = true }

serde = { workspace = true, optional = true }

Expand Down
35 changes: 34 additions & 1 deletion crates/oxc_allocator/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use allocator_api2::vec;
use bumpalo::Bump;
#[cfg(any(feature = "serialize", test))]
use serde::{ser::SerializeSeq, Serialize, Serializer};
use simdutf8::basic::{from_utf8, Utf8Error};

use crate::{Allocator, Box};
use crate::{Allocator, Box, String};

/// A `Vec` without [`Drop`], which stores its data in the arena allocator.
///
Expand Down Expand Up @@ -180,6 +181,38 @@ impl<'alloc, T> Vec<'alloc, T> {
}
}

impl<'alloc> Vec<'alloc, u8> {
/// Convert `Vec<u8>` into `String`.
///
/// # Errors
/// Returns [`Err`] if the `Vec` does not comprise a valid UTF-8 string.
pub fn into_string(self) -> Result<String<'alloc>, Utf8Error> {
// Check vec comprises a valid UTF-8 string.
from_utf8(&self.0)?;
// SAFETY: We just checked it's a valid UTF-8 string
let s = unsafe { self.into_string_unchecked() };
Ok(s)
}

/// Convert `Vec<u8>` into [`String`], without checking bytes comprise a valid UTF-8 string.
///
/// Does not copy the contents of the `Vec`, converts in place. This is a zero-cost operation.
///
/// # SAFETY
/// Caller must ensure this `Vec<u8>` comprises a valid UTF-8 string.
#[expect(clippy::missing_safety_doc, clippy::unnecessary_safety_comment)]
#[inline] // `#[inline]` because this is a no-op at runtime
pub unsafe fn into_string_unchecked(self) -> String<'alloc> {
// Cannot use `bumpalo::String::from_utf8_unchecked` because it takes a `bumpalo::collections::Vec`,
// and our inner `Vec` type is `allocator_api2::vec::Vec`.
// SAFETY: Conversion is safe because both types store data in arena in same way.
// Lifetime of returned `String` is same as lifetime of original `Vec<u8>`.
let inner = ManuallyDrop::into_inner(self.0);
let (ptr, len, cap, bump) = inner.into_raw_parts_with_alloc();
String::from_raw_parts_in(ptr, len, cap, bump)
}
}

impl<'alloc, T> ops::Deref for Vec<'alloc, T> {
type Target = vec::Vec<T, &'alloc Bump>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ impl IsolatedLintHandler {
return None;
}
let source_text = source_text.or_else(|| fs::read_to_string(path).ok())?;

let javascript_sources = match self.loader.load_str(path, &source_text) {
Ok(s) => s,
Err(e) => {
Expand Down
86 changes: 51 additions & 35 deletions crates/oxc_transformer/src/jsx/refresh.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::hash_map::Entry;

use base64::prelude::{Engine, BASE64_STANDARD};
use base64::{
encoded_len as base64_encoded_len,
prelude::{Engine, BASE64_STANDARD},
};
use rustc_hash::FxHashMap;
use sha1::{Digest, Sha1};

Expand Down Expand Up @@ -372,38 +375,42 @@ impl<'a, 'ctx> Traverse<'a> for ReactRefresh<'a, 'ctx> {
};

let args = &call_expr.arguments;
let args_key = if hook_name == "useState" && args.len() > 0 {
args[0].span().source_text(self.ctx.source_text)
let (args_key, mut key_len) = if hook_name == "useState" && !args.is_empty() {
let args_key = args[0].span().source_text(self.ctx.source_text);
(args_key, args_key.len() + 4)
} else if hook_name == "useReducer" && args.len() > 1 {
args[1].span().source_text(self.ctx.source_text)
let args_key = args[1].span().source_text(self.ctx.source_text);
(args_key, args_key.len() + 4)
} else {
""
("", 2)
};

let is_empty = args_key.is_empty();
// `hook_name{{declarator_id(args_key)}}` or `hook_name{{declarator_id}}`
let push_key_to_string = |string: &mut String| {
string.push_str(hook_name.as_str());
string.push('{');
string.push_str(declarator_id);
string.push_str(if is_empty { "" } else { "(" });
string.push_str(args_key);
string.push_str(if is_empty { "" } else { ")" });
string.push('}');
};
key_len += hook_name.len() + declarator_id.len();

match self.function_signature_keys.entry(current_scope_id) {
Entry::Occupied(mut entry) => {
let string = entry.get_mut();
let string = match self.function_signature_keys.entry(current_scope_id) {
Entry::Occupied(entry) => {
let string = entry.into_mut();
string.reserve(key_len + 2);
string.push_str("\\n");
push_key_to_string(string);
}
Entry::Vacant(entry) => {
let mut string = String::new();
push_key_to_string(&mut string);
entry.insert(string);
string
}
Entry::Vacant(entry) => entry.insert(String::with_capacity(key_len)),
};

// `hook_name{{declarator_id(args_key)}}` or `hook_name{{declarator_id}}`
let old_len = string.len();

string.push_str(&hook_name);
string.push('{');
string.push_str(declarator_id);
if !args_key.is_empty() {
string.push('(');
string.push_str(args_key);
string.push(')');
}
string.push('}');

debug_assert_eq!(key_len, string.len() - old_len);
}
}

Expand Down Expand Up @@ -528,18 +535,31 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> {
body: &mut FunctionBody<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<(BindingIdentifier<'a>, ArenaVec<'a, Argument<'a>>)> {
let mut key = self.function_signature_keys.remove(&scope_id)?;
let key = self.function_signature_keys.remove(&scope_id)?;

if !self.emit_full_signatures {
let key = if self.emit_full_signatures {
ctx.ast.atom(&key)
} else {
// Prefer to hash when we can (e.g. outside of ASTExplorer).
// This makes it deterministically compact, even if there's
// e.g. a useState initializer with some code inside.
// We also need it for www that has transforms like cx()
// that don't understand if something is part of a string.
const SHA1_HASH_LEN: usize = 20;
const ENCODED_LEN: usize = base64_encoded_len(SHA1_HASH_LEN, true).unwrap();

let mut hasher = Sha1::new();
hasher.update(key);
key = BASE64_STANDARD.encode(hasher.finalize());
}
hasher.update(&key);
let hash = hasher.finalize();
debug_assert_eq!(hash.len(), SHA1_HASH_LEN);

// Encode to base64 string directly in arena, without an intermediate string allocation
let mut hashed_key = ArenaVec::from_array_in([0; ENCODED_LEN], ctx.ast.allocator);
let encoded_bytes = BASE64_STANDARD.encode_slice(hash, &mut hashed_key).unwrap();
debug_assert_eq!(encoded_bytes, ENCODED_LEN);
let hashed_key = hashed_key.into_string().unwrap();
Atom::from(hashed_key)
};

let callee_list = self.non_builtin_hooks_callee.remove(&scope_id).unwrap_or_default();
let callee_len = callee_list.len();
Expand All @@ -550,11 +570,7 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> {
let force_reset = custom_hooks_in_scope.len() != callee_len;

let mut arguments = ctx.ast.vec();
arguments.push(Argument::from(ctx.ast.expression_string_literal(
SPAN,
ctx.ast.atom(&key),
None,
)));
arguments.push(Argument::from(ctx.ast.expression_string_literal(SPAN, key, None)));

if force_reset || !custom_hooks_in_scope.is_empty() {
arguments.push(Argument::from(ctx.ast.expression_boolean_literal(SPAN, force_reset)));
Expand Down

0 comments on commit 73dbbf6

Please sign in to comment.