From c1626f59b2ed648cd638657830f1980aa70e0e74 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Fri, 17 Nov 2023 17:02:41 +0100 Subject: [PATCH] challenge(formatter): edge case wit hexpression statement and type cast --- .../src/ts/expressions/as_expression.rs | 37 +++- .../typescript/as/expression-statement.ts | 4 +- .../as/expression-statement.ts.snap | 50 ----- .../expression-statement.ts.snap | 180 ------------------ 4 files changed, 34 insertions(+), 237 deletions(-) delete mode 100644 crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts.snap delete mode 100644 crates/biome_js_formatter/tests/specs/prettier/typescript/satisfies-operators/expression-statement.ts.snap diff --git a/crates/biome_js_formatter/src/ts/expressions/as_expression.rs b/crates/biome_js_formatter/src/ts/expressions/as_expression.rs index 84fd847a0df2..8e8d79aad1d0 100644 --- a/crates/biome_js_formatter/src/ts/expressions/as_expression.rs +++ b/crates/biome_js_formatter/src/ts/expressions/as_expression.rs @@ -7,7 +7,7 @@ use crate::ts::expressions::type_assertion_expression::type_cast_like_needs_pare use biome_formatter::{format_args, write}; use biome_js_syntax::{AnyJsExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, TsAsExpression}; use biome_js_syntax::{AnyTsType, TsSatisfiesExpression}; -use biome_rowan::{declare_node_union, SyntaxResult}; +use biome_rowan::{declare_node_union, SyntaxNodeOptionExt, SyntaxResult}; #[derive(Debug, Clone, Default)] pub struct FormatTsAsExpression; @@ -62,9 +62,38 @@ impl Format for TsAsOrSatisfiesExpression { let expression = self.expression(); let operation_token = self.operation_token()?; let ty = self.ty()?; - + // edge case: `(type) as T;` + // This check could be implemented in `JsIdentifierExpression::needs_parentheses_with_parent`, + // however this could incurs a perf penality. + let expression_needs_parens = self + .syntax() + .ancestors() + .skip(1) + .find(|x| !TsAsOrSatisfiesExpression::can_cast(x.kind())) + .kind() + == Some(JsSyntaxKind::JS_EXPRESSION_STATEMENT) + && expression + .as_ref() + .ok() + .and_then(|expr| expr.as_js_reference_identifier()?.value_token().ok()) + .map_or(false, |name| { + // These keywords are contextually reserved by TypeSCript in strict and sloppy modes. + matches!( + name.text_trimmed(), + "await" | "interface" | "let" | "module" | "type" | "yield" | "using" + ) + }); let format_inner = format_with(|f| { - write!(f, [expression.format(), space(), operation_token.format()])?; + write!( + f, + [ + expression_needs_parens.then_some(text("(")), + expression.format(), + expression_needs_parens.then_some(text(")")), + space(), + operation_token.format() + ] + )?; if f.comments().has_leading_own_line_comment(ty.syntax()) { write!(f, [indent(&format_args![hard_line_break(), &ty.format()])]) @@ -104,7 +133,7 @@ impl NeedsParentheses for TsAsOrSatisfiesExpression { mod tests { use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; - use biome_js_syntax::{JsFileSource, TsAsExpression}; + use biome_js_syntax::{JsFileSource, JsIdentifierExpression, TsAsExpression}; #[test] fn needs_parentheses() { diff --git a/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts b/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts index 6799787a850b..abdb2ed74da4 100644 --- a/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts +++ b/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts @@ -1,6 +1,4 @@ // expression statemnt of "as" expression hardly ever makes sense, but it's still valid. const [type, x] = [0, 0]; -// FIXME -// TODO: parse issue -// (type) as unknown; +(type) as unknown; x as unknown; diff --git a/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts.snap b/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts.snap deleted file mode 100644 index bffa92e318a8..000000000000 --- a/crates/biome_js_formatter/tests/specs/prettier/typescript/as/expression-statement.ts.snap +++ /dev/null @@ -1,50 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: typescript/as/expression-statement.ts ---- - -# Input - -```ts -// expression statemnt of "as" expression hardly ever makes sense, but it's still valid. -const [type, x] = [0, 0]; -// FIXME -// TODO: parse issue -// (type) as unknown; -x as unknown; - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -1,4 +1,6 @@ - // expression statemnt of "as" expression hardly ever makes sense, but it's still valid. - const [type, x] = [0, 0]; --(type) as unknown; -+// FIXME -+// TODO: parse issue -+// (type) as unknown; - x as unknown; -``` - -# Output - -```ts -// expression statemnt of "as" expression hardly ever makes sense, but it's still valid. -const [type, x] = [0, 0]; -// FIXME -// TODO: parse issue -// (type) as unknown; -x as unknown; -``` - -# Lines exceeding max width of 80 characters -``` - 1: // expression statemnt of "as" expression hardly ever makes sense, but it's still valid. -``` - - diff --git a/crates/biome_js_formatter/tests/specs/prettier/typescript/satisfies-operators/expression-statement.ts.snap b/crates/biome_js_formatter/tests/specs/prettier/typescript/satisfies-operators/expression-statement.ts.snap deleted file mode 100644 index ca47a1c0c72f..000000000000 --- a/crates/biome_js_formatter/tests/specs/prettier/typescript/satisfies-operators/expression-statement.ts.snap +++ /dev/null @@ -1,180 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: typescript/satisfies-operators/expression-statement.ts ---- - -# Input - -```ts - -let type: 'foo' | 'bar' = 'foo'; - -// demonstrating how "satisfies" expression can be practically used as expression statement. -const _ = () => { -switch (type) { - case 'foo': - return 1; - case 'bar': - return 2; - default: - // exhaustiveness check idiom - (type) satisfies never; - throw new Error('unreachable'); -} -} - -function needParens() { -(let) satisfies unknown; -(interface) satisfies unknown; -(module) satisfies unknown; -(using) satisfies unknown; -(yield) satisfies unknown; -(await) satisfies unknown; -} - -function noNeedParens() { -async satisfies unknown; -satisfies satisfies unknown; -as satisfies unknown; - -abc satisfies unknown; // not a keyword -} - -function satisfiesChain() { -satisfies satisfies satisfies satisfies satisfies; -(type) satisfies never satisfies unknown; -} - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -9,7 +9,7 @@ - return 2; - default: - // exhaustiveness check idiom -- (type) satisfies never; -+ type satisfies never; - throw new Error("unreachable"); - } - }; -@@ -17,8 +17,8 @@ - function needParens() { - (let) satisfies unknown; - (interface) satisfies unknown; -- (module) satisfies unknown; -- (using) satisfies unknown; -+ module satisfies unknown; -+ using satisfies unknown; - (yield) satisfies unknown; - (await) satisfies unknown; - } -@@ -33,5 +33,5 @@ - - function satisfiesChain() { - satisfies satisfies satisfies satisfies satisfies; -- (type) satisfies never satisfies unknown; -+ type satisfies never satisfies unknown; - } -``` - -# Output - -```ts -let type: "foo" | "bar" = "foo"; - -// demonstrating how "satisfies" expression can be practically used as expression statement. -const _ = () => { - switch (type) { - case "foo": - return 1; - case "bar": - return 2; - default: - // exhaustiveness check idiom - type satisfies never; - throw new Error("unreachable"); - } -}; - -function needParens() { - (let) satisfies unknown; - (interface) satisfies unknown; - module satisfies unknown; - using satisfies unknown; - (yield) satisfies unknown; - (await) satisfies unknown; -} - -function noNeedParens() { - async satisfies unknown; - satisfies satisfies unknown; - as satisfies unknown; - - abc satisfies unknown; // not a keyword -} - -function satisfiesChain() { - satisfies satisfies satisfies satisfies satisfies; - type satisfies never satisfies unknown; -} -``` - -# Errors -``` -expression-statement.ts:19:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of reserved keyword `let` as an identifier in strict mode - - 18 │ function needParens() { - > 19 │ (let) satisfies unknown; - │ ^^^ - 20 │ (interface) satisfies unknown; - 21 │ (module) satisfies unknown; - -expression-statement.ts:20:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of reserved keyword `interface` as an identifier in strict mode - - 18 │ function needParens() { - 19 │ (let) satisfies unknown; - > 20 │ (interface) satisfies unknown; - │ ^^^^^^^^^ - 21 │ (module) satisfies unknown; - 22 │ (using) satisfies unknown; - -expression-statement.ts:23:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of reserved keyword `yield` as an identifier in strict mode - - 21 │ (module) satisfies unknown; - 22 │ (using) satisfies unknown; - > 23 │ (yield) satisfies unknown; - │ ^^^^^ - 24 │ (await) satisfies unknown; - 25 │ } - -expression-statement.ts:24:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of `await` as an identifier inside of a module - - 22 │ (using) satisfies unknown; - 23 │ (yield) satisfies unknown; - > 24 │ (await) satisfies unknown; - │ ^^^^^ - 25 │ } - 26 │ - - -``` - -# Lines exceeding max width of 80 characters -``` - 3: // demonstrating how "satisfies" expression can be practically used as expression statement. -``` - -