From 24040d4e3a5e3c7da8ca08f719d32491fe768d25 Mon Sep 17 00:00:00 2001 From: Cuong Do Date: Fri, 15 Mar 2024 21:37:16 -0400 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 64c3213a79..fa51160d0b 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -1733,6 +1733,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 33cd307f45..6936a200b4 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -4654,6 +4654,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 bebd42ccd6..9ac61cc112 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -320,6 +320,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) \ @@ -476,7 +477,7 @@ namespace quick_lint_js { /* END */ // clang-format on -inline constexpr int Diag_Type_Count = 462; +inline constexpr int Diag_Type_Count = 463; 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 da49d06021..b18eece4bb 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -2417,6 +2417,16 @@ struct Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript { 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; + Source_Code_Span equal; +}; + struct Diag_TypeScript_Index_Signature_Cannot_Be_Method { [[qljs::diag("E0227", Diagnostic_Severity::error)]] // [[qljs::message("index signature must be a field, not a method", diff --git a/src/quick-lint-js/fe/parse-statement.cpp b/src/quick-lint-js/fe/parse-statement.cpp index 740a82f04b..ad362b8ec6 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) { + Source_Code_Span equal_span = this->peek().span(); this->skip(); switch (this->peek().type) { QLJS_CASE_CONTEXTUAL_KEYWORD: @@ -4845,6 +4839,13 @@ void Parser::parse_and_visit_import( // import fs = require("fs"); // import type fs = require("fs"); + if (!this->options_.typescript) { + this->diag_reporter_->report( + Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript{ + .import_keyword = import_span, + .equal = equal_span, + }); + } // NOTE[TypeScript-type-import-alias]: 'import fs = ' and // 'import type fs = ...' both declare variables which conflict with // 'let', 'class', etc. Overwrite Variable_Kind::_import_type. @@ -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 0102a53d8a..16bb45837f 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}, // @@ -2016,6 +2017,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 d780d0d640..d9dc68712e 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 = 606; -constexpr std::size_t translation_table_string_table_size = 82441; +constexpr std::uint16_t translation_table_mapping_table_size = 607; +constexpr std::size_t translation_table_string_table_size = 82500; 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 1349a6b0b1..9a310033fa 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[605] = { +inline const Translated_String test_translation_table[606] = { { "\"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[605] = { 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 22ff27293b..8dd54705ae 100644 --- a/test/test-parse-typescript-export-declare.cpp +++ b/test/test-parse-typescript-export-declare.cpp @@ -71,7 +71,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 b931b6a795..ab0abbc256 100644 --- a/test/test-parse-typescript-module.cpp +++ b/test/test-parse-typescript-module.cpp @@ -830,7 +830,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); @@ -845,7 +845,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 e5d2d01671..fc75f4717d 100644 --- a/test/test-parse-typescript-namespace.cpp +++ b/test/test-parse-typescript-namespace.cpp @@ -657,7 +657,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({ @@ -667,6 +667,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) { { @@ -721,7 +735,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); @@ -762,7 +776,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, // @@ -778,7 +792,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); @@ -794,7 +808,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 3b0f4d90d2..39371237c2 100644 --- a/test/test-variable-analyzer-assign.cpp +++ b/test/test-variable-analyzer-assign.cpp @@ -193,7 +193,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 a998ddd3ce..cdc20e341d 100644 --- a/test/test-variable-analyzer-multiple-declarations.cpp +++ b/test/test-variable-analyzer-multiple-declarations.cpp @@ -355,7 +355,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, @@ -375,7 +375,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 @@ -400,7 +400,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 @@ -409,7 +409,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 @@ -423,7 +423,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