From d85ec2958af9dcd28d16126d3a2b078a59604af2 Mon Sep 17 00:00:00 2001 From: "Matthew \"strager\" Glazar" Date: Tue, 21 Nov 2023 16:48:31 -0500 Subject: [PATCH] feat(typescript): allow 'export default I; interface I {}' Allow 'export default' to find TypeScript types (such as interfaces), not just run-time variables. --- docs/CHANGELOG.md | 2 ++ src/quick-lint-js/fe/variable-analyzer.cpp | 33 +++++++++++++++------- test/test-variable-analyzer-module.cpp | 24 ++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b70e8f409b..b83006897d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -21,6 +21,8 @@ Semantic Versioning. an arrow function parameter list. * If a type predicate appears outside a return type, quick-lint-js now reports [E0426][] ("type predicates are only allowed as function return types"). + * `export default` now supports separately-declared TypeScript interfaces and + types. ### Fixed diff --git a/src/quick-lint-js/fe/variable-analyzer.cpp b/src/quick-lint-js/fe/variable-analyzer.cpp index d1af5e8fa9..9fc415211d 100644 --- a/src/quick-lint-js/fe/variable-analyzer.cpp +++ b/src/quick-lint-js/fe/variable-analyzer.cpp @@ -467,12 +467,22 @@ void Variable_Analyzer::visit_variable_use(Identifier name, Used_Variable_Kind use_kind) { QLJS_ASSERT(!this->scopes_.empty()); Scope ¤t_scope = this->current_scope(); - Declared_Variable *var = - use_kind == Used_Variable_Kind::type - ? current_scope.declared_variables.find_type(name) - : use_kind == Used_Variable_Kind::_export - ? current_scope.declared_variables.find(name) - : current_scope.declared_variables.find_runtime(name); + Declared_Variable *var; + switch (use_kind) { + case Used_Variable_Kind::type: + var = current_scope.declared_variables.find_type(name); + break; + case Used_Variable_Kind::_export: + case Used_Variable_Kind::_export_default: + var = current_scope.declared_variables.find(name); + break; + case Used_Variable_Kind::_delete: + case Used_Variable_Kind::_typeof: + case Used_Variable_Kind::assignment: + case Used_Variable_Kind::use: + var = current_scope.declared_variables.find_runtime(name); + break; + } if (var) { var->is_used = true; } else { @@ -534,12 +544,15 @@ void Variable_Analyzer::visit_end_of_module() { switch (var.kind) { case Used_Variable_Kind::_delete: case Used_Variable_Kind::_export: - case Used_Variable_Kind::_export_default: case Used_Variable_Kind::_typeof: case Used_Variable_Kind::assignment: case Used_Variable_Kind::use: QLJS_ASSERT(!global_scope.declared_variables.find_runtime(var.name)); break; + case Used_Variable_Kind::_export_default: + QLJS_ASSERT(!global_scope.declared_variables.find_runtime(var.name)); + QLJS_ASSERT(!global_scope.declared_variables.find_type(var.name)); + break; case Used_Variable_Kind::type: QLJS_ASSERT(!global_scope.declared_variables.find_type(var.name)); break; @@ -644,11 +657,11 @@ void Variable_Analyzer::propagate_variable_uses_to_parent_scope( Found_Variable_Type var = {}; switch (used_var.kind) { case Used_Variable_Kind::_export: + case Used_Variable_Kind::_export_default: QLJS_ASSERT(!current_scope.declared_variables.find(used_var.name)); var = parent_scope.declared_variables.find(used_var.name); break; case Used_Variable_Kind::_delete: - case Used_Variable_Kind::_export_default: case Used_Variable_Kind::_typeof: case Used_Variable_Kind::assignment: case Used_Variable_Kind::use: @@ -688,10 +701,10 @@ void Variable_Analyzer::propagate_variable_uses_to_parent_scope( Found_Variable_Type var = {}; switch (used_var.kind) { case Used_Variable_Kind::_export: + case Used_Variable_Kind::_export_default: var = parent_scope.declared_variables.find(used_var.name); break; case Used_Variable_Kind::_delete: - case Used_Variable_Kind::_export_default: case Used_Variable_Kind::_typeof: case Used_Variable_Kind::assignment: case Used_Variable_Kind::use: @@ -1174,10 +1187,10 @@ bool Variable_Analyzer::Used_Variable::is_runtime() const { bool Variable_Analyzer::Used_Variable::is_type() const { switch (this->kind) { case Used_Variable_Kind::_export: + case Used_Variable_Kind::_export_default: case Used_Variable_Kind::type: return true; case Used_Variable_Kind::_delete: - case Used_Variable_Kind::_export_default: case Used_Variable_Kind::_typeof: case Used_Variable_Kind::assignment: case Used_Variable_Kind::use: diff --git a/test/test-variable-analyzer-module.cpp b/test/test-variable-analyzer-module.cpp index a639646244..b405031874 100644 --- a/test/test-variable-analyzer-module.cpp +++ b/test/test-variable-analyzer-module.cpp @@ -155,6 +155,30 @@ TEST( u8"declare module 'm' { export default C; class C {} }"_sv, no_diags, typescript_analyze_options, default_globals); } + +TEST(Test_Variable_Analyzer_Module, + variable_export_can_export_typescript_types) { + test_parse_and_analyze(u8"interface I {}; export {I};"_sv, no_diags, + typescript_analyze_options, default_globals); + test_parse_and_analyze(u8"export {T}; type T = null;"_sv, no_diags, + typescript_analyze_options, default_globals); +} + +TEST(Test_Variable_Analyzer_Module, + export_default_can_export_typescript_types_after_declaration) { + test_parse_and_analyze(u8"interface I {}; export default I;"_sv, no_diags, + typescript_analyze_options, default_globals); + test_parse_and_analyze(u8"type T = null; export default T;"_sv, no_diags, + typescript_analyze_options, default_globals); +} + +TEST(Test_Variable_Analyzer_Module, + export_default_can_export_typescript_types_before_declaration) { + test_parse_and_analyze(u8"export default I; interface I {}"_sv, no_diags, + typescript_analyze_options, default_globals); + test_parse_and_analyze(u8"export default T; type T = null;"_sv, no_diags, + typescript_analyze_options, default_globals); +} } }