diff --git a/Cargo.lock b/Cargo.lock index bae4b43..981f5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flavor-cli" -version = "0.3.3" +version = "0.3.4" dependencies = [ "flavor-core", "flavor-grammar", diff --git a/crates/flavor-cli/Cargo.toml b/crates/flavor-cli/Cargo.toml index 8b5c80e..4c55849 100644 --- a/crates/flavor-cli/Cargo.toml +++ b/crates/flavor-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flavor-cli" -version = "0.3.3" +version = "0.3.4" edition = "2021" license = "MIT" description = "Personal check-only AST-backed code flavor lint CLI." diff --git a/crates/flavor-plugin-typescript/src/parser/expressions.rs b/crates/flavor-plugin-typescript/src/parser/expressions.rs index 12f9005..8e96871 100644 --- a/crates/flavor-plugin-typescript/src/parser/expressions.rs +++ b/crates/flavor-plugin-typescript/src/parser/expressions.rs @@ -32,9 +32,12 @@ impl<'a> Parser<'a> { while !self.at(kind::END_OF_FILE) && !self.at_any(stops) { let start = self.cursor; match self.current() { - kind::LESS_THAN if self.jsx_enabled() && self.starts_jsx_open() => { + kind::LESS_THAN if self.jsx_enabled() && self.starts_jsx_expression_open() => { self.parse_jsx_element(); } + kind::KEYWORD_AS | kind::KEYWORD_SATISFIES => { + self.parse_type_expression_tail(stops) + } kind::KEYWORD_AWAIT => self.parse_await_expression(), kind::KEYWORD_NEW => self.parse_new_expression(), kind if is_expr_base(kind) && self.next_is(kind::OPEN_PAREN) => { @@ -139,6 +142,11 @@ impl<'a> Parser<'a> { self.builder.finish_node(); } + fn parse_type_expression_tail(&mut self, stops: &[Kind]) { + self.bump(); + self.parse_type(stops); + } + fn parse_expression_operand(&mut self) { match self.current() { kind::KEYWORD_NEW => self.parse_new_expression(), diff --git a/crates/flavor-plugin-typescript/src/parser/jsx.rs b/crates/flavor-plugin-typescript/src/parser/jsx.rs index f863a3b..f3e0f5d 100644 --- a/crates/flavor-plugin-typescript/src/parser/jsx.rs +++ b/crates/flavor-plugin-typescript/src/parser/jsx.rs @@ -45,6 +45,10 @@ impl<'a> Parser<'a> { self.at(kind::LESS_THAN) && is_jsx_name_start(self.token_kind_at(1)) } + pub(super) fn starts_jsx_expression_open(&self) -> bool { + self.starts_jsx_open() && can_start_jsx_after(self.token_kind_at_back(1)) + } + fn parse_jsx_opening(&mut self) -> Option { self.builder.start_node(kind::JSX_OPENING_ELEMENT); self.bump(); @@ -209,3 +213,32 @@ fn is_jsx_name_part(kind: Kind) -> bool { fn is_jsx_name_join(kind: Kind) -> bool { matches!(kind, kind::DOT | kind::COLON | kind::MINUS) } + +fn can_start_jsx_after(kind: Kind) -> bool { + matches!( + kind, + kind::END_OF_FILE + | kind::OPEN_PAREN + | kind::OPEN_BRACKET + | kind::OPEN_BRACE + | kind::COMMA + | kind::SEMICOLON + | kind::COLON + | kind::QUESTION + | kind::EQUALS + | kind::ARROW + | kind::BANG + | kind::PLUS + | kind::MINUS + | kind::STAR + | kind::SLASH + | kind::PERCENT + | kind::AMPERSAND_AMPERSAND + | kind::PIPE_PIPE + | kind::QUESTION_QUESTION + | kind::KEYWORD_RETURN + | kind::KEYWORD_THROW + | kind::KEYWORD_CASE + | kind::KEYWORD_YIELD + ) +} diff --git a/crates/flavor-plugin-typescript/tests/parser.rs b/crates/flavor-plugin-typescript/tests/parser.rs index 4c07066..3d8b5df 100644 --- a/crates/flavor-plugin-typescript/tests/parser.rs +++ b/crates/flavor-plugin-typescript/tests/parser.rs @@ -96,11 +96,33 @@ mod tsx { #[test] fn comparison_stays_expression() { - let output = ts_output("const ok = left < right;"); + let output = tsx_output( + "function inspectionLabelsOverlap(a: InspectionLabelBox, b: InspectionLabelBox) { + return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top; +}", + ); assert!(has_node(&output, kind::BINARY_EXPRESSION)); + assert!(has_node(&output, kind::MEMBER_EXPRESSION)); assert!(!has_node(&output, kind::JSX_ELEMENT)); - assert!(output.diagnostics.is_empty()); + assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); + } + + #[test] + fn satisfies_generics_stay_type() { + let output = tsx_output( + r#"type InspectDomain = "session" | "message"; +type InspectDomainPanel = (props: InspectDomainPanelProps) => ReactNode; +const inspectDomainRegistry = { + message: MessageInspectDomain, + session: SessionInspectDomain, +} satisfies Record;"#, + ); + + assert!(has_node(&output, kind::BINARY_EXPRESSION)); + assert!(has_node(&output, kind::TYPE_REFERENCE)); + assert!(!has_node(&output, kind::JSX_ELEMENT)); + assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); } }