Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -431,6 +431,23 @@ /// type. bool IndentWrappedFunctionNames; + /// \brief Quotation styles for JavaScript strings. Does not affect template + /// strings. + enum JavaScriptQuoteStyle { + /// Leave string quotes as they are. + JSQS_Leave, + /// Always use single quotes. + JSQS_Single, + /// Always use double quotes. + JSQS_Double + }; + + /// \brief The JavaScriptQuoteStyle to use for JavaScript strings. + JavaScriptQuoteStyle JavaScriptQuotes; + + /// \brief Whether to wrap JavaScript import/export statements. + bool JavaScriptWrapImports; + /// \brief If true, empty lines at the start of blocks are kept. bool KeepEmptyLinesAtTheStartOfBlocks; @@ -613,20 +630,6 @@ /// \brief The way to use tab characters in the resulting file. UseTabStyle UseTab; - /// \brief Quotation styles for JavaScript strings. Does not affect template - /// strings. - enum JavaScriptQuoteStyle { - /// Leave string quotes as they are. - JSQS_Leave, - /// Always use single quotes. - JSQS_Single, - /// Always use double quotes. - JSQS_Double - }; - - /// \brief The JavaScriptQuoteStyle to use for JavaScript strings. - JavaScriptQuoteStyle JavaScriptQuotes; - bool operator==(const FormatStyle &R) const { return AccessModifierOffset == R.AccessModifierOffset && AlignAfterOpenBracket == R.AlignAfterOpenBracket && @@ -675,6 +678,8 @@ IndentCaseLabels == R.IndentCaseLabels && IndentWidth == R.IndentWidth && Language == R.Language && IndentWrappedFunctionNames == R.IndentWrappedFunctionNames && + JavaScriptQuotes == R.JavaScriptQuotes && + JavaScriptWrapImports == R.JavaScriptWrapImports && KeepEmptyLinesAtTheStartOfBlocks == R.KeepEmptyLinesAtTheStartOfBlocks && MacroBlockBegin == R.MacroBlockBegin && @@ -703,8 +708,7 @@ SpacesInParentheses == R.SpacesInParentheses && SpacesInSquareBrackets == R.SpacesInSquareBrackets && Standard == R.Standard && TabWidth == R.TabWidth && - UseTab == R.UseTab && - JavaScriptQuotes == R.JavaScriptQuotes; + UseTab == R.UseTab; } }; Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -314,6 +314,8 @@ IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); + IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes); + IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports); IO.mapOptional("KeepEmptyLinesAtTheStartOfBlocks", Style.KeepEmptyLinesAtTheStartOfBlocks); IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin); @@ -353,7 +355,6 @@ IO.mapOptional("Standard", Style.Standard); IO.mapOptional("TabWidth", Style.TabWidth); IO.mapOptional("UseTab", Style.UseTab); - IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes); } }; @@ -613,6 +614,7 @@ GoogleStyle.MaxEmptyLinesToKeep = 3; GoogleStyle.SpacesInContainerLiterals = false; GoogleStyle.JavaScriptQuotes = FormatStyle::JSQS_Single; + GoogleStyle.JavaScriptWrapImports = false; } else if (Language == FormatStyle::LK_Proto) { GoogleStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; GoogleStyle.SpacesInContainerLiterals = false; Index: lib/Format/TokenAnnotator.cpp =================================================================== --- lib/Format/TokenAnnotator.cpp +++ lib/Format/TokenAnnotator.cpp @@ -784,13 +784,14 @@ return LT_ImportStatement; } + bool KeywordVirtualFound = false; + bool ImportStatement = false; + // import {...} from '...'; if (Style.Language == FormatStyle::LK_JavaScript && CurrentToken->is(Keywords.kw_import)) - return LT_ImportStatement; + ImportStatement = true; - bool KeywordVirtualFound = false; - bool ImportStatement = false; while (CurrentToken) { if (CurrentToken->is(tok::kw_virtual)) KeywordVirtualFound = true; @@ -2126,6 +2127,11 @@ // locations that should have whitespace following are identified by the // above set of follower tokens. return false; + // Postfix non-null assertion operator, as in `foo!.bar()`. + if (Right.is(tok::exclaim) && (Left.isOneOf(tok::identifier, tok::r_paren, + tok::r_square, tok::r_brace) || + Left.Tok.isLiteral())) + return false; } else if (Style.Language == FormatStyle::LK_Java) { if (Left.is(tok::r_square) && Right.is(tok::l_brace)) return true; Index: lib/Format/UnwrappedLineFormatter.cpp =================================================================== --- lib/Format/UnwrappedLineFormatter.cpp +++ lib/Format/UnwrappedLineFormatter.cpp @@ -847,7 +847,9 @@ unsigned ColumnLimit = getColumnLimit(TheLine.InPPDirective, NextLine); bool FitsIntoOneLine = TheLine.Last->TotalLength + Indent <= ColumnLimit || - TheLine.Type == LT_ImportStatement; + (TheLine.Type == LT_ImportStatement && + (Style.Language != FormatStyle::LK_JavaScript || + !Style.JavaScriptWrapImports)); if (Style.ColumnLimit == 0) NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this) Index: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -1007,9 +1007,6 @@ verifyFormat("import SomeThing from 'some/module.js';"); verifyFormat("import {X, Y} from 'some/module.js';"); verifyFormat("import a, {X, Y} from 'some/module.js';"); - verifyFormat("import {VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying," - " VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying" - "} from 'some/module.js';"); verifyFormat("import {X, Y,} from 'some/module.js';"); verifyFormat("import {X as myLocalX, Y as myLocalY} from 'some/module.js';"); // Ensure Automatic Semicolon Insertion does not break on "as\n". @@ -1078,6 +1075,30 @@ "}"); } +TEST_F(FormatTestJS, ImportWrapping) { + verifyFormat("import {VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying," + " VeryLongImportsAreAnnoying, VeryLongImportsAreAnnoying" + "} from 'some/module.js';"); + FormatStyle Style = getGoogleJSStyleWithColumns(80); + Style.JavaScriptWrapImports = true; + verifyFormat("import {\n" + " VeryLongImportsAreAnnoying,\n" + " VeryLongImportsAreAnnoying,\n" + " VeryLongImportsAreAnnoying,\n" + "} from 'some/module.js';", + Style); + verifyFormat("import {\n" + " A,\n" + " A,\n" + "} from 'some/module.js';", + Style); + verifyFormat("export {\n" + " A,\n" + " A,\n" + "} from 'some/module.js';", + Style); +} + TEST_F(FormatTestJS, TemplateStrings) { // Keeps any whitespace/indentation within the template string. verifyFormat("var x = `hello\n" @@ -1293,5 +1314,14 @@ "var x = hello();"); } +TEST_F(FormatTestJS, NonNullAssertionOperator) { + verifyFormat("let x = foo!.bar();\n"); + verifyFormat("let x = foo ? bar! : baz;\n"); + verifyFormat("let x = !foo;\n"); + verifyFormat("let x = foo[0]!;\n"); + verifyFormat("let x = (foo)!;\n"); + verifyFormat("let x = {foo: 1}!;\n"); +} + } // end namespace tooling } // end namespace clang Index: unittests/Format/SortImportsTestJS.cpp =================================================================== --- unittests/Format/SortImportsTestJS.cpp +++ unittests/Format/SortImportsTestJS.cpp @@ -92,12 +92,12 @@ TEST_F(SortImportsTestJS, Comments) { verifySort("/** @fileoverview This is a great file. */\n" "// A very important import follows.\n" - "import {sym} from 'a'; /* more comments */\n" - "import {sym} from 'b'; // from //foo:bar\n", + "import {sym} from 'a'; /* more comments */\n" + "import {sym} from 'b'; // from //foo:bar\n", "/** @fileoverview This is a great file. */\n" - "import {sym} from 'b'; // from //foo:bar\n" + "import {sym} from 'b'; // from //foo:bar\n" "// A very important import follows.\n" - "import {sym} from 'a'; /* more comments */\n"); + "import {sym} from 'a'; /* more comments */\n"); } TEST_F(SortImportsTestJS, SortStar) {