Skip to content

Commit

Permalink
feat(configuration): add jsx-mode to quick-lint-js.config
Browse files Browse the repository at this point in the history
Allow users to override the React detection heuristics used for some
JSX diagnostics.
  • Loading branch information
strager committed Jan 9, 2024
1 parent ae53f02 commit fd37fda
Show file tree
Hide file tree
Showing 22 changed files with 543 additions and 11 deletions.
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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][].)
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ Each group name is a string.
For the list of group names, see the <<Global Groups>> 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

Expand Down
4 changes: 4 additions & 0 deletions docs/errors/E0191.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
4 changes: 4 additions & 0 deletions docs/errors/E0192.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
4 changes: 4 additions & 0 deletions docs/errors/E0193.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
51 changes: 51 additions & 0 deletions docs/man/quick-lint-js.config.5
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions docs/quick-lint-js.config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
"uniqueItems": true
}
]
},

"jsx-mode": {
"description": "How to lint intrinsic JSX elements.",
"type": "string",
"enum": ["auto", "none", "react"]
}
}
}
8 changes: 8 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
48 changes: 48 additions & 0 deletions src/quick-lint-js/configuration/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/quick-lint-js/configuration/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <quick-lint-js/container/vector.h>
#include <quick-lint-js/fe/global-declared-variable-set.h>
#include <quick-lint-js/fe/global-variables.h>
#include <quick-lint-js/fe/parse.h>
#include <quick-lint-js/port/char8.h>
#include <quick-lint-js/simdjson-fwd.h>
#include <vector>
Expand All @@ -22,6 +23,8 @@ class Configuration {
public:
explicit Configuration();

Parser_JSX_Mode jsx_mode;

const Global_Declared_Variable_Set& globals();

void reset_global_groups();
Expand Down
28 changes: 28 additions & 0 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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];
}
Expand Down
14 changes: 14 additions & 0 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))]] //
Expand Down
1 change: 1 addition & 0 deletions src/quick-lint-js/fe/linter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}, //
Expand Down Expand Up @@ -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}, //
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit fd37fda

Please sign in to comment.