Skip to content

Commit

Permalink
feat: add new diag "namespace alias" in javascript code
Browse files Browse the repository at this point in the history
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
  • Loading branch information
UnfairBots authored and strager committed Mar 16, 2024
1 parent 195cd69 commit 24040d4
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 46 deletions.
19 changes: 4 additions & 15 deletions docs/errors/E0274.md
Original file line number Diff line number Diff line change
@@ -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.
25 changes: 25 additions & 0 deletions docs/errors/E0719.md
Original file line number Diff line number Diff line change
@@ -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;
```
4 changes: 4 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
18 changes: 18 additions & 0 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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];
}
Expand Down
10 changes: 10 additions & 0 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 22 additions & 12 deletions src/quick-lint-js/fe/parse-statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token> declared_variable = std::nullopt;
Variable_Kind declared_variable_kind = Variable_Kind::_import;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -4776,7 +4777,7 @@ void Parser::parse_and_visit_import(
break;
}
} else {
possibly_typescript_import_alias = true;
possibly_typescript_import_namespace_alias = true;
}
break;

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}, //
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 3 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 = 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(
Expand Down Expand 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,
Expand Down
13 changes: 12 additions & 1 deletion src/quick-lint-js/i18n/translation-table-test-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion test/test-parse-typescript-export-declare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, //
Expand Down
4 changes: 2 additions & 2 deletions test/test-parse-typescript-module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
24 changes: 19 additions & 5 deletions test/test-parse-typescript-namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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) {
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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, //
Expand All @@ -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);
Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion test/test-variable-analyzer-assign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 24040d4

Please sign in to comment.