Skip to content

Commit 35b4365

Browse files
committed
feat(typescript): parse 'module ns { ... }'
1 parent 763fd54 commit 35b4365

18 files changed

+476
-66
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Semantic Versioning.
1515
* TypeScript: `declare class`, `declare abstract class`, `declare const`,
1616
`declare let`, `declare var`, `declare type`, `declare interface`,
1717
`declare function`, and `declare namespace` are now supported.
18+
* TypeScript: The old-style `module` syntax for namespaces is now supported.
1819
* `array[i, j]` now reports [E0450][] ("misleading use of ',' operator in
1920
index") (implemented by [Yunus][]).
2021
* `while (x > 0, y > 0)` now reports [E0451][] ("misleading use of ',' operator

po/de.po

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1134,8 +1134,10 @@ msgid "newline is not allowed after 'interface'"
11341134
msgstr ""
11351135

11361136
#: src/quick-lint-js/diag/diagnostic-types.h
1137-
msgid "newline is not allowed after 'namespace'"
1137+
#, fuzzy
1138+
msgid "newline is not allowed after '{0}'"
11381139
msgstr ""
1140+
"Mehrere Kommata zwischen den Argumenten eines Funktionsaufruf sind ungültig"
11391141

11401142
#: src/quick-lint-js/diag/diagnostic-types.h
11411143
msgid "newline is not allowed after 'type'"
@@ -1252,6 +1254,14 @@ msgstr "Vereinzeltes Komma in let-Statement"
12521254
msgid "stray comma in function parameter"
12531255
msgstr "Vereinzeltes Komme in Funktionsparameter"
12541256

1257+
#: src/quick-lint-js/diag/diagnostic-types.h
1258+
msgid "string module name is only allowed with 'declare module'"
1259+
msgstr ""
1260+
1261+
#: src/quick-lint-js/diag/diagnostic-types.h
1262+
msgid "module with string name is only allowed at the top level"
1263+
msgstr ""
1264+
12551265
#: src/quick-lint-js/diag/diagnostic-types.h
12561266
msgid "'this' must be the first parameter"
12571267
msgstr ""

po/[email protected]

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,8 +1091,9 @@ msgid "newline is not allowed after 'interface'"
10911091
msgstr ""
10921092

10931093
#: src/quick-lint-js/diag/diagnostic-types.h
1094-
msgid "newline is not allowed after 'namespace'"
1095-
msgstr ""
1094+
#, fuzzy
1095+
msgid "newline is not allowed after '{0}'"
1096+
msgstr "that's way too many commas 🚮"
10961097

10971098
#: src/quick-lint-js/diag/diagnostic-types.h
10981099
msgid "newline is not allowed after 'type'"
@@ -1202,6 +1203,14 @@ msgstr "throw that comma in the bin 🚮"
12021203
msgid "stray comma in function parameter"
12031204
msgstr "take that stray comma out back and have it meet its maker 🔫"
12041205

1206+
#: src/quick-lint-js/diag/diagnostic-types.h
1207+
msgid "string module name is only allowed with 'declare module'"
1208+
msgstr ""
1209+
1210+
#: src/quick-lint-js/diag/diagnostic-types.h
1211+
msgid "module with string name is only allowed at the top level"
1212+
msgstr ""
1213+
12051214
#: src/quick-lint-js/diag/diagnostic-types.h
12061215
msgid "'this' must be the first parameter"
12071216
msgstr ""

po/fr_FR.po

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,8 +1151,10 @@ msgid "newline is not allowed after 'interface'"
11511151
msgstr ""
11521152

11531153
#: src/quick-lint-js/diag/diagnostic-types.h
1154-
msgid "newline is not allowed after 'namespace'"
1154+
#, fuzzy
1155+
msgid "newline is not allowed after '{0}'"
11551156
msgstr ""
1157+
"',' supplémentaire non autorisé entre les arguments d'appel d'une fonction"
11561158

11571159
#: src/quick-lint-js/diag/diagnostic-types.h
11581160
msgid "newline is not allowed after 'type'"
@@ -1268,6 +1270,14 @@ msgstr "virgule isolée dans une instruction let"
12681270
msgid "stray comma in function parameter"
12691271
msgstr "virgule isolée dans un paramètre de fonction"
12701272

1273+
#: src/quick-lint-js/diag/diagnostic-types.h
1274+
msgid "string module name is only allowed with 'declare module'"
1275+
msgstr ""
1276+
1277+
#: src/quick-lint-js/diag/diagnostic-types.h
1278+
msgid "module with string name is only allowed at the top level"
1279+
msgstr ""
1280+
12711281
#: src/quick-lint-js/diag/diagnostic-types.h
12721282
msgid "'this' must be the first parameter"
12731283
msgstr ""

po/messages.pot

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ msgid "newline is not allowed after 'interface'"
10091009
msgstr ""
10101010

10111011
#: src/quick-lint-js/diag/diagnostic-types.h
1012-
msgid "newline is not allowed after 'namespace'"
1012+
msgid "newline is not allowed after '{0}'"
10131013
msgstr ""
10141014

10151015
#: src/quick-lint-js/diag/diagnostic-types.h
@@ -1113,6 +1113,14 @@ msgstr ""
11131113
msgid "stray comma in function parameter"
11141114
msgstr ""
11151115

1116+
#: src/quick-lint-js/diag/diagnostic-types.h
1117+
msgid "string module name is only allowed with 'declare module'"
1118+
msgstr ""
1119+
1120+
#: src/quick-lint-js/diag/diagnostic-types.h
1121+
msgid "module with string name is only allowed at the top level"
1122+
msgstr ""
1123+
11161124
#: src/quick-lint-js/diag/diagnostic-types.h
11171125
msgid "'this' must be the first parameter"
11181126
msgstr ""

po/pt_BR.po

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,8 +1043,9 @@ msgid "newline is not allowed after 'interface'"
10431043
msgstr "quebra de linha não é permitida após 'interface'"
10441044

10451045
#: src/quick-lint-js/diag/diagnostic-types.h
1046-
msgid "newline is not allowed after 'namespace'"
1047-
msgstr "quebra de linha não é permitida após 'namespace'"
1046+
#, fuzzy
1047+
msgid "newline is not allowed after '{0}'"
1048+
msgstr "quebra de linha não é permitida após 'type'"
10481049

10491050
#: src/quick-lint-js/diag/diagnostic-types.h
10501051
msgid "newline is not allowed after 'type'"
@@ -1150,6 +1151,14 @@ msgstr "vírgula extra em instrução let"
11501151
msgid "stray comma in function parameter"
11511152
msgstr "vírgula extra em parâmetro da função"
11521153

1154+
#: src/quick-lint-js/diag/diagnostic-types.h
1155+
msgid "string module name is only allowed with 'declare module'"
1156+
msgstr ""
1157+
1158+
#: src/quick-lint-js/diag/diagnostic-types.h
1159+
msgid "module with string name is only allowed at the top level"
1160+
msgstr ""
1161+
11531162
#: src/quick-lint-js/diag/diagnostic-types.h
11541163
msgid "'this' must be the first parameter"
11551164
msgstr "'this' precisa ser o primeiro parâmetro"
@@ -1843,6 +1852,9 @@ msgstr "esperado {1:singular}"
18431852
msgid "here"
18441853
msgstr "aqui"
18451854

1855+
#~ msgid "newline is not allowed after 'namespace'"
1856+
#~ msgstr "quebra de linha não é permitida após 'namespace'"
1857+
18461858
#, fuzzy
18471859
#~ msgid "',' is not expected between the mems in '['']'"
18481860
#~ msgstr "',' extra não é permitido entre membros de um enum"

po/sv_SE.po

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,8 +1083,9 @@ msgid "newline is not allowed after 'interface'"
10831083
msgstr ""
10841084

10851085
#: src/quick-lint-js/diag/diagnostic-types.h
1086-
msgid "newline is not allowed after 'namespace'"
1087-
msgstr ""
1086+
#, fuzzy
1087+
msgid "newline is not allowed after '{0}'"
1088+
msgstr "extra ',' är inte tillåtet mellan funktionkallelses argument"
10881089

10891090
#: src/quick-lint-js/diag/diagnostic-types.h
10901091
msgid "newline is not allowed after 'type'"
@@ -1195,6 +1196,14 @@ msgstr "vilset komma i let påstående"
11951196
msgid "stray comma in function parameter"
11961197
msgstr "ogiltig funktions parameter"
11971198

1199+
#: src/quick-lint-js/diag/diagnostic-types.h
1200+
msgid "string module name is only allowed with 'declare module'"
1201+
msgstr ""
1202+
1203+
#: src/quick-lint-js/diag/diagnostic-types.h
1204+
msgid "module with string name is only allowed at the top level"
1205+
msgstr ""
1206+
11981207
#: src/quick-lint-js/diag/diagnostic-types.h
11991208
msgid "'this' must be the first parameter"
12001209
msgstr ""

src/quick-lint-js/diag/diagnostic-types.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1619,7 +1619,7 @@
16191619
QLJS_DIAG_TYPE( \
16201620
diag_newline_not_allowed_after_namespace_keyword, "E0276", \
16211621
diagnostic_severity::error, { source_code_span namespace_keyword; }, \
1622-
MESSAGE(QLJS_TRANSLATABLE("newline is not allowed after 'namespace'"), \
1622+
MESSAGE(QLJS_TRANSLATABLE("newline is not allowed after '{0}'"), \
16231623
namespace_keyword)) \
16241624
\
16251625
QLJS_DIAG_TYPE( \
@@ -1799,6 +1799,20 @@
17991799
{ source_code_span comma; }, \
18001800
MESSAGE(QLJS_TRANSLATABLE("stray comma in function parameter"), comma)) \
18011801
\
1802+
QLJS_DIAG_TYPE( \
1803+
diag_string_namespace_name_is_only_allowed_with_declare_module, "E0359", \
1804+
diagnostic_severity::error, { source_code_span module_name; }, \
1805+
MESSAGE(QLJS_TRANSLATABLE("string module name is only allowed with " \
1806+
"'declare module'"), \
1807+
module_name)) \
1808+
\
1809+
QLJS_DIAG_TYPE( \
1810+
diag_string_namespace_name_is_only_allowed_at_top_level, "E0361", \
1811+
diagnostic_severity::error, { source_code_span module_name; }, \
1812+
MESSAGE(QLJS_TRANSLATABLE( \
1813+
"module with string name is only allowed at the top level"), \
1814+
module_name)) \
1815+
\
18021816
QLJS_DIAG_TYPE( \
18031817
diag_this_parameter_must_be_first, "E0303", diagnostic_severity::error, \
18041818
{ \

src/quick-lint-js/fe/parse-statement.cpp

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,6 @@ bool parser::parse_and_visit_statement(
372372
case token_type::kw_intrinsic:
373373
case token_type::kw_is:
374374
case token_type::kw_keyof:
375-
case token_type::kw_module:
376375
case token_type::kw_never:
377376
case token_type::kw_number:
378377
case token_type::kw_object:
@@ -423,6 +422,7 @@ bool parser::parse_and_visit_statement(
423422
// interface * x;
424423
// interface I {} // TypeScript only.
425424
case token_type::kw_interface:
425+
case token_type::kw_module:
426426
case token_type::kw_namespace:
427427
case token_type::kw_type:
428428
switch (
@@ -622,11 +622,14 @@ parser::parse_and_visit_typescript_interface_or_namespace_or_type_statement(
622622

623623
// type T = number; // TypeScript only.
624624
//
625+
// namespace 'my namespace name' {} // Invalid.
626+
//
625627
// type // ASI
626628
// f();
627629
QLJS_CASE_CONTEXTUAL_KEYWORD:
628630
case token_type::kw_await:
629631
case token_type::identifier:
632+
case token_type::string:
630633
if (this->peek().has_leading_newline) {
631634
bool is_expression = true;
632635
if (initial_keyword.type == token_type::kw_interface) {
@@ -657,7 +660,8 @@ parser::parse_and_visit_typescript_interface_or_namespace_or_type_statement(
657660
});
658661
}
659662
}
660-
if (initial_keyword.type == token_type::kw_namespace) {
663+
if (initial_keyword.type == token_type::kw_module ||
664+
initial_keyword.type == token_type::kw_namespace) {
661665
lexer_transaction inner_transaction = this->lexer_.begin_transaction();
662666
this->skip();
663667
if (this->peek().type == token_type::left_curly &&
@@ -679,8 +683,11 @@ parser::parse_and_visit_typescript_interface_or_namespace_or_type_statement(
679683
case token_type::kw_interface:
680684
this->parse_and_visit_typescript_interface(v, initial_keyword.span());
681685
break;
686+
case token_type::kw_module:
682687
case token_type::kw_namespace:
683-
this->parse_and_visit_typescript_namespace(v, initial_keyword.span());
688+
this->parse_and_visit_typescript_namespace(
689+
v,
690+
/*export_keyword_span=*/std::nullopt, initial_keyword.span());
684691
break;
685692
case token_type::kw_type:
686693
this->parse_and_visit_typescript_type_alias(v, initial_keyword.span());
@@ -1100,10 +1107,13 @@ void parser::parse_and_visit_export(parse_visitor_base &v) {
11001107
break;
11011108

11021109
// export namespace ns {} // TypeScript only.
1110+
case token_type::kw_module:
11031111
case token_type::kw_namespace: {
11041112
source_code_span namespace_keyword = this->peek().span();
11051113
this->skip();
1106-
this->parse_and_visit_typescript_namespace(v, namespace_keyword);
1114+
this->parse_and_visit_typescript_namespace(
1115+
v,
1116+
/*export_keyword_span=*/export_token_span, namespace_keyword);
11071117
break;
11081118
}
11091119

@@ -2016,8 +2026,14 @@ void parser::parse_and_visit_switch(parse_visitor_base &v) {
20162026
}
20172027

20182028
void parser::parse_and_visit_typescript_namespace(
2019-
parse_visitor_base &v, source_code_span namespace_keyword_span) {
2020-
this->parse_and_visit_typescript_namespace_head(v, namespace_keyword_span);
2029+
parse_visitor_base &v, std::optional<source_code_span> export_keyword_span,
2030+
source_code_span namespace_keyword_span) {
2031+
this->parse_and_visit_typescript_namespace_head(
2032+
v,
2033+
/*export_keyword_span=*/export_keyword_span,
2034+
/*declare_keyword_span=*/std::nullopt, namespace_keyword_span);
2035+
2036+
typescript_namespace_guard g = this->enter_typescript_namespace();
20212037

20222038
if (this->peek().type != token_type::left_curly) {
20232039
this->diag_reporter_->report(diag_missing_body_for_typescript_namespace{
@@ -2033,7 +2049,9 @@ void parser::parse_and_visit_typescript_namespace(
20332049
}
20342050

20352051
void parser::parse_and_visit_typescript_namespace_head(
2036-
parse_visitor_base &v, source_code_span namespace_keyword_span) {
2052+
parse_visitor_base &v, std::optional<source_code_span> export_keyword_span,
2053+
std::optional<source_code_span> declare_keyword_span,
2054+
source_code_span namespace_keyword_span) {
20372055
if (this->peek().has_leading_newline) {
20382056
this->diag_reporter_->report(
20392057
diag_newline_not_allowed_after_namespace_keyword{
@@ -2048,6 +2066,7 @@ void parser::parse_and_visit_typescript_namespace_head(
20482066
}
20492067

20502068
switch (this->peek().type) {
2069+
// namespace ns { }
20512070
QLJS_CASE_CONTEXTUAL_KEYWORD:
20522071
case token_type::identifier:
20532072
v.visit_variable_declaration(this->peek().identifier_name(),
@@ -2056,6 +2075,27 @@ void parser::parse_and_visit_typescript_namespace_head(
20562075
this->skip();
20572076
break;
20582077

2078+
// module 'my namespace' { }
2079+
// namespace 'ns' { } // Invalid.
2080+
case token_type::string: {
2081+
bool namespace_keyword_is_module =
2082+
namespace_keyword_span.string_view()[0] == u8'm';
2083+
if (!namespace_keyword_is_module || !declare_keyword_span.has_value() ||
2084+
export_keyword_span.has_value()) {
2085+
this->diag_reporter_->report(
2086+
diag_string_namespace_name_is_only_allowed_with_declare_module{
2087+
.module_name = this->peek().span(),
2088+
});
2089+
} else if (this->in_typescript_namespace_) {
2090+
this->diag_reporter_->report(
2091+
diag_string_namespace_name_is_only_allowed_at_top_level{
2092+
.module_name = this->peek().span(),
2093+
});
2094+
}
2095+
this->skip();
2096+
break;
2097+
}
2098+
20592099
default:
20602100
QLJS_PARSER_UNIMPLEMENTED();
20612101
break;
@@ -2064,10 +2104,16 @@ void parser::parse_and_visit_typescript_namespace_head(
20642104

20652105
void parser::parse_and_visit_typescript_declare_namespace(
20662106
parse_visitor_base &v, source_code_span declare_keyword_span) {
2067-
QLJS_ASSERT(this->peek().type == token_type::kw_namespace);
2107+
QLJS_ASSERT(this->peek().type == token_type::kw_module ||
2108+
this->peek().type == token_type::kw_namespace);
20682109
source_code_span namespace_keyword_span = this->peek().span();
20692110
this->skip();
2070-
this->parse_and_visit_typescript_namespace_head(v, namespace_keyword_span);
2111+
this->parse_and_visit_typescript_namespace_head(
2112+
v,
2113+
/*export_keyword_span=*/std::nullopt,
2114+
/*declare_keyword_span=*/declare_keyword_span, namespace_keyword_span);
2115+
2116+
typescript_namespace_guard g = this->enter_typescript_namespace();
20712117

20722118
if (this->peek().type != token_type::left_curly) {
20732119
this->diag_reporter_->report(diag_missing_body_for_typescript_namespace{
@@ -2091,6 +2137,7 @@ void parser::parse_and_visit_typescript_declare_namespace(
20912137
case token_type::kw_enum:
20922138
case token_type::kw_function:
20932139
case token_type::kw_let:
2140+
case token_type::kw_module:
20942141
case token_type::kw_namespace:
20952142
case token_type::kw_var:
20962143
this->parse_and_visit_declare_statement(v, declare_keyword_span);
@@ -4558,6 +4605,7 @@ parser::parse_and_visit_possible_declare_statement(parse_visitor_base &v) {
45584605
case token_type::kw_type:
45594606
case token_type::kw_var:
45604607
case token_type::kw_function:
4608+
case token_type::kw_module:
45614609
case token_type::kw_namespace:
45624610
this->lexer_.commit_transaction(std::move(transaction));
45634611
this->parse_and_visit_declare_statement(v, declare_keyword_span);
@@ -4760,6 +4808,7 @@ void parser::parse_and_visit_declare_statement(
47604808
}
47614809

47624810
// declare namespace ns {}
4811+
case token_type::kw_module:
47634812
case token_type::kw_namespace:
47644813
this->parse_and_visit_typescript_declare_namespace(v, declare_keyword_span);
47654814
break;

src/quick-lint-js/fe/parse.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ parser::switch_guard parser::enter_switch() {
101101
return switch_guard(this, std::exchange(this->in_switch_statement_, true));
102102
}
103103

104+
parser::typescript_namespace_guard parser::enter_typescript_namespace() {
105+
return typescript_namespace_guard(
106+
this, std::exchange(this->in_typescript_namespace_, true));
107+
}
108+
104109
parser::binary_expression_builder::binary_expression_builder(
105110
monotonic_allocator* allocator, expression* first_child)
106111
: children_("binary_expression_builder children", allocator),

0 commit comments

Comments
 (0)