From 9e773ad19f41cac5ecea9b0739b130fa15a78984 Mon Sep 17 00:00:00 2001 From: Cuong Do Date: Sat, 27 Jan 2024 22:19:11 +0700 Subject: [PATCH] feat: add new diag "namespace alias" in javascript code Summary: Add new diag E0719, using Typescript namespace alias in javascript code to separate E0274, rename some test functions from import_alias to namespace_alias This closes #1138 --- docs/errors/E0274.md | 19 +++-------- docs/errors/E0719.md | 25 ++++++++++++++ po/messages.pot | 4 +++ .../diag/diagnostic-metadata-generated.cpp | 18 ++++++++++ .../diag/diagnostic-metadata-generated.h | 3 +- src/quick-lint-js/diag/diagnostic-types-2.h | 10 ++++++ src/quick-lint-js/fe/parse-statement.cpp | 34 ++++++++++++------- .../i18n/translation-table-generated.cpp | 4 ++- .../i18n/translation-table-generated.h | 5 +-- .../i18n/translation-table-test-generated.h | 13 ++++++- test/test-parse-typescript-export-declare.cpp | 2 +- test/test-parse-typescript-module.cpp | 4 +-- test/test-parse-typescript-namespace.cpp | 24 ++++++++++--- test/test-variable-analyzer-assign.cpp | 3 +- ...ariable-analyzer-multiple-declarations.cpp | 10 +++--- 15 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 docs/errors/E0719.md diff --git a/docs/errors/E0274.md b/docs/errors/E0274.md index d8bbde7a05..56a82aae22 100644 --- a/docs/errors/E0274.md +++ b/docs/errors/E0274.md @@ -1,27 +1,16 @@ # E0274: TypeScript import aliases are not allowed in JavaScript -```config-for-examples -{ - "globals": { - "goog": true - } -} -``` - -TypeScript supports aliasing namespaces and namespace members using `import`. It -is a syntax error to write such an alias in JavaScript code: +TypeScript supports import alias using `import`. It is +a syntax error to write such an alias in JavaScript code: ```javascript -import Chart = goog.Chart; +import m = require('mod'); ``` To fix this error, use `const` instead of `import`: ```javascript -const Chart = goog.Chart; -``` -```javascript -const { Chart } = goog; +const m = require('mod'); ``` Introduced in quick-lint-js version 2.8.0. diff --git a/docs/errors/E0719.md b/docs/errors/E0719.md new file mode 100644 index 0000000000..4da07e7909 --- /dev/null +++ b/docs/errors/E0719.md @@ -0,0 +1,25 @@ +# E0719: TypeScript namespace aliases are not allowed in JavaScript + +```config-for-examples +{ + "globals": { + "goog": true + } +} +``` + +TypeScript supports aliasing namespaces and namespace members using `import`. It +is a syntax error to write such an alias in JavaScript code: + +```javascript +import Chart = goog.Chart; +``` + +To fix this error, use `const` instead of `import`: + +```javascript +const Chart = goog.Chart; +``` +```javascript +const { Chart } = goog; +``` diff --git a/po/messages.pot b/po/messages.pot index 94c27e0037..4d849507ba 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -1729,6 +1729,10 @@ msgstr "" msgid "write 'const' instead of '{0}' here" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "TypeScript namespace aliases are not allowed in JavaScript" +msgstr "" + #: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp msgid "index signature must be a field, not a method" msgstr "" diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp index 4abe4aea0d..0cc2b8c4ac 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -4637,6 +4637,24 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = { }, }, + // Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript + { + .code = 719, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("TypeScript namespace aliases are not allowed in JavaScript"), + QLJS_TRANSLATABLE("write 'const' instead of '{0}' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript, equal), Diagnostic_Arg_Type::source_code_span), + }, + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript, import_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + // Diag_TypeScript_Index_Signature_Cannot_Be_Method { .code = 227, diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.h b/src/quick-lint-js/diag/diagnostic-metadata-generated.h index b8569eef2c..769f09fcbe 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -319,6 +319,7 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Export_Equal_Not_Allowed_In_JavaScript) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Implements_Must_Be_After_Extends) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript) \ + QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Index_Signature_Cannot_Be_Method) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Index_Signature_Needs_Type) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Infer_Outside_Conditional_Type) \ @@ -475,7 +476,7 @@ namespace quick_lint_js { /* END */ // clang-format on -inline constexpr int Diag_Type_Count = 461; +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 850dca9f0d..92eb81ab27 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -2401,6 +2401,16 @@ struct Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript { [[qljs::diag("E0274", Diagnostic_Severity::error)]] // [[qljs::message("TypeScript import aliases are not allowed in JavaScript", ARG(equal))]] // + [[qljs::message("write 'const' instead of '{0}' here", + ARG(import_keyword))]] // + Source_Code_Span import_keyword; + Source_Code_Span equal; +}; + +struct Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript { + [[qljs::diag("E0719", Diagnostic_Severity::error)]] // + [[qljs::message("TypeScript namespace aliases are not allowed in JavaScript", + ARG(equal))]] // [[qljs::message("write 'const' instead of '{0}' here", ARG(import_keyword))]] // Source_Code_Span import_keyword; diff --git a/src/quick-lint-js/fe/parse-statement.cpp b/src/quick-lint-js/fe/parse-statement.cpp index 740a82f04b..8712690703 100644 --- a/src/quick-lint-js/fe/parse-statement.cpp +++ b/src/quick-lint-js/fe/parse-statement.cpp @@ -4566,7 +4566,7 @@ void Parser::parse_and_visit_import( Source_Code_Span import_span = this->peek().span(); this->skip(); - bool possibly_typescript_import_alias = false; + bool possibly_typescript_import_namespace_alias = false; // For 'import fs from "node:fs";', declared_variable is 'fs'. std::optional declared_variable = std::nullopt; Variable_Kind declared_variable_kind = Variable_Kind::_import; @@ -4671,8 +4671,9 @@ void Parser::parse_and_visit_import( } } else { // import fs from "fs"; + // import myns = ns; // import fs = require("fs"); // TypeScript only. - possibly_typescript_import_alias = true; + possibly_typescript_import_namespace_alias = true; } break; @@ -4776,7 +4777,7 @@ void Parser::parse_and_visit_import( break; } } else { - possibly_typescript_import_alias = true; + possibly_typescript_import_namespace_alias = true; } break; @@ -4825,15 +4826,8 @@ void Parser::parse_and_visit_import( // import myns = ns; // TypeScript only. // import C = ns.C; // TypeScript only. case Token_Type::equal: - if (possibly_typescript_import_alias) { - if (!this->options_.typescript) { - this->diag_reporter_->report( - Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript{ - .import_keyword = import_span, - .equal = this->peek().span(), - }); - } - + if (possibly_typescript_import_namespace_alias) { + std::optional equal_span = this->peek().span(); this->skip(); switch (this->peek().type) { QLJS_CASE_CONTEXTUAL_KEYWORD: @@ -4851,6 +4845,13 @@ void Parser::parse_and_visit_import( // // FIXME(strager): Should this behave like an import or an import // alias or some other kind? + if (!this->options_.typescript) { + this->diag_reporter_->report( + Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript{ + .import_keyword = import_span, + .equal = *equal_span, + }); + } declared_variable_kind = Variable_Kind::_import; declare_variable_if_needed(); if (declare_context.declare_namespace_declare_keyword.has_value() && @@ -4875,6 +4876,15 @@ void Parser::parse_and_visit_import( this->diag_reporter_->report( Diag_TypeScript_Namespace_Alias_Cannot_Use_Import_Type{ .type_keyword = *type_span}); + } else { + if (!this->options_.typescript) { + // import a = b; // Invalid. + this->diag_reporter_->report( + Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript{ + .import_keyword = import_span, + .equal = *equal_span, + }); + } } // import myns = ns; // import C = ns.C; diff --git a/src/quick-lint-js/i18n/translation-table-generated.cpp b/src/quick-lint-js/i18n/translation-table-generated.cpp index e224f00985..ce7c16ad4e 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.cpp +++ b/src/quick-lint-js/i18n/translation-table-generated.cpp @@ -144,7 +144,8 @@ const Translation_Table translation_data = { {0, 45, 0, 77, 0, 61}, // {0, 0, 0, 61, 0, 51}, // {0, 44, 0, 104, 0, 81}, // - {0, 0, 0, 65, 0, 51}, // + {0, 0, 0, 0, 0, 51}, // + {0, 0, 0, 65, 0, 59}, // {0, 0, 0, 60, 0, 52}, // {0, 0, 0, 84, 0, 59}, // {0, 0, 0, 83, 0, 61}, // @@ -2015,6 +2016,7 @@ const Translation_Table translation_data = { u8"TypeScript interface methods cannot contain a body\0" u8"TypeScript interface properties are always public and cannot be marked protected\0" u8"TypeScript interface properties cannot be 'static'\0" + u8"TypeScript namespace aliases are not allowed in JavaScript\0" u8"TypeScript namespaces are not allowed in JavaScript\0" u8"TypeScript non-null assertion is not allowed on parameters\0" u8"TypeScript non-null assertions are not allowed in JavaScript\0" diff --git a/src/quick-lint-js/i18n/translation-table-generated.h b/src/quick-lint-js/i18n/translation-table-generated.h index 881b73ee6b..c3b00f7034 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 = 605; -constexpr std::size_t translation_table_string_table_size = 82408; +constexpr std::uint16_t translation_table_mapping_table_size = 606; +constexpr std::size_t translation_table_string_table_size = 82467; constexpr std::size_t translation_table_locale_table_size = 35; QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( @@ -159,6 +159,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "TypeScript interface methods cannot contain a body"sv, "TypeScript interface properties are always public and cannot be marked protected"sv, "TypeScript interface properties cannot be 'static'"sv, + "TypeScript namespace aliases are not allowed in JavaScript"sv, "TypeScript namespaces are not allowed in JavaScript"sv, "TypeScript non-null assertion is not allowed on parameters"sv, "TypeScript non-null assertions are not allowed in JavaScript"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 c56c46e69a..ef0ba76e88 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[604] = { +inline const Translated_String test_translation_table[605] = { { "\"global-groups\" entries must be strings"_translatable, u8"\"global-groups\" entries must be strings", @@ -1491,6 +1491,17 @@ inline const Translated_String test_translation_table[604] = { u8"TypeScript interface properties cannot be 'static'", }, }, + { + "TypeScript namespace aliases are not allowed in JavaScript"_translatable, + u8"TypeScript namespace aliases are not allowed in JavaScript", + { + u8"TypeScript namespace aliases are not allowed in JavaScript", + u8"TypeScript namespace aliases are not allowed in JavaScript", + u8"TypeScript namespace aliases are not allowed in JavaScript", + u8"TypeScript namespace aliases are not allowed in JavaScript", + u8"TypeScript namespace aliases are not allowed in JavaScript", + }, + }, { "TypeScript namespaces are not allowed in JavaScript"_translatable, u8"TypeScript namespaces are not allowed in JavaScript", diff --git a/test/test-parse-typescript-export-declare.cpp b/test/test-parse-typescript-export-declare.cpp index b6d12b8544..7612b2d35c 100644 --- a/test/test-parse-typescript-export-declare.cpp +++ b/test/test-parse-typescript-export-declare.cpp @@ -72,7 +72,7 @@ TEST_F(Test_Parse_TypeScript_Export_Declare, export_declare_abstract_class) { } TEST_F(Test_Parse_TypeScript_Export_Declare, - export_declare_import_alias_is_not_allowed) { + export_declare_namespace_alias_is_not_allowed) { test_parse_and_visit_module( u8"export declare import A = B;"_sv, // u8" ^^^^^^^ Diag_Import_Cannot_Have_Declare_Keyword"_diag, // diff --git a/test/test-parse-typescript-module.cpp b/test/test-parse-typescript-module.cpp index 691bbf1ec7..a69d1b11ab 100644 --- a/test/test-parse-typescript-module.cpp +++ b/test/test-parse-typescript-module.cpp @@ -831,7 +831,7 @@ TEST_F(Test_Parse_TypeScript_Module, } } -TEST_F(Test_Parse_TypeScript_Module, export_import_alias) { +TEST_F(Test_Parse_TypeScript_Module, export_namespace_alias) { { Spy_Visitor p = test_parse_and_visit_module(u8"export import A = B;"_sv, no_diags, typescript_options); @@ -846,7 +846,7 @@ TEST_F(Test_Parse_TypeScript_Module, export_import_alias) { } TEST_F(Test_Parse_TypeScript_Module, - export_import_alias_cannot_be_named_certain_keywords) { + export_namespace_alias_cannot_be_named_certain_keywords) { // TODO[TypeScript-export-namespace-alias-keyword-name]: Disallow 'await', // 'implements', etc. (strict_reserved_keywords). for (String8_View keyword : disallowed_binding_identifier_keywords) { diff --git a/test/test-parse-typescript-namespace.cpp b/test/test-parse-typescript-namespace.cpp index 3272d5ea9b..23f0086064 100644 --- a/test/test-parse-typescript-namespace.cpp +++ b/test/test-parse-typescript-namespace.cpp @@ -658,7 +658,7 @@ TEST_F(Test_Parse_TypeScript_Namespace, { Spy_Visitor p = test_parse_and_visit_statement( u8"import A = ns;"_sv, // - u8"^^^^^^ Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript.import_keyword\n"_diag + u8"^^^^^^ Diag_TypeScript_Namespace_Alias_Not_Allowed_In_JavaScript.import_keyword\n"_diag u8" ^ .equal"_diag, // javascript_options); EXPECT_THAT(p.visits, ElementsAreArray({ @@ -668,6 +668,20 @@ TEST_F(Test_Parse_TypeScript_Namespace, } } +TEST_F(Test_Parse_TypeScript_Namespace, + import_alias_not_allowed_in_javascript) { + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"import A = require('ns');"_sv, // + u8"^^^^^^ Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript.import_keyword\n"_diag + u8" ^ .equal"_diag, // + javascript_options); + EXPECT_THAT(p.visits, ElementsAreArray({ + "visit_variable_declaration", // A + })); + } +} + TEST_F(Test_Parse_TypeScript_Namespace, namespace_alias_cannot_use_import_type) { { @@ -722,7 +736,7 @@ TEST_F(Test_Parse_TypeScript_Namespace, } } -TEST_F(Test_Parse_TypeScript_Namespace, import_alias_of_namespace_member) { +TEST_F(Test_Parse_TypeScript_Namespace, namespace_alias_of_namespace_member) { { Spy_Visitor p = test_parse_and_visit_module(u8"import A = ns.B;"_sv, no_diags, typescript_options); @@ -763,7 +777,7 @@ TEST_F(Test_Parse_TypeScript_Namespace, import_alias_of_namespace_member) { } TEST_F(Test_Parse_TypeScript_Namespace, - import_alias_requires_semicolon_or_newline) { + namespace_alias_requires_semicolon_or_newline) { { Spy_Visitor p = test_parse_and_visit_module( u8"import A = ns nextStatement"_sv, // @@ -779,7 +793,7 @@ TEST_F(Test_Parse_TypeScript_Namespace, } TEST_F(Test_Parse_TypeScript_Namespace, - namespace_can_be_contextual_keyword_in_import_alias) { + namespace_can_be_contextual_keyword_in_namespace_alias) { for (String8 name : contextual_keywords) { Padded_String code(concat(u8"import A = "_sv, name, u8".Member;"_sv)); SCOPED_TRACE(code); @@ -795,7 +809,7 @@ TEST_F(Test_Parse_TypeScript_Namespace, } TEST_F(Test_Parse_TypeScript_Namespace, - namespace_member_can_be_contextual_keyword_in_import_alias) { + namespace_member_can_be_contextual_keyword_in_namespace_alias) { for (String8 name : contextual_keywords) { Padded_String code(concat(u8"import A = ns."_sv, name, u8";"_sv)); SCOPED_TRACE(code); diff --git a/test/test-variable-analyzer-assign.cpp b/test/test-variable-analyzer-assign.cpp index 1593d82627..c03e2f8682 100644 --- a/test/test-variable-analyzer-assign.cpp +++ b/test/test-variable-analyzer-assign.cpp @@ -194,7 +194,8 @@ TEST(Test_Variable_Analyzer_Assign, cannot_assign_to_typescript_enum) { typescript_analyze_options, default_globals); } -TEST(Test_Variable_Analyzer_Assign, cannot_assign_to_typescript_import_alias) { +TEST(Test_Variable_Analyzer_Assign, + cannot_assign_to_typescript_namespace_alias) { test_parse_and_analyze( u8"namespace A {} import B = A; B = null;"_sv, // u8" ^ Diag_Assignment_To_Const_Variable.assignment\n"_diag diff --git a/test/test-variable-analyzer-multiple-declarations.cpp b/test/test-variable-analyzer-multiple-declarations.cpp index 4ce7ed2bc3..0659ae1170 100644 --- a/test/test-variable-analyzer-multiple-declarations.cpp +++ b/test/test-variable-analyzer-multiple-declarations.cpp @@ -356,7 +356,7 @@ TEST(Test_Variable_Analyzer_Multiple_Declarations, } TEST(Test_Variable_Analyzer_Multiple_Declarations, - import_alias_does_not_conflict_with_most_other_things) { + namespace_alias_does_not_conflict_with_most_other_things) { for (String8_View other_thing : { u8"class A {}"_sv, u8"const A = 42;"_sv, @@ -376,7 +376,7 @@ TEST(Test_Variable_Analyzer_Multiple_Declarations, } TEST(Test_Variable_Analyzer_Multiple_Declarations, - import_alias_conflicts_with_import) { + import_namespace_alias_conflicts_with_import) { test_parse_and_analyze( u8"namespace ns {} import A = ns; import A from 'mod';"_sv, // u8" ^ Diag_Redeclaration_Of_Variable.redeclaration\n"_diag @@ -401,7 +401,7 @@ TEST(Test_Variable_Analyzer_Multiple_Declarations, } TEST(Test_Variable_Analyzer_Multiple_Declarations, - import_alias_conflicts_with_import_alias) { + namespace_alias_conflicts_with_namespace_alias) { test_parse_and_analyze( u8"namespace ns1 {} namespace ns2 {} import A = ns1; import A = ns2;"_sv, // u8" ^ Diag_Redeclaration_Of_Variable.redeclaration\n"_diag @@ -410,7 +410,7 @@ TEST(Test_Variable_Analyzer_Multiple_Declarations, } TEST(Test_Variable_Analyzer_Multiple_Declarations, - import_alias_conflicts_with_namespace) { + namespace_alias_conflicts_with_namespace) { test_parse_and_analyze( u8"namespace ns {} import A = ns; namespace A {}"_sv, // u8" ^ Diag_Redeclaration_Of_Variable.redeclaration\n"_diag @@ -424,7 +424,7 @@ TEST(Test_Variable_Analyzer_Multiple_Declarations, } TEST(Test_Variable_Analyzer_Multiple_Declarations, - import_alias_conflicts_with_enum) { + namespace_alias_conflicts_with_enum) { test_parse_and_analyze( u8"namespace ns {} import A = ns; enum A {}"_sv, // u8" ^ Diag_Redeclaration_Of_Variable.redeclaration\n"_diag