Skip to content

Commit 00fda91

Browse files
committed
fix(minifier): fix KATAKANA MIDDLE DOT syntax error for unicode 4.1 to 15 (#12829)
fixes rolldown/rolldown#5591
1 parent c2d3464 commit 00fda91

File tree

3 files changed

+54
-31
lines changed

3 files changed

+54
-31
lines changed

crates/oxc_minifier/src/ctx.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use oxc_ecmascript::{
99
};
1010
use oxc_semantic::{IsGlobalReference, Scoping, SymbolId};
1111
use oxc_span::format_atom;
12-
use oxc_syntax::reference::ReferenceId;
12+
use oxc_syntax::{
13+
identifier::{is_identifier_part, is_identifier_start},
14+
reference::ReferenceId,
15+
};
1316

1417
use crate::{options::CompressOptions, state::MinifierState, symbol_value::SymbolValue};
1518

@@ -241,4 +244,14 @@ impl<'a> Ctx<'a, '_> {
241244
}
242245
Some(f64::from(int_value))
243246
}
247+
248+
/// `is_identifier_name` patched with KATAKANA MIDDLE DOT and HALFWIDTH KATAKANA MIDDLE DOT
249+
/// Otherwise `({ 'x・': 0 })` gets converted to `({ x・: 0 })`, which breaks in Unicode 4.1 to
250+
/// 15.
251+
/// <https://github.com/oxc-project/unicode-id-start/pull/3>
252+
pub fn is_identifier_name_patched(s: &str) -> bool {
253+
let mut chars = s.chars();
254+
chars.next().is_some_and(is_identifier_start)
255+
&& chars.all(|c| is_identifier_part(c) && c != '・' && c != '・')
256+
}
244257
}

crates/oxc_minifier/src/peephole/convert_to_dotted_properties.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use oxc_allocator::TakeIn;
22
use oxc_ast::ast::*;
3-
use oxc_syntax::identifier::is_identifier_name;
43

54
use crate::ctx::Ctx;
65

@@ -17,7 +16,7 @@ impl<'a> LatePeepholeOptimizations {
1716
pub fn convert_to_dotted_properties(expr: &mut MemberExpression<'a>, ctx: &mut Ctx<'a, '_>) {
1817
let MemberExpression::ComputedMemberExpression(e) = expr else { return };
1918
let Expression::StringLiteral(s) = &e.expression else { return };
20-
if is_identifier_name(&s.value) {
19+
if Ctx::is_identifier_name_patched(&s.value) {
2120
let property = ctx.ast.identifier_name(s.span, s.value);
2221
*expr =
2322
MemberExpression::StaticMemberExpression(ctx.ast.alloc_static_member_expression(

crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use oxc_span::GetSpan;
88
use oxc_span::SPAN;
99
use oxc_syntax::{
1010
es_target::ESTarget,
11-
identifier::is_identifier_name,
1211
number::NumberBase,
1312
operator::{BinaryOperator, UnaryOperator},
1413
};
@@ -877,35 +876,40 @@ impl<'a> PeepholeOptimizations {
877876
computed: &mut bool,
878877
ctx: &mut Ctx<'a, '_>,
879878
) {
880-
if let PropertyKey::NumericLiteral(_) = key {
881-
if *computed {
882-
*computed = false;
879+
match key {
880+
PropertyKey::NumericLiteral(_) => {
881+
if *computed {
882+
*computed = false;
883+
}
883884
}
884-
return;
885-
}
886-
let PropertyKey::StringLiteral(s) = key else { return };
887-
let value = s.value.as_str();
888-
if is_identifier_name(value) {
889-
*computed = false;
890-
*key = PropertyKey::StaticIdentifier(ctx.ast.alloc_identifier_name(s.span, s.value));
891-
ctx.state.changed = true;
892-
return;
893-
}
894-
if let Some(value) = Ctx::string_to_equivalent_number_value(value) {
895-
if value >= 0.0 {
896-
*computed = false;
897-
*key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal(
898-
s.span,
899-
value,
900-
None,
901-
NumberBase::Decimal,
902-
));
903-
ctx.state.changed = true;
904-
return;
885+
PropertyKey::StringLiteral(s) => {
886+
let value = s.value.as_str();
887+
if Ctx::is_identifier_name_patched(value) {
888+
*computed = false;
889+
*key = PropertyKey::StaticIdentifier(
890+
ctx.ast.alloc_identifier_name(s.span, s.value),
891+
);
892+
ctx.state.changed = true;
893+
return;
894+
}
895+
if let Some(value) = Ctx::string_to_equivalent_number_value(value) {
896+
if value >= 0.0 {
897+
*computed = false;
898+
*key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal(
899+
s.span,
900+
value,
901+
None,
902+
NumberBase::Decimal,
903+
));
904+
ctx.state.changed = true;
905+
return;
906+
}
907+
}
908+
if *computed {
909+
*computed = false;
910+
}
905911
}
906-
}
907-
if *computed {
908-
*computed = false;
912+
_ => {}
909913
}
910914
}
911915

@@ -1744,6 +1748,13 @@ mod test {
17441748
"class C { static accessor __proto__ = 0 }",
17451749
);
17461750

1751+
// Patch KATAKANA MIDDLE DOT and HALFWIDTH KATAKANA MIDDLE DOT
1752+
// <https://github.com/oxc-project/unicode-id-start/pull/3>
1753+
test_same("x = { 'x・': 0 };");
1754+
test_same("x = { 'x・': 0 };");
1755+
test_same("x = y['x・'];");
1756+
test_same("x = y['x・'];");
1757+
17471758
// <https://tc39.es/ecma262/2024/multipage/ecmascript-language-functions-and-classes.html#sec-static-semantics-classelementkind>
17481759
// <https://tc39.es/ecma262/2024/multipage/ecmascript-language-functions-and-classes.html#sec-class-definitions-static-semantics-early-errors>
17491760
// <https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-class-definitions-static-semantics-early-errors>

0 commit comments

Comments
 (0)