mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +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 {
|
pub fn is_lexical_declaration(&self) -> bool {
|
||||||
matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical())
|
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
|
/// For-In Statement
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use std::borrow::Cow;
|
||||||
use oxc_allocator::{Box, Vec};
|
use oxc_allocator::{Box, Vec};
|
||||||
use oxc_ast::{ast::*, AstKind};
|
use oxc_ast::{ast::*, AstKind};
|
||||||
use oxc_span::GetSpan;
|
use oxc_span::GetSpan;
|
||||||
use oxc_syntax::identifier::is_identify_name;
|
use oxc_syntax::identifier::is_identifier_name;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
array,
|
array,
|
||||||
|
|
@ -95,8 +95,8 @@ impl<'a> Format<'a> for Directive {
|
||||||
self.expression.value.as_str(),
|
self.expression.value.as_str(),
|
||||||
p.options.single_quote,
|
p.options.single_quote,
|
||||||
)));
|
)));
|
||||||
if p.options.semi {
|
if let Some(semi) = p.semi() {
|
||||||
parts.push(ss!(";"));
|
parts.push(semi);
|
||||||
}
|
}
|
||||||
parts.extend(hardline!());
|
parts.extend(hardline!());
|
||||||
Doc::Array(parts)
|
Doc::Array(parts)
|
||||||
|
|
@ -135,8 +135,8 @@ impl<'a> Format<'a> for ExpressionStatement<'a> {
|
||||||
wrap!(p, self, ExpressionStatement, {
|
wrap!(p, self, ExpressionStatement, {
|
||||||
let mut parts = p.vec();
|
let mut parts = p.vec();
|
||||||
parts.push(self.expression.format(p));
|
parts.push(self.expression.format(p));
|
||||||
if p.options.semi {
|
if let Some(semi) = p.semi() {
|
||||||
parts.push(ss!(";"));
|
parts.push(semi);
|
||||||
}
|
}
|
||||||
Doc::Array(parts)
|
Doc::Array(parts)
|
||||||
})
|
})
|
||||||
|
|
@ -363,8 +363,8 @@ impl<'a> Format<'a> for DoWhileStatement<'a> {
|
||||||
parts.push(ss!("while ("));
|
parts.push(ss!("while ("));
|
||||||
parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!()));
|
parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!()));
|
||||||
parts.push(ss!(")"));
|
parts.push(ss!(")"));
|
||||||
if p.options.semi {
|
if let Some(semi) = p.semi() {
|
||||||
parts.push(ss!(";"));
|
parts.push(semi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Doc::Array(parts)
|
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) {
|
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))
|
Doc::Group(Group::new(parts, false))
|
||||||
|
|
@ -674,8 +676,8 @@ impl<'a> Format<'a> for TSTypeAliasDeclaration<'a> {
|
||||||
parts.push(ss!(" = "));
|
parts.push(ss!(" = "));
|
||||||
parts.push(format!(p, self.type_annotation));
|
parts.push(format!(p, self.type_annotation));
|
||||||
|
|
||||||
if p.options.semi {
|
if let Some(semi) = p.semi() {
|
||||||
parts.push(ss!(";"));
|
parts.push(semi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Doc::Array(parts)
|
Doc::Array(parts)
|
||||||
|
|
@ -1047,8 +1049,8 @@ impl<'a> Format<'a> for ImportDeclaration<'a> {
|
||||||
}
|
}
|
||||||
parts.push(ss!(" from "));
|
parts.push(ss!(" from "));
|
||||||
parts.push(self.source.format(p));
|
parts.push(self.source.format(p));
|
||||||
if p.options.semi {
|
if let Some(semi) = p.semi() {
|
||||||
parts.push(ss!(";"));
|
parts.push(semi);
|
||||||
}
|
}
|
||||||
Doc::Array(parts)
|
Doc::Array(parts)
|
||||||
}
|
}
|
||||||
|
|
@ -1223,7 +1225,7 @@ impl<'a> Format<'a> for Expression<'a> {
|
||||||
|
|
||||||
impl<'a> Format<'a> for IdentifierReference {
|
impl<'a> Format<'a> for IdentifierReference {
|
||||||
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
|
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 {
|
let unquote = if need_quote {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
is_identify_name(literal.value.as_str())
|
is_identifier_name(literal.value.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
if !unquote || p.options.quote_props.is_preserve() {
|
if !unquote || p.options.quote_props.is_preserve() {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use oxc_ast::ast::{Expression, PropertyKey};
|
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 {
|
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()))
|
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 {
|
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`.
|
// 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) }
|
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 {
|
pub fn should_print_es5_comma(&self) -> bool {
|
||||||
self.should_print_comma_impl(false)
|
self.should_print_comma_impl(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
use oxc_ast::{
|
use oxc_ast::{
|
||||||
ast::{
|
ast::{
|
||||||
AssignmentTarget, AssignmentTargetPattern, ChainElement, ExportDefaultDeclarationKind,
|
AssignmentTarget, AssignmentTargetPattern, ChainElement, ExportDefaultDeclarationKind,
|
||||||
Expression, ModuleDeclaration, ObjectExpression, SimpleAssignmentTarget,
|
Expression, ForStatementLeft, MemberExpression, ModuleDeclaration, ObjectExpression,
|
||||||
|
SimpleAssignmentTarget,
|
||||||
},
|
},
|
||||||
AstKind,
|
AstKind,
|
||||||
};
|
};
|
||||||
|
|
@ -30,34 +31,23 @@ impl<'a> Prettier<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn need_parens(&mut self, kind: AstKind<'a>) -> bool {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind.is_statement() || kind.is_declaration() {
|
if matches!(kind, AstKind::ObjectExpression(e) if self.check_object_expression(e))
|
||||||
return false;
|
|| self.check_let_object(kind)
|
||||||
}
|
|| self.check_parent_kind(kind)
|
||||||
|
|| self.check_kind(kind)
|
||||||
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) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
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 {
|
match kind {
|
||||||
AstKind::NumberLiteral(literal) => {
|
AstKind::NumberLiteral(literal) => {
|
||||||
matches!(parent_kind, AstKind::MemberExpression(e) if e.object().span() == literal.span)
|
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 {
|
fn check_parent_kind(&mut self, kind: AstKind<'a>) -> bool {
|
||||||
match parent_kind {
|
match self.parent_kind() {
|
||||||
AstKind::Class(class) => {
|
AstKind::Class(class) => {
|
||||||
if let Some(h) = &class.super_class {
|
if let Some(h) = &class.super_class {
|
||||||
match kind {
|
match kind {
|
||||||
|
|
@ -263,6 +253,54 @@ impl<'a> Prettier<'a> {
|
||||||
false
|
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 {
|
fn check_object_function_class(&self, span: Span) -> bool {
|
||||||
for ast_kind in self.nodes.iter().rev() {
|
for ast_kind in self.nodes.iter().rev() {
|
||||||
if let AstKind::ExpressionStatement(e) = ast_kind {
|
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
|
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();
|
let mut chars = name.chars();
|
||||||
chars.next().is_some_and(is_identifier_start_all) && chars.all(is_identifier_part)
|
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
|
# Failed
|
||||||
|
|
||||||
|
|
@ -157,7 +157,6 @@ Compatibility: 201/561 (35.83%)
|
||||||
* comments/jsdoc.js
|
* comments/jsdoc.js
|
||||||
* comments/jsx.js
|
* comments/jsx.js
|
||||||
* comments/last-arg.js
|
* comments/last-arg.js
|
||||||
* comments/multi-comments-2.js
|
|
||||||
* comments/multi-comments-on-same-line-2.js
|
* comments/multi-comments-on-same-line-2.js
|
||||||
* comments/multi-comments-on-same-line.js
|
* comments/multi-comments-on-same-line.js
|
||||||
* comments/multi-comments.js
|
* comments/multi-comments.js
|
||||||
|
|
@ -277,7 +276,6 @@ Compatibility: 201/561 (35.83%)
|
||||||
* identifier/for-of/let.js
|
* identifier/for-of/let.js
|
||||||
|
|
||||||
### identifier/parentheses
|
### identifier/parentheses
|
||||||
* identifier/parentheses/const.js
|
|
||||||
* identifier/parentheses/let.js
|
* identifier/parentheses/let.js
|
||||||
|
|
||||||
### if
|
### if
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue