mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(prettier): support quoteProps option in PropertyKey (#1578)
This commit is contained in:
parent
93d5b0f879
commit
f19032e102
5 changed files with 101 additions and 5 deletions
|
|
@ -17,6 +17,7 @@ mod function_parameters;
|
|||
mod misc;
|
||||
mod module;
|
||||
mod object;
|
||||
mod property;
|
||||
mod statement;
|
||||
mod string;
|
||||
mod template_literal;
|
||||
|
|
@ -27,6 +28,7 @@ use std::borrow::Cow;
|
|||
use oxc_allocator::{Box, Vec};
|
||||
use oxc_ast::{ast::*, AstKind};
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::identifier::is_identify_name;
|
||||
|
||||
use crate::{
|
||||
array,
|
||||
|
|
@ -1515,14 +1517,50 @@ impl<'a> Format<'a> for ObjectProperty<'a> {
|
|||
impl<'a> Format<'a> for PropertyKey<'a> {
|
||||
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
||||
wrap!(p, self, PropertyKey, {
|
||||
// Perf: Cache the result of `need_quote` to avoid checking it in each PropertyKey
|
||||
let need_quote = p.options.quote_props.is_consistent()
|
||||
&& match p.parent_parent_kind() {
|
||||
Some(AstKind::ObjectExpression(a)) => a.properties.iter().any(|x| match x {
|
||||
ObjectPropertyKind::ObjectProperty(p) => {
|
||||
property::is_property_key_has_quote(&p.key)
|
||||
}
|
||||
ObjectPropertyKind::SpreadProperty(_) => false,
|
||||
}),
|
||||
Some(AstKind::ClassBody(a)) => a.body.iter().any(|x| match x {
|
||||
ClassElement::PropertyDefinition(p) => {
|
||||
property::is_property_key_has_quote(&p.key)
|
||||
}
|
||||
_ => false,
|
||||
}),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match self {
|
||||
PropertyKey::Identifier(ident) => ident.format(p),
|
||||
PropertyKey::Identifier(ident) => {
|
||||
if need_quote {
|
||||
Doc::Str(string::print_string(p, &ident.name, p.options.single_quote))
|
||||
} else {
|
||||
ident.format(p)
|
||||
}
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(ident) => ident.format(p),
|
||||
PropertyKey::Expression(expr) => match expr {
|
||||
Expression::StringLiteral(literal) => {
|
||||
let value = literal.value.as_bytes();
|
||||
if !&value[0].is_ascii_digit() && !value.contains(&b'_') {
|
||||
p.str(&literal.value)
|
||||
let unquote = if need_quote {
|
||||
false
|
||||
} else {
|
||||
is_identify_name(literal.value.as_str())
|
||||
};
|
||||
|
||||
if !unquote || p.options.quote_props.is_preserve() {
|
||||
literal.format(p)
|
||||
} else {
|
||||
p.str(literal.value.as_str())
|
||||
}
|
||||
}
|
||||
Expression::NumberLiteral(literal) => {
|
||||
if need_quote {
|
||||
Doc::Str(string::print_string(p, literal.raw, p.options.single_quote))
|
||||
} else {
|
||||
literal.format(p)
|
||||
}
|
||||
|
|
|
|||
27
crates/oxc_prettier/src/format/property.rs
Normal file
27
crates/oxc_prettier/src/format/property.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use oxc_ast::ast::{Expression, PropertyKey};
|
||||
use oxc_syntax::identifier::is_identify_name;
|
||||
|
||||
pub(super) fn is_property_key_has_quote(key: &PropertyKey<'_>) -> bool {
|
||||
matches!(key, PropertyKey::Expression(Expression::StringLiteral(literal)) if is_string_prop_safe_to_unquote(literal.value.as_str()))
|
||||
}
|
||||
|
||||
pub(super) fn is_string_prop_safe_to_unquote(value: &str) -> bool {
|
||||
!is_identify_name(value) && !is_simple_number(value)
|
||||
}
|
||||
|
||||
// Matches “simple” numbers like `123` and `2.5` but not `1_000`, `1e+100` or `0b10`.
|
||||
pub(super) fn is_simple_number(str: &str) -> bool {
|
||||
let mut bytes = str.as_bytes().iter();
|
||||
let mut has_dot = false;
|
||||
bytes.next().is_some_and(u8::is_ascii_digit)
|
||||
&& bytes.all(|c| {
|
||||
if c == &b'.' {
|
||||
if has_dot {
|
||||
return false;
|
||||
}
|
||||
has_dot = true;
|
||||
return true;
|
||||
}
|
||||
c.is_ascii_digit()
|
||||
})
|
||||
}
|
||||
|
|
@ -124,6 +124,28 @@ pub enum QuoteProps {
|
|||
Preserve,
|
||||
}
|
||||
|
||||
impl QuoteProps {
|
||||
pub fn is_preserve(self) -> bool {
|
||||
matches!(self, Self::Preserve)
|
||||
}
|
||||
pub fn is_consistent(self) -> bool {
|
||||
matches!(self, Self::Consistent)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for QuoteProps {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"as_needed" => Self::AsNeeded,
|
||||
"consistent" => Self::Consistent,
|
||||
"preserve" => Self::Preserve,
|
||||
_ => Self::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum TrailingComma {
|
||||
/// Trailing commas wherever possible (including function parameters and calls).
|
||||
|
|
|
|||
|
|
@ -108,3 +108,8 @@ pub fn is_identifier_part(c: char) -> bool {
|
|||
}
|
||||
is_id_continue_unicode(c) || c == ZWNJ || c == ZWJ
|
||||
}
|
||||
|
||||
pub fn is_identify_name(name: &str) -> bool {
|
||||
let mut chars = name.chars();
|
||||
chars.next().is_some_and(is_identifier_start_all) && chars.all(is_identifier_part)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use oxc_ast::{
|
|||
VisitMut,
|
||||
};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_prettier::{EndOfLine, PrettierOptions, TrailingComma};
|
||||
use oxc_prettier::{EndOfLine, PrettierOptions, QuoteProps, TrailingComma};
|
||||
use oxc_span::{Atom, GetSpan, SourceType};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -89,6 +89,10 @@ impl VisitMut<'_> for SpecParser {
|
|||
options.end_of_line =
|
||||
EndOfLine::from_str(literal.value.as_str()).unwrap();
|
||||
}
|
||||
"quoteProps" => {
|
||||
options.quote_props =
|
||||
QuoteProps::from_str(literal.value.as_str()).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue