From a51899663b69312702e653043fb963937c797768 Mon Sep 17 00:00:00 2001 From: "Matthew \"strager\" Glazar" Date: Thu, 4 Jan 2024 21:56:29 -0500 Subject: [PATCH] fix(fe): don't report React-specific JSX errors in non-React code Preact uses JSX but has different rules for attributes. Disable our React-specific rules if a React import is not detected. --- docs/CHANGELOG.md | 5 + src/quick-lint-js/fe/parse-expression.cpp | 2 +- src/quick-lint-js/fe/parse-statement.cpp | 16 ++++ src/quick-lint-js/fe/parse.cpp | 21 +++++ src/quick-lint-js/fe/parse.h | 33 +++++++ test/test-parse-jsx-react.cpp | 110 ++++++++++++++++++---- 6 files changed, 167 insertions(+), 20 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index beffed9502..2b935576bd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -20,6 +20,9 @@ Semantic Versioning. * quick-lint-js's tracing no longer crashes with an assertion failure when setting its thread name on FreeBSD. +* React-specific JSX diagnostics, such as [E0193][] ("misspelled React + attribute; write 'className' instead"), are now only reported when 'react' is + imported. This fixes false warnings in Preact code. ([#1152][]) ## 3.0.0 (2024-01-01) @@ -1376,6 +1379,8 @@ Beta release. [toastin0]: https://github.com/toastin0 [wagner riffel]: https://github.com/wgrr +[#1152]: https://github.com/quick-lint/quick-lint-js/issues/1152 + [E0001]: https://quick-lint-js.com/errors/E0001/ [E0003]: https://quick-lint-js.com/errors/E0003/ [E0013]: https://quick-lint-js.com/errors/E0013/ diff --git a/src/quick-lint-js/fe/parse-expression.cpp b/src/quick-lint-js/fe/parse-expression.cpp index 7e512798df..b2f5de8aab 100644 --- a/src/quick-lint-js/fe/parse-expression.cpp +++ b/src/quick-lint-js/fe/parse-expression.cpp @@ -3851,7 +3851,7 @@ Expression* Parser::parse_jsx_element_or_fragment(Parse_Visitor_Base& v, this->lexer_.skip_in_jsx(); } if (is_intrinsic && !has_namespace && !tag_namespace) { - this->check_jsx_attribute(attribute); + this->jsx_intrinsic_attributes_.emplace_back(attribute); } if (this->peek().type == Token_Type::equal) { this->lexer_.skip_in_jsx(); diff --git a/src/quick-lint-js/fe/parse-statement.cpp b/src/quick-lint-js/fe/parse-statement.cpp index a7bc286f83..70436d5342 100644 --- a/src/quick-lint-js/fe/parse-statement.cpp +++ b/src/quick-lint-js/fe/parse-statement.cpp @@ -68,6 +68,7 @@ void Parser::parse_and_visit_module(Parse_Visitor_Base &v) { } } } + this->check_all_jsx_attributes(); v.visit_end_of_module(); } @@ -4695,6 +4696,7 @@ void Parser::parse_and_visit_import( // import "foo"; case Token_Type::string: // Do not set is_current_typescript_namespace_non_empty_. + this->visited_module_import(this->peek()); this->skip(); this->consume_semicolon_after_statement(); return; @@ -4853,6 +4855,7 @@ void Parser::parse_and_visit_import( this->skip(); QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::string); + this->visited_module_import(this->peek()); this->skip(); QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::right_paren); this->skip(); @@ -4918,6 +4921,7 @@ void Parser::parse_and_visit_import( .declare_keyword = *declare_context.declare_namespace_declare_keyword, }); } + this->visited_module_import(this->peek()); this->skip(); break; @@ -5313,6 +5317,18 @@ void Parser::parse_and_visit_named_exports( this->skip(); } +void Parser::visited_module_import(const Token &module_name) { + QLJS_ASSERT(module_name.type == Token_Type::string); + // TODO(#1159): Write a proper routine to decode string literals. + String8_View module_name_unescaped = + make_string_view(module_name.begin + 1, module_name.end - 1); + if (module_name_unescaped == u8"react"_sv || + module_name_unescaped == u8"react-dom"_sv || + starts_with(module_name_unescaped, u8"react-dom/"_sv)) { + this->imported_react_ = true; + } +} + void Parser::parse_and_visit_variable_declaration_statement( Parse_Visitor_Base &v, bool is_top_level_typescript_definition_without_declare_or_export) { diff --git a/src/quick-lint-js/fe/parse.cpp b/src/quick-lint-js/fe/parse.cpp index c6942560ac..a438652ce6 100644 --- a/src/quick-lint-js/fe/parse.cpp +++ b/src/quick-lint-js/fe/parse.cpp @@ -158,6 +158,27 @@ Expression* Parser::build_expression(Binary_Expression_Builder& builder) { } } +void Parser::check_all_jsx_attributes() { + switch (this->options_.jsx_mode) { + case Parser_JSX_Mode::none: + break; + + case Parser_JSX_Mode::auto_detect: + if (this->imported_react_) { + goto react; + } + break; + + react: + case Parser_JSX_Mode::react: + this->jsx_intrinsic_attributes_.for_each( + [&](const Identifier& attribute_name) -> void { + this->check_jsx_attribute(attribute_name); + }); + break; + } +} + QLJS_WARNING_PUSH QLJS_WARNING_IGNORE_GCC("-Wnull-dereference") void Parser::check_jsx_attribute(const Identifier& attribute_name) { diff --git a/src/quick-lint-js/fe/parse.h b/src/quick-lint-js/fe/parse.h index 0e0ea9fb12..ed9b99f7bb 100644 --- a/src/quick-lint-js/fe/parse.h +++ b/src/quick-lint-js/fe/parse.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -55,11 +56,30 @@ enum class Parser_Top_Level_Await_Mode { await_operator, }; +// How to interpret props for builtins such as