From fd37fdacc615c75f7878244e9ab27261f8728e9a Mon Sep 17 00:00:00 2001 From: "Matthew \"strager\" Glazar" Date: Tue, 9 Jan 2024 17:54:17 -0500 Subject: [PATCH] feat(configuration): add jsx-mode to quick-lint-js.config Allow users to override the React detection heuristics used for some JSX diagnostics. --- docs/CHANGELOG.md | 4 + docs/config.adoc | 17 ++ docs/errors/E0191.md | 4 + docs/errors/E0192.md | 4 + docs/errors/E0193.md | 4 + docs/man/quick-lint-js.config.5 | 51 ++++ docs/quick-lint-js.config.schema.json | 6 + po/messages.pot | 8 + .../configuration/configuration.cpp | 48 ++++ .../configuration/configuration.h | 3 + .../diag/diagnostic-metadata-generated.cpp | 28 +++ .../diag/diagnostic-metadata-generated.h | 4 +- src/quick-lint-js/diag/diagnostic-types-2.h | 14 ++ src/quick-lint-js/fe/linter.cpp | 1 + .../i18n/translation-table-generated.cpp | 6 +- .../i18n/translation-table-generated.h | 6 +- .../i18n/translation-table-test-generated.h | 24 +- test/test-configuration.cpp | 80 +++++- test/test-lsp-server.cpp | 6 +- website/public/common-nav.ejs.html | 1 + website/public/docs/index.ejs.html | 7 +- website/public/errors/jsx/index.ejs.html | 228 ++++++++++++++++++ 22 files changed, 543 insertions(+), 11 deletions(-) create mode 100644 website/public/errors/jsx/index.ejs.html diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index db49edcdd5..f076f133e5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,9 @@ Semantic Versioning. ### Added +* quick-lint-js's JSX diagnostics can now be configured via quick-lint-js's [JSX + mode][] mechanism. New JSX modes are `"none"`, `"react"`, and `"auto"` + (default). * Writing a namespace alias with `import type`, such as in `import type ns = otherns;`, now reports [E0717][] ("namespace alias cannot use 'import type'"). (Implemented by [koopiehoop][].) @@ -1322,6 +1325,7 @@ Beta release. [Bun]: https://bun.sh/ [Deno]: https://deno.land/ +[JSX Mode]: https://quick-lint-js.com/errors/jsx/ [cli-language]: ../cli/#language [cmake-install-component-build-tools-patch]: https://github.com/quick-lint/quick-lint-js/commit/3923f0df76d24b73d57f15eec61ab190ea048093.patch [coc.nvim]: https://github.com/neoclide/coc.nvim diff --git a/docs/config.adoc b/docs/config.adoc index 8c1a657047..f7392460d8 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -114,6 +114,23 @@ Each group name is a string. For the list of group names, see the <> section. -- +*jsx-mode*:: Optional. +How to lint intrinsic JSX elements. ++ +-- +*jsx-mode* is a string. +Possible values are as follows: + +- *"auto"* (default): Determine the JSX flavor automatically using various heuristics. +See the link:https://quick-lint-js.com/errors/jsx/#auto[JSX linting documentation] for more information about these heuristics. +- *"react"*: Assume that JSX is intended for the React.js framework. +- *"none"*: Disable all framework-specific JSX linting rules. + +See the link:https://quick-lint-js.com/errors/jsx/[JSX linting documentation] for more information on which rules are configured by *jsx-mode*. + +Introduced in quick-lint-js version 3.1.0. +-- + [#globals] == Globals diff --git a/docs/errors/E0191.md b/docs/errors/E0191.md index acb4800b7c..427bf6e993 100644 --- a/docs/errors/E0191.md +++ b/docs/errors/E0191.md @@ -29,4 +29,8 @@ function TodoEntry({addTodo, changePendingTodo}) { } ``` +This diagnostic enabled in the `"react"` [JSX mode][]. + Introduced in quick-lint-js version 2.0.0. + +[JSX Mode]: https://quick-lint-js.com/errors/jsx/ diff --git a/docs/errors/E0192.md b/docs/errors/E0192.md index 096e1efedf..1c9c4e3dca 100644 --- a/docs/errors/E0192.md +++ b/docs/errors/E0192.md @@ -30,4 +30,8 @@ function Header({columns}) { } ``` +This diagnostic enabled in the `"react"` [JSX mode][]. + Introduced in quick-lint-js version 2.0.0. + +[JSX Mode]: https://quick-lint-js.com/errors/jsx/ diff --git a/docs/errors/E0193.md b/docs/errors/E0193.md index 8850b7cf42..0d926ca944 100644 --- a/docs/errors/E0193.md +++ b/docs/errors/E0193.md @@ -29,4 +29,8 @@ function Title({page}) { } ``` +This diagnostic enabled in the `"react"` [JSX mode][]. + Introduced in quick-lint-js version 2.0.0. + +[JSX Mode]: https://quick-lint-js.com/errors/jsx/ diff --git a/docs/man/quick-lint-js.config.5 b/docs/man/quick-lint-js.config.5 index 6a9cfa5c25..77db5c0b24 100644 --- a/docs/man/quick-lint-js.config.5 +++ b/docs/man/quick-lint-js.config.5 @@ -226,6 +226,57 @@ If \fBglobal\-groups\fP is a non\-empty array, then global variables are defined Each group name is a string. For the list of group names, see the GLOBAL GROUPS section. .RE +.sp +\fBjsx\-mode\fP +.RS 4 +Optional. +How to lint intrinsic JSX elements. +.sp +\fBjsx\-mode\fP is a string. +Possible values are as follows: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB"auto"\fP (default): Determine the JSX flavor automatically using various heuristics. +See the \c +.URL "https://quick\-lint\-js.com/errors/jsx/#auto" "JSX linting documentation" "" +for more information about these heuristics. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB"react"\fP: Assume that JSX is intended for the React.js framework. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB"none"\fP: Disable all framework\-specific JSX linting rules. +.RE +.sp +See the \c +.URL "https://quick\-lint\-js.com/errors/jsx/" "JSX linting documentation" "" +for more information on which rules are configured by \fBjsx\-mode\fP. +.sp +Introduced in quick\-lint\-js version 3.1.0. +.RE .SH "GLOBALS" .sp The \fBglobals\fP configuration property tells quick\-lint\-js what global variables to assume exist. diff --git a/docs/quick-lint-js.config.schema.json b/docs/quick-lint-js.config.schema.json index 6ecfa91228..40621410ca 100644 --- a/docs/quick-lint-js.config.schema.json +++ b/docs/quick-lint-js.config.schema.json @@ -62,6 +62,12 @@ "uniqueItems": true } ] + }, + + "jsx-mode": { + "description": "How to lint intrinsic JSX elements.", + "type": "string", + "enum": ["auto", "none", "react"] } } } diff --git a/po/messages.pot b/po/messages.pot index 01eb3195e6..cfca0327e2 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -709,6 +709,14 @@ msgstr "" msgid "\"globals\" must be an object" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "\"jsx-mode\" must be a string; try \"none\" or \"react\"" +msgstr "" + +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "unknown JSX mode; try \"none\" or \"react\"" +msgstr "" + #: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp msgid "depth limit exceeded" msgstr "" diff --git a/src/quick-lint-js/configuration/configuration.cpp b/src/quick-lint-js/configuration/configuration.cpp index 9a13b861dc..41df9b9b43 100644 --- a/src/quick-lint-js/configuration/configuration.cpp +++ b/src/quick-lint-js/configuration/configuration.cpp @@ -135,9 +135,57 @@ void Configuration::load_from_json(Padded_String_View json, this->report_json_error(json, out_diags); return; } + + auto jsx_mode = document["jsx-mode"]; + std::string_view jsx_mode_string; + switch (jsx_mode.get(jsx_mode_string)) { + case ::simdjson::error_code::SUCCESS: { + if (jsx_mode_string == "auto"sv) { + this->jsx_mode = Parser_JSX_Mode::auto_detect; + } else if (jsx_mode_string == "react"sv) { + this->jsx_mode = Parser_JSX_Mode::react; + } else if (jsx_mode_string == "none"sv) { + this->jsx_mode = Parser_JSX_Mode::none; + } else { + ::simdjson::ondemand::value v; + if (jsx_mode.get(v) == ::simdjson::SUCCESS) { + out_diags->add(Diag_Config_JSX_Mode_Unrecognized{ + .value = span_of_json_value(v), + }); + } else { + this->report_json_error(json, out_diags); + } + } + break; + } + + case ::simdjson::error_code::INCORRECT_TYPE: { + // Either "jsx-mode" has the wrong type or there is a syntax error. simdjson + // gives us INCORRECT_TYPE in both cases. + ::simdjson::ondemand::value v; + if (jsx_mode.get(v) == ::simdjson::SUCCESS && + v.type().error() == ::simdjson::SUCCESS) { + out_diags->add(Diag_Config_JSX_Mode_Type_Mismatch{ + .value = span_of_json_value(v), + }); + } else { + this->report_json_error(json, out_diags); + return; + } + break; + } + + case ::simdjson::error_code::NO_SUCH_FIELD: + break; + + default: + this->report_json_error(json, out_diags); + return; + } } void Configuration::reset() { + this->jsx_mode = Parser_JSX_Mode::auto_detect; this->globals_.clear(); this->globals_to_remove_.clear(); this->did_add_globals_from_groups_ = false; diff --git a/src/quick-lint-js/configuration/configuration.h b/src/quick-lint-js/configuration/configuration.h index cad294ae98..15bc04d2bd 100644 --- a/src/quick-lint-js/configuration/configuration.h +++ b/src/quick-lint-js/configuration/configuration.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,8 @@ class Configuration { public: explicit Configuration(); + Parser_JSX_Mode jsx_mode; + const Global_Declared_Variable_Set& globals(); void reset_global_groups(); diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp index c1e91e03a1..f29b70bfbd 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -1364,6 +1364,34 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = { }, }, + // Diag_Config_JSX_Mode_Type_Mismatch + { + .code = 456, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("\"jsx-mode\" must be a string; try \"none\" or \"react\""), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Config_JSX_Mode_Type_Mismatch, value), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + + // Diag_Config_JSX_Mode_Unrecognized + { + .code = 455, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("unknown JSX mode; try \"none\" or \"react\""), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Config_JSX_Mode_Unrecognized, value), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + // Diag_Depth_Limit_Exceeded { .code = 203, diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.h b/src/quick-lint-js/diag/diagnostic-metadata-generated.h index fc881f957b..bbd9239b0e 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -99,6 +99,8 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_Config_Globals_Descriptor_Shadowable_Type_Mismatch) \ QLJS_DIAG_TYPE_NAME(Diag_Config_Globals_Descriptor_Writable_Type_Mismatch) \ QLJS_DIAG_TYPE_NAME(Diag_Config_Globals_Type_Mismatch) \ + QLJS_DIAG_TYPE_NAME(Diag_Config_JSX_Mode_Type_Mismatch) \ + QLJS_DIAG_TYPE_NAME(Diag_Config_JSX_Mode_Unrecognized) \ QLJS_DIAG_TYPE_NAME(Diag_Depth_Limit_Exceeded) \ QLJS_DIAG_TYPE_NAME(Diag_Dot_Not_Allowed_After_Generic_Arguments_In_Type) \ QLJS_DIAG_TYPE_NAME(Diag_Dot_Dot_Is_Not_An_Operator) \ @@ -474,7 +476,7 @@ namespace quick_lint_js { /* END */ // clang-format on -inline constexpr int Diag_Type_Count = 460; +inline constexpr int Diag_Type_Count = 462; extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count]; } diff --git a/src/quick-lint-js/diag/diagnostic-types-2.h b/src/quick-lint-js/diag/diagnostic-types-2.h index 49b7a9059a..b858eae431 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -731,6 +731,20 @@ struct Diag_Config_Globals_Type_Mismatch { Source_Code_Span value; }; +struct Diag_Config_JSX_Mode_Type_Mismatch { + [[qljs::diag("E0456", Diagnostic_Severity::error)]] // + [[qljs::message("\"jsx-mode\" must be a string; try \"none\" or \"react\"", + ARG(value))]] // + Source_Code_Span value; +}; + +struct Diag_Config_JSX_Mode_Unrecognized { + [[qljs::diag("E0455", Diagnostic_Severity::error)]] // + [[qljs::message("unknown JSX mode; try \"none\" or \"react\"", + ARG(value))]] // + Source_Code_Span value; +}; + struct Diag_Depth_Limit_Exceeded { [[qljs::diag("E0203", Diagnostic_Severity::error)]] // [[qljs::message("depth limit exceeded", ARG(token))]] // diff --git a/src/quick-lint-js/fe/linter.cpp b/src/quick-lint-js/fe/linter.cpp index 9ffedaeadd..6044eba8ab 100644 --- a/src/quick-lint-js/fe/linter.cpp +++ b/src/quick-lint-js/fe/linter.cpp @@ -18,6 +18,7 @@ namespace quick_lint_js { void parse_and_lint(Padded_String_View code, Diag_Reporter& reporter, Linter_Options options) { Parser_Options parser_options; + parser_options.jsx_mode = options.configuration->jsx_mode; switch (options.language) { case File_Language::javascript: parser_options.jsx = false; diff --git a/src/quick-lint-js/i18n/translation-table-generated.cpp b/src/quick-lint-js/i18n/translation-table-generated.cpp index 066fe68fac..5c8c06916d 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.cpp +++ b/src/quick-lint-js/i18n/translation-table-generated.cpp @@ -18,6 +18,7 @@ const Translation_Table translation_data = { {74, 87, 79, 56, 0, 59}, // {71, 80, 60, 58, 0, 52}, // {0, 0, 0, 0, 0, 28}, // + {0, 0, 0, 0, 0, 51}, // {0, 0, 0, 0, 0, 73}, // {0, 0, 0, 0, 0, 74}, // {0, 0, 0, 0, 0, 63}, // @@ -576,7 +577,8 @@ const Translation_Table translation_data = { {0, 0, 0, 0, 0, 65}, // {92, 45, 78, 81, 70, 43}, // {0, 0, 0, 0, 0, 77}, // - {98, 37, 86, 82, 83, 81}, // + {0, 0, 0, 0, 0, 81}, // + {98, 37, 86, 82, 83, 40}, // {38, 35, 17, 23, 13, 14}, // {38, 27, 34, 28, 33, 27}, // {26, 41, 26, 32, 0, 22}, // @@ -1887,6 +1889,7 @@ const Translation_Table translation_data = { u8"\"globals\" descriptor \"writable\" property must be a boolean\0" u8"\"globals\" descriptor must be a boolean or an object\0" u8"\"globals\" must be an object\0" + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"\0" u8"'!' (definite assignment assertion) cannot be used with an initial value\0" u8"'!' (definite assignment assertion) is not allowed on 'declare' variables\0" u8"'!' here treated as the TypeScript non-null assertion operator\0" @@ -2446,6 +2449,7 @@ const Translation_Table translation_data = { u8"unexpected whitespace between '!' and '=='\0" u8"unicode byte order mark (BOM) cannot appear before #! at beginning of script\0" u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &\0" + u8"unknown JSX mode; try \"none\" or \"react\"\0" u8"unmatched '}'\0" u8"unmatched indexing bracket\0" u8"unmatched parenthesis\0" diff --git a/src/quick-lint-js/i18n/translation-table-generated.h b/src/quick-lint-js/i18n/translation-table-generated.h index 7443edc0c7..02355e8fb5 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.h +++ b/src/quick-lint-js/i18n/translation-table-generated.h @@ -18,8 +18,8 @@ namespace quick_lint_js { using namespace std::literals::string_view_literals; constexpr std::uint32_t translation_table_locale_count = 5; -constexpr std::uint16_t translation_table_mapping_table_size = 604; -constexpr std::size_t translation_table_string_table_size = 82391; +constexpr std::uint16_t translation_table_mapping_table_size = 606; +constexpr std::size_t translation_table_string_table_size = 82482; constexpr std::size_t translation_table_locale_table_size = 35; QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( @@ -32,6 +32,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "\"globals\" descriptor \"writable\" property must be a boolean"sv, "\"globals\" descriptor must be a boolean or an object"sv, "\"globals\" must be an object"sv, + "\"jsx-mode\" must be a string; try \"none\" or \"react\""sv, "'!' (definite assignment assertion) cannot be used with an initial value"sv, "'!' (definite assignment assertion) is not allowed on 'declare' variables"sv, "'!' here treated as the TypeScript non-null assertion operator"sv, @@ -591,6 +592,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "unexpected whitespace between '!' and '=='"sv, "unicode byte order mark (BOM) cannot appear before #! at beginning of script"sv, "unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &"sv, + "unknown JSX mode; try \"none\" or \"react\""sv, "unmatched '}'"sv, "unmatched indexing bracket"sv, "unmatched parenthesis"sv, diff --git a/src/quick-lint-js/i18n/translation-table-test-generated.h b/src/quick-lint-js/i18n/translation-table-test-generated.h index 4f7c7ebfb0..db9a0443c5 100644 --- a/src/quick-lint-js/i18n/translation-table-test-generated.h +++ b/src/quick-lint-js/i18n/translation-table-test-generated.h @@ -27,7 +27,7 @@ struct Translated_String { }; // clang-format off -inline const Translated_String test_translation_table[603] = { +inline const Translated_String test_translation_table[605] = { { "\"global-groups\" entries must be strings"_translatable, u8"\"global-groups\" entries must be strings", @@ -94,6 +94,17 @@ inline const Translated_String test_translation_table[603] = { u8"\"globals\" must be an object", }, }, + { + "\"jsx-mode\" must be a string; try \"none\" or \"react\""_translatable, + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + { + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + u8"\"jsx-mode\" must be a string; try \"none\" or \"react\"", + }, + }, { "'!' (definite assignment assertion) cannot be used with an initial value"_translatable, u8"'!' (definite assignment assertion) cannot be used with an initial value", @@ -6243,6 +6254,17 @@ inline const Translated_String test_translation_table[603] = { u8"unintuitive operator precedence when using & and '{0}'; '{0}' evaluates before &", }, }, + { + "unknown JSX mode; try \"none\" or \"react\""_translatable, + u8"unknown JSX mode; try \"none\" or \"react\"", + { + u8"unknown JSX mode; try \"none\" or \"react\"", + u8"unknown JSX mode; try \"none\" or \"react\"", + u8"unknown JSX mode; try \"none\" or \"react\"", + u8"unknown JSX mode; try \"none\" or \"react\"", + u8"unknown JSX mode; try \"none\" or \"react\"", + }, + }, { "unmatched '}'"_translatable, u8"unmatched '}'", diff --git a/test/test-configuration.cpp b/test/test-configuration.cpp index ce92e801bb..2499223a86 100644 --- a/test/test-configuration.cpp +++ b/test/test-configuration.cpp @@ -14,8 +14,16 @@ #include #include -#define EXPECT_DEFAULT_CONFIG(config) \ +#define EXPECT_DEFAULT_CONFIG(config) \ + do { \ + EXPECT_EQ((config).jsx_mode, \ + ::quick_lint_js::Parser_JSX_Mode::auto_detect); \ + EXPECT_DEFAULT_CONFIG_GLOBALS(config); \ + } while (false) + +#define EXPECT_DEFAULT_CONFIG_GLOBALS(config) \ do { \ + EXPECT_TRUE((config).globals().find_runtime_or_type(u8"Array"_sv)); \ EXPECT_TRUE((config).globals().find_runtime_or_type(u8"Array"_sv)); \ EXPECT_TRUE((config).globals().find_runtime_or_type(u8"console"_sv)); \ EXPECT_FALSE( \ @@ -502,7 +510,7 @@ TEST(Test_Configuration_JSON, true_global_groups_leaves_defaults) { Configuration c; load_from_json(c, u8R"({"global-groups": true})"_sv); - EXPECT_DEFAULT_CONFIG(c); + EXPECT_DEFAULT_CONFIG_GLOBALS(c); } TEST(Test_Configuration_JSON, false_global_groups_disables_all_groups) { @@ -533,7 +541,7 @@ TEST(Test_Configuration_JSON, empty_globals_leaves_defaults) { Configuration c; load_from_json(c, u8R"({"globals": {}})"_sv); - EXPECT_DEFAULT_CONFIG(c); + EXPECT_DEFAULT_CONFIG_GLOBALS(c); } TEST(Test_Configuration_JSON, true_global_is_usable) { @@ -767,6 +775,72 @@ TEST(Test_Configuration_JSON, bad_global_error_excludes_trailing_whitespace) { assert_diagnostics(&json, diags, {error}); } +TEST(Test_Configuration_JSON, valid_jsx_mode) { + { + Configuration c; + load_from_json(c, u8R"({"jsx-mode": "auto"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::auto_detect); + } + + { + Configuration c; + load_from_json(c, u8R"({"jsx-mode": "react"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::react); + } + + { + Configuration c; + load_from_json(c, u8R"({"jsx-mode": "none"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::none); + } +} + +TEST(Test_Configuration_JSON, revert_jsx_mode_to_default) { + { + Configuration c; + load_from_json(c, u8R"({"jsx-mode": "react"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::react); + c.reset(); + load_from_json(c, u8R"({"jsx-mode": "auto"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::auto_detect); + } + + { + Configuration c; + load_from_json(c, u8R"({"jsx-mode": "react"})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::react); + c.reset(); + load_from_json(c, u8R"({})"_sv); + EXPECT_EQ(c.jsx_mode, Parser_JSX_Mode::auto_detect); + } +} + +TEST(Test_Configuration_JSON, invalid_jsx_mode) { + Monotonic_Allocator temp_memory("test"); + + { + // clang-format off + Padded_String json(u8"{\"jsx-mode\": \"AUTO\"}"_sv); + auto error = /* */ u8" ^^^^^^^^ Diag_Config_JSX_Mode_Unrecognized"_diag; + // clang-format on + Diag_List diags(&temp_memory); + Configuration c; + c.load_from_json(&json, &diags); + assert_diagnostics(&json, diags, {error}); + } + + { + // clang-format off + Padded_String json(u8"{\"jsx-mode\": true}"_sv); + auto error = /* */ u8" ^^^^ Diag_Config_JSX_Mode_Type_Mismatch"_diag; + // clang-format on + Diag_List diags(&temp_memory); + Configuration c; + c.load_from_json(&json, &diags); + assert_diagnostics(&json, diags, {error}); + } +} + void load_from_json(Configuration& config, Padded_String_View json) { Monotonic_Allocator temp_memory("test"); Diag_List diags(&temp_memory); diff --git a/test/test-lsp-server.cpp b/test/test-lsp-server.cpp index 60af7a6571..292782394d 100644 --- a/test/test-lsp-server.cpp +++ b/test/test-lsp-server.cpp @@ -843,14 +843,16 @@ TEST_F(Test_Linting_LSP_Server, } TEST_F(Test_Linting_LSP_Server, linting_uses_config_from_file) { - this->fs.create_file(this->fs.rooted("quick-lint-js.config"), - u8R"({"globals": {"testGlobalVariable": true}})"_sv); + this->fs.create_file( + this->fs.rooted("quick-lint-js.config"), + u8R"({"globals": {"testGlobalVariable": true}, "jsx-mode": "none"})"_sv); auto lint_callback = [&](Configuration& config, File_Language, Padded_String_View, String8_View, String8_View, Outgoing_JSON_RPC_Message_Queue&) { EXPECT_TRUE( config.globals().find_runtime_or_type(u8"testGlobalVariable"_sv)); + EXPECT_EQ(config.jsx_mode, Parser_JSX_Mode::none); }; this->linter.lint_callback = lint_callback; diff --git a/website/public/common-nav.ejs.html b/website/public/common-nav.ejs.html index bcbc1ca419..f3a36c4220 100644 --- a/website/public/common-nav.ejs.html +++ b/website/public/common-nav.ejs.html @@ -217,6 +217,7 @@ { pattern: /^\/errors\/(E[0-9]+)\/$/, hidden: true, makeTitle(uri, match) { return match[1]; } }, + { uri: "/errors/jsx/", title: "JSX" }, { uri: "/cli/", title: "CLI" }, { uri: "/config/", title: "Config" }, { uri: "/docs/lsp/", title: "LSP" }, diff --git a/website/public/docs/index.ejs.html b/website/public/docs/index.ejs.html index 7d7fc4c350..e463510e56 100644 --- a/website/public/docs/index.ejs.html +++ b/website/public/docs/index.ejs.html @@ -30,7 +30,12 @@

Documentation

CLI manual for the quick-lint-js command -
  • Error codes and examples
  • +
  • + Error codes and examples + +
  • Configuring quick-lint-js with a quick-lint-js.config file diff --git a/website/public/errors/jsx/index.ejs.html b/website/public/errors/jsx/index.ejs.html new file mode 100644 index 0000000000..53131b8819 --- /dev/null +++ b/website/public/errors/jsx/index.ejs.html @@ -0,0 +1,228 @@ + + + + + + + + <%- await include("../../common-head.ejs.html") %> + + + + +
    <%- await include("../../common-nav.ejs.html") %>
    + +
    +

    JSX linting

    + +

    + quick-lint-js supports JSX in JavaScript and TypeScript code. Different + web frameworks, such as React.js, treat JSX differently. quick-lint-js + has different diagnostics for different frameworks. +

    + +

    JSX modes

    + +

    + quick-lint-js has a configurable JSX mode which determines + which diagnostics are reported, if any, for invalid JSX use. +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + quick-lint-js's JSX modes +
    JSX modeDescription
    "auto" (default)See below
    "react"React.js
    "none" + JSX is allowed, but no framework-specific
    diagnostics are + reported +
    + +

    + You can specify the JSX mode explicitly by creating a + quick-lint-js.config file. This is useful if + quick-lint-js fails to automatically detect your framework. For example: +

    +
    +
    {
    +  "jsx-mode": "react"
    +}
    +
    + quick-lint-js.config which lints JSX as React.js without needing + import "react"; +
    +
    + +
    +

    "auto" JSX mode

    + +

    + By default, quick-lint-js's JSX mode is + "auto". If the JSX mode is + "auto", then quick-lint-js will guess which + framework you are using based on + import statements in your code: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + quick-lint-js's JSX mode heuristics +
    ImportGuessed JSX mode
    + import React from "react"; + "react"
    + import ReactDOM from "react-dom"; + "react"
    + import ReactDOM from "react-dom/client"; + "react"
    + import ReactDOM from "react-dom/server"; + "react"
    + (none of the above + import statements) + "none"
    (multiple JSX modes detected)"none"
    +
    + +
    +

    React.js ("react" JSX mode)

    + +

    + quick-lint-js detects misuse of JSX with the React.js framework if the + JSX mode is "react". +

    + +

    + React.js-specific diagnostics: E0191, + E0192, E0193 +

    + + +
    +
    <%- await lintJSXCodeAsync(`import React from "react";
    +
    +export function CommentButton({onClick}) {
    +  return (
    +    
    +  );
    +}`) %>
    +
    + Demonstration of different React-specific diagnostics in + quick-lint-js +
    +
    +
    +
    + +
    <%- await include("../../common-footer-nav.ejs.html") %>
    + + + + + +