From 6e5c5ff2d98f297fcfbb107f00010b792c4a8b14 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Tue, 19 Mar 2019 12:28:41 +0000 Subject: [PATCH] [clang-format] [JS] handle private members. Addresses PR40999 https://bugs.llvm.org/show_bug.cgi?id=40999 Private fields and methods in JavaScript would get incorrectly indented (it sees them as preprocessor directives and hence left aligns them) In this revision `#identifier` tokens `tok::hash->tok::identifier` are merged into a single new token `tok::identifier` with the `#` contained inside the TokenText. Before: ``` class Example { pub = 1; static pub2 = "foo"; static #priv2 = "bar"; method() { this.#priv = 5; } static staticMethod() { switch (this.#priv) { case '1': break; } } this.#privateMethod(); // infinite loop } static #staticPrivateMethod() {} } ``` After this fix the code will be correctly indented ``` class Example { pub = 1; #priv = 2; static pub2 = "foo"; static #priv2 = "bar"; method() { this.#priv = 5; } static staticMethod() { switch (this.#priv) { case '1': #priv = 3; break; } } #privateMethod() { this.#privateMethod(); // infinite loop } static #staticPrivateMethod() {} } ``` NOTE: There might be some JavaScript code out there which uses the C processor to preprocess .js files http://www.nongnu.org/espresso/js-cpp.html. It's not clear how this revision or even private fields and methods would interact. Patch originally by MyDeveloperDays (thanks!). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@356449 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Format/FormatToken.h | 1 + lib/Format/FormatTokenLexer.cpp | 21 +++++++++++++++++++++ lib/Format/FormatTokenLexer.h | 1 + unittests/Format/FormatTestJS.cpp | 24 +++++++++++++++++++++++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/Format/FormatToken.h b/lib/Format/FormatToken.h index 2989c760b6..a5daf8aed4 100644 --- a/lib/Format/FormatToken.h +++ b/lib/Format/FormatToken.h @@ -60,6 +60,7 @@ namespace format { TYPE(JsExponentiationEqual) \ TYPE(JsFatArrow) \ TYPE(JsNonNullAssertion) \ + TYPE(JsPrivateIdentifier) \ TYPE(JsTypeColon) \ TYPE(JsTypeOperator) \ TYPE(JsTypeOptionalQuestion) \ diff --git a/lib/Format/FormatTokenLexer.cpp b/lib/Format/FormatTokenLexer.cpp index 2f1b08a8b0..da755e36c1 100644 --- a/lib/Format/FormatTokenLexer.cpp +++ b/lib/Format/FormatTokenLexer.cpp @@ -95,6 +95,8 @@ void FormatTokenLexer::tryMergePreviousTokens() { Tokens.back()->Tok.setKind(tok::starequal); return; } + if (tryMergeJSPrivateIdentifier()) + return; } if (Style.Language == FormatStyle::LK_Java) { @@ -121,6 +123,25 @@ bool FormatTokenLexer::tryMergeNSStringLiteral() { return true; } +bool FormatTokenLexer::tryMergeJSPrivateIdentifier() { + // Merges #idenfier into a single identifier with the text #identifier + // but the token tok::identifier. + if (Tokens.size() < 2) + return false; + auto &Hash = *(Tokens.end() - 2); + auto &Identifier = *(Tokens.end() - 1); + if (!Hash->is(tok::hash) || !Identifier->is(tok::identifier)) + return false; + Hash->Tok.setKind(tok::identifier); + Hash->TokenText = + StringRef(Hash->TokenText.begin(), + Identifier->TokenText.end() - Hash->TokenText.begin()); + Hash->ColumnWidth += Identifier->ColumnWidth; + Hash->Type = TT_JsPrivateIdentifier; + Tokens.erase(Tokens.end() - 1); + return true; +} + bool FormatTokenLexer::tryMergeLessLess() { // Merge X,less,less,Y into X,lessless,Y unless X or Y is less. if (Tokens.size() < 3) diff --git a/lib/Format/FormatTokenLexer.h b/lib/Format/FormatTokenLexer.h index 61a4c041d1..a964626146 100644 --- a/lib/Format/FormatTokenLexer.h +++ b/lib/Format/FormatTokenLexer.h @@ -48,6 +48,7 @@ private: bool tryMergeLessLess(); bool tryMergeNSStringLiteral(); + bool tryMergeJSPrivateIdentifier(); bool tryMergeTokens(ArrayRef Kinds, TokenType NewType); diff --git a/unittests/Format/FormatTestJS.cpp b/unittests/Format/FormatTestJS.cpp index 528e4cf57c..b332f1bd97 100644 --- a/unittests/Format/FormatTestJS.cpp +++ b/unittests/Format/FormatTestJS.cpp @@ -2334,5 +2334,27 @@ TEST_F(FormatTestJS, ConditionalTypes) { " never) extends((k: infer I) => void) ? I : never;"); } -} // end namespace tooling +TEST_F(FormatTestJS, SupportPrivateFieldsAndMethods) { + verifyFormat("class Example {\n" + " pub = 1;\n" + " #priv = 2;\n" + " static pub2 = 'foo';\n" + " static #priv2 = 'bar';\n" + " method() {\n" + " this.#priv = 5;\n" + " }\n" + " static staticMethod() {\n" + " switch (this.#priv) {\n" + " case '1':\n" + " #priv = 3;\n" + " break;\n" + " }\n" + " }\n" + " #privateMethod() {\n" + " this.#privateMethod(); // infinite loop\n" + " }\n" + " static #staticPrivateMethod() {}\n"); +} + +} // namespace format } // end namespace clang -- 2.40.0