mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(prettier): check parens for (let)[a] = 1 (#1585)
This commit is contained in:
parent
e6681a8683
commit
1bd1c5b51b
7 changed files with 91 additions and 42 deletions
|
|
@ -1248,6 +1248,13 @@ impl<'a> ForStatementInit<'a> {
|
|||
pub fn is_lexical_declaration(&self) -> bool {
|
||||
matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical())
|
||||
}
|
||||
|
||||
pub fn expression(&self) -> Option<&Expression<'a>> {
|
||||
match self {
|
||||
Self::Expression(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For-In Statement
|
||||
|
|
|
|||
|
|
@ -28,7 +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 oxc_syntax::identifier::is_identifier_name;
|
||||
|
||||
use crate::{
|
||||
array,
|
||||
|
|
@ -95,8 +95,8 @@ impl<'a> Format<'a> for Directive {
|
|||
self.expression.value.as_str(),
|
||||
p.options.single_quote,
|
||||
)));
|
||||
if p.options.semi {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
parts.extend(hardline!());
|
||||
Doc::Array(parts)
|
||||
|
|
@ -135,8 +135,8 @@ impl<'a> Format<'a> for ExpressionStatement<'a> {
|
|||
wrap!(p, self, ExpressionStatement, {
|
||||
let mut parts = p.vec();
|
||||
parts.push(self.expression.format(p));
|
||||
if p.options.semi {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
Doc::Array(parts)
|
||||
})
|
||||
|
|
@ -363,8 +363,8 @@ impl<'a> Format<'a> for DoWhileStatement<'a> {
|
|||
parts.push(ss!("while ("));
|
||||
parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!()));
|
||||
parts.push(ss!(")"));
|
||||
if p.options.semi {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
|
||||
Doc::Array(parts)
|
||||
|
|
@ -651,7 +651,9 @@ impl<'a> Format<'a> for VariableDeclaration<'a> {
|
|||
}));
|
||||
|
||||
if !parent_for_loop.is_some_and(|span| span != self.span) {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
}
|
||||
|
||||
Doc::Group(Group::new(parts, false))
|
||||
|
|
@ -674,8 +676,8 @@ impl<'a> Format<'a> for TSTypeAliasDeclaration<'a> {
|
|||
parts.push(ss!(" = "));
|
||||
parts.push(format!(p, self.type_annotation));
|
||||
|
||||
if p.options.semi {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
|
||||
Doc::Array(parts)
|
||||
|
|
@ -1047,8 +1049,8 @@ impl<'a> Format<'a> for ImportDeclaration<'a> {
|
|||
}
|
||||
parts.push(ss!(" from "));
|
||||
parts.push(self.source.format(p));
|
||||
if p.options.semi {
|
||||
parts.push(ss!(";"));
|
||||
if let Some(semi) = p.semi() {
|
||||
parts.push(semi);
|
||||
}
|
||||
Doc::Array(parts)
|
||||
}
|
||||
|
|
@ -1223,7 +1225,7 @@ impl<'a> Format<'a> for Expression<'a> {
|
|||
|
||||
impl<'a> Format<'a> for IdentifierReference {
|
||||
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
||||
p.str(self.name.as_str())
|
||||
wrap!(p, self, IdentifierReference, { p.str(self.name.as_str()) })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1551,7 +1553,7 @@ impl<'a> Format<'a> for PropertyKey<'a> {
|
|||
let unquote = if need_quote {
|
||||
false
|
||||
} else {
|
||||
is_identify_name(literal.value.as_str())
|
||||
is_identifier_name(literal.value.as_str())
|
||||
};
|
||||
|
||||
if !unquote || p.options.quote_props.is_preserve() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use oxc_ast::ast::{Expression, PropertyKey};
|
||||
use oxc_syntax::identifier::is_identify_name;
|
||||
use oxc_syntax::identifier::is_identifier_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)
|
||||
!is_identifier_name(value) && !is_simple_number(value)
|
||||
}
|
||||
|
||||
// Matches “simple” numbers like `123` and `2.5` but not `1_000`, `1e+100` or `0b10`.
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ impl<'a> Prettier<'a> {
|
|||
unsafe { std::mem::transmute(t) }
|
||||
}
|
||||
|
||||
pub fn semi(&self) -> Option<Doc<'a>> {
|
||||
self.options.semi.then(|| Doc::Str(";"))
|
||||
}
|
||||
|
||||
pub fn should_print_es5_comma(&self) -> bool {
|
||||
self.should_print_comma_impl(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
use oxc_ast::{
|
||||
ast::{
|
||||
AssignmentTarget, AssignmentTargetPattern, ChainElement, ExportDefaultDeclarationKind,
|
||||
Expression, ModuleDeclaration, ObjectExpression, SimpleAssignmentTarget,
|
||||
Expression, ForStatementLeft, MemberExpression, ModuleDeclaration, ObjectExpression,
|
||||
SimpleAssignmentTarget,
|
||||
},
|
||||
AstKind,
|
||||
};
|
||||
|
|
@ -30,34 +31,23 @@ impl<'a> Prettier<'a> {
|
|||
}
|
||||
|
||||
fn need_parens(&mut self, kind: AstKind<'a>) -> bool {
|
||||
if matches!(kind, AstKind::Program(_)) {
|
||||
if matches!(kind, AstKind::Program(_)) || kind.is_statement() || kind.is_declaration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if kind.is_statement() || kind.is_declaration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let parent_kind = self.parent_kind();
|
||||
|
||||
if let AstKind::ObjectExpression(e) = kind {
|
||||
if self.check_object_expression(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.check_parent_kind(kind, parent_kind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.check_kind(kind, parent_kind) {
|
||||
if matches!(kind, AstKind::ObjectExpression(e) if self.check_object_expression(e))
|
||||
|| self.check_let_object(kind)
|
||||
|| self.check_parent_kind(kind)
|
||||
|| self.check_kind(kind)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn check_kind(&self, kind: AstKind<'a>, parent_kind: AstKind<'a>) -> bool {
|
||||
fn check_kind(&self, kind: AstKind<'a>) -> bool {
|
||||
let parent_kind = self.parent_kind();
|
||||
match kind {
|
||||
AstKind::NumberLiteral(literal) => {
|
||||
matches!(parent_kind, AstKind::MemberExpression(e) if e.object().span() == literal.span)
|
||||
|
|
@ -190,8 +180,8 @@ impl<'a> Prettier<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_parent_kind(&mut self, kind: AstKind<'a>, parent_kind: AstKind<'a>) -> bool {
|
||||
match parent_kind {
|
||||
fn check_parent_kind(&mut self, kind: AstKind<'a>) -> bool {
|
||||
match self.parent_kind() {
|
||||
AstKind::Class(class) => {
|
||||
if let Some(h) = &class.super_class {
|
||||
match kind {
|
||||
|
|
@ -263,6 +253,54 @@ impl<'a> Prettier<'a> {
|
|||
false
|
||||
}
|
||||
|
||||
/// `(let)[a] = 1`
|
||||
fn check_let_object(&self, kind: AstKind<'a>) -> bool {
|
||||
let AstKind::IdentifierReference(ident) = kind else { return false };
|
||||
if ident.name != "let" {
|
||||
return false;
|
||||
}
|
||||
let AstKind::MemberExpression(MemberExpression::ComputedMemberExpression(expr)) =
|
||||
self.parent_kind()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(&expr.object, Expression::Identifier(ident) if ident.name == "let") {
|
||||
return false;
|
||||
}
|
||||
let Some(statement) = self.nodes.iter().rev().find(|node| {
|
||||
matches!(
|
||||
node,
|
||||
AstKind::ExpressionStatement(_)
|
||||
| AstKind::ForStatement(_)
|
||||
| AstKind::ForInStatement(_)
|
||||
)
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
match statement {
|
||||
AstKind::ExpressionStatement(stmt) => {
|
||||
Self::starts_with_no_lookahead_token(&stmt.expression, ident.span)
|
||||
}
|
||||
AstKind::ForStatement(stmt) => stmt
|
||||
.init
|
||||
.as_ref()
|
||||
.and_then(|init| init.expression())
|
||||
.map_or(false, |e| Self::starts_with_no_lookahead_token(e, ident.span)),
|
||||
AstKind::ForInStatement(stmt) => {
|
||||
if let ForStatementLeft::AssignmentTarget(
|
||||
AssignmentTarget::SimpleAssignmentTarget(
|
||||
SimpleAssignmentTarget::MemberAssignmentTarget(e),
|
||||
),
|
||||
) = &stmt.left
|
||||
{
|
||||
return Self::starts_with_no_lookahead_token(e.object(), ident.span);
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_object_function_class(&self, span: Span) -> bool {
|
||||
for ast_kind in self.nodes.iter().rev() {
|
||||
if let AstKind::ExpressionStatement(e) = ast_kind {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ 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 {
|
||||
pub fn is_identifier_name(name: &str) -> bool {
|
||||
let mut chars = name.chars();
|
||||
chars.next().is_some_and(is_identifier_start_all) && chars.all(is_identifier_part)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Compatibility: 201/561 (35.83%)
|
||||
Compatibility: 203/561 (36.19%)
|
||||
|
||||
# Failed
|
||||
|
||||
|
|
@ -157,7 +157,6 @@ Compatibility: 201/561 (35.83%)
|
|||
* comments/jsdoc.js
|
||||
* comments/jsx.js
|
||||
* comments/last-arg.js
|
||||
* comments/multi-comments-2.js
|
||||
* comments/multi-comments-on-same-line-2.js
|
||||
* comments/multi-comments-on-same-line.js
|
||||
* comments/multi-comments.js
|
||||
|
|
@ -277,7 +276,6 @@ Compatibility: 201/561 (35.83%)
|
|||
* identifier/for-of/let.js
|
||||
|
||||
### identifier/parentheses
|
||||
* identifier/parentheses/const.js
|
||||
* identifier/parentheses/let.js
|
||||
|
||||
### if
|
||||
|
|
|
|||
Loading…
Reference in a new issue