From 34b422bafbd934ee3c644fd7a8f0b6803976c818 Mon Sep 17 00:00:00 2001 From: Jan Kuhle Date: Wed, 10 May 2023 15:11:50 +0200 Subject: [PATCH] clang-format: [JS] support import/export type Contributed by @jankuehle! Users can choose to only import/export the type of the symbol (not value nor namespace) by adding a `type` keyword, e.g.: ``` import type {x} from 'y'; import {type x} from 'y'; export type {x}; export {type x}; ``` Previously, this was not handled and would: - Terminate import sorting - Remove the space before the curly bracket in `export type {` With this change, both formatting and import sorting work as expected. Reviewed By: MyDeveloperDay, krasimir Differential Revision: https://reviews.llvm.org/D150116 --- clang/lib/Format/SortJavaScriptImports.cpp | 22 +++++++++++++-- clang/lib/Format/UnwrappedLineParser.cpp | 4 ++- clang/unittests/Format/FormatTestJS.cpp | 11 ++++++++ clang/unittests/Format/SortImportsTestJS.cpp | 40 ++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/clang/lib/Format/SortJavaScriptImports.cpp b/clang/lib/Format/SortJavaScriptImports.cpp index ce83dde..af2d340 100644 --- a/clang/lib/Format/SortJavaScriptImports.cpp +++ b/clang/lib/Format/SortJavaScriptImports.cpp @@ -72,6 +72,7 @@ struct JsImportedSymbol { struct JsModuleReference { bool FormattingOff = false; bool IsExport = false; + bool IsTypeOnly = false; // Module references are sorted into these categories, in order. enum ReferenceCategory { SIDE_EFFECT, // "import 'something';" @@ -306,6 +307,7 @@ private: if (Reference->Category == JsModuleReference::SIDE_EFFECT || PreviousReference->Category == JsModuleReference::SIDE_EFFECT || Reference->IsExport != PreviousReference->IsExport || + Reference->IsTypeOnly != PreviousReference->IsTypeOnly || !PreviousReference->Prefix.empty() || !Reference->Prefix.empty() || !PreviousReference->DefaultImport.empty() || !Reference->DefaultImport.empty() || Reference->Symbols.empty() || @@ -488,6 +490,11 @@ private: bool parseStarBinding(const AdditionalKeywords &Keywords, JsModuleReference &Reference) { // * as prefix from '...'; + if (Current->is(Keywords.kw_type) && Current->Next && + Current->Next->is(tok::star)) { + Reference.IsTypeOnly = true; + nextToken(); + } if (Current->isNot(tok::star)) return false; nextToken(); @@ -503,6 +510,12 @@ private: bool parseNamedBindings(const AdditionalKeywords &Keywords, JsModuleReference &Reference) { + if (Current->is(Keywords.kw_type) && Current->Next && + Current->Next->isOneOf(tok::identifier, tok::l_brace)) { + Reference.IsTypeOnly = true; + nextToken(); + } + // eat a potential "import X, " prefix. if (Current->is(tok::identifier)) { Reference.DefaultImport = Current->TokenText; @@ -535,14 +548,19 @@ private: nextToken(); if (Current->is(tok::r_brace)) break; - if (!Current->isOneOf(tok::identifier, tok::kw_default)) + bool isTypeOnly = + Current->is(Keywords.kw_type) && Current->Next && + Current->Next->isOneOf(tok::identifier, tok::kw_default); + if (!isTypeOnly && !Current->isOneOf(tok::identifier, tok::kw_default)) return false; JsImportedSymbol Symbol; - Symbol.Symbol = Current->TokenText; // Make sure to include any preceding comments. Symbol.Range.setBegin( Current->getPreviousNonComment()->Next->WhitespaceRange.getBegin()); + if (isTypeOnly) + nextToken(); + Symbol.Symbol = Current->TokenText; nextToken(); if (Current->is(Keywords.kw_as)) { diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 2e5bdf9..64035b0 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -4068,7 +4068,9 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() { // parsing the structural element, i.e. the declaration or expression for // `export default`. if (!IsImport && !FormatTok->isOneOf(tok::l_brace, tok::star) && - !FormatTok->isStringLiteral()) { + !FormatTok->isStringLiteral() && + !(FormatTok->is(Keywords.kw_type) && + Tokens->peekNextToken()->isOneOf(tok::l_brace, tok::star))) { return; } diff --git a/clang/unittests/Format/FormatTestJS.cpp b/clang/unittests/Format/FormatTestJS.cpp index 6f07d8b..ce81bd5 100644 --- a/clang/unittests/Format/FormatTestJS.cpp +++ b/clang/unittests/Format/FormatTestJS.cpp @@ -1476,6 +1476,17 @@ TEST_F(FormatTestJS, ImportExportASI) { " export class Y {}"); } +TEST_F(FormatTestJS, ImportExportType) { + verifyFormat("import type {x, y} from 'y';\n" + "import type * as x from 'y';\n" + "import type x from 'y';\n" + "import {x, type yu, z} from 'y';\n"); + verifyFormat("export type {x, y} from 'y';\n" + "export {x, type yu, z} from 'y';\n" + "export type {x, y};\n" + "export {x, type yu, z};\n"); +} + TEST_F(FormatTestJS, ClosureStyleCasts) { verifyFormat("var x = /** @type {foo} */ (bar);"); } diff --git a/clang/unittests/Format/SortImportsTestJS.cpp b/clang/unittests/Format/SortImportsTestJS.cpp index 1c7e508..9d779cd 100644 --- a/clang/unittests/Format/SortImportsTestJS.cpp +++ b/clang/unittests/Format/SortImportsTestJS.cpp @@ -465,6 +465,46 @@ TEST_F(SortImportsTestJS, ImportEqAliases) { "console.log(Z);\n"); } +TEST_F(SortImportsTestJS, ImportExportType) { + verifySort("import type {sym} from 'a';\n" + "import {type sym} from 'b';\n" + "import {sym} from 'c';\n" + "import type sym from 'd';\n" + "import type * as sym from 'e';\n" + "\n" + "let x = 1;", + "import {sym} from 'c';\n" + "import type {sym} from 'a';\n" + "import type * as sym from 'e';\n" + "import type sym from 'd';\n" + "import {type sym} from 'b';\n" + "let x = 1;"); + + // Symbols within import statement + verifySort("import {type sym1, type sym2 as a, sym3} from 'b';\n", + "import {type sym2 as a, type sym1, sym3} from 'b';\n"); + + // Merging + verifySort("import {X, type Z} from 'a';\n" + "import type {Y} from 'a';\n" + "\n" + "X + Y + Z;\n", + "import {X} from 'a';\n" + "import {type Z} from 'a';\n" + "import type {Y} from 'a';\n" + "\n" + "X + Y + Z;\n"); + + // Merging: empty imports + verifySort("import type {A} from 'foo';\n", "import type {} from 'foo';\n" + "import type {A} from 'foo';"); + + // Merging: exports + verifySort("export {A, type B} from 'foo';\n", + "export {A} from 'foo';\n" + "export {type B} from 'foo';"); +} + } // end namespace } // end namespace format } // end namespace clang -- 2.7.4