Path: blob/main/contrib/llvm-project/clang/lib/Format/NamespaceEndCommentsFixer.cpp
35233 views
//===--- NamespaceEndCommentsFixer.cpp --------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file implements NamespaceEndCommentsFixer, a TokenAnalyzer that10/// fixes namespace end comments.11///12//===----------------------------------------------------------------------===//1314#include "NamespaceEndCommentsFixer.h"15#include "clang/Basic/TokenKinds.h"16#include "llvm/Support/Debug.h"17#include "llvm/Support/Regex.h"1819#define DEBUG_TYPE "namespace-end-comments-fixer"2021namespace clang {22namespace format {2324namespace {25// Iterates all tokens starting from StartTok to EndTok and apply Fn to all26// tokens between them including StartTok and EndTok. Returns the token after27// EndTok.28const FormatToken *29processTokens(const FormatToken *Tok, tok::TokenKind StartTok,30tok::TokenKind EndTok,31llvm::function_ref<void(const FormatToken *)> Fn) {32if (!Tok || Tok->isNot(StartTok))33return Tok;34int NestLevel = 0;35do {36if (Tok->is(StartTok))37++NestLevel;38else if (Tok->is(EndTok))39--NestLevel;40if (Fn)41Fn(Tok);42Tok = Tok->getNextNonComment();43} while (Tok && NestLevel > 0);44return Tok;45}4647const FormatToken *skipAttribute(const FormatToken *Tok) {48if (!Tok)49return nullptr;50if (Tok->isAttribute()) {51Tok = Tok->getNextNonComment();52Tok = processTokens(Tok, tok::l_paren, tok::r_paren, nullptr);53} else if (Tok->is(tok::l_square)) {54Tok = processTokens(Tok, tok::l_square, tok::r_square, nullptr);55}56return Tok;57}5859// Computes the name of a namespace given the namespace token.60// Returns "" for anonymous namespace.61std::string computeName(const FormatToken *NamespaceTok) {62assert(NamespaceTok &&63NamespaceTok->isOneOf(tok::kw_namespace, TT_NamespaceMacro) &&64"expecting a namespace token");65std::string name;66const FormatToken *Tok = NamespaceTok->getNextNonComment();67if (NamespaceTok->is(TT_NamespaceMacro)) {68// Collects all the non-comment tokens between opening parenthesis69// and closing parenthesis or comma.70assert(Tok && Tok->is(tok::l_paren) && "expected an opening parenthesis");71Tok = Tok->getNextNonComment();72while (Tok && !Tok->isOneOf(tok::r_paren, tok::comma)) {73name += Tok->TokenText;74Tok = Tok->getNextNonComment();75}76return name;77}78Tok = skipAttribute(Tok);7980std::string FirstNSName;81// For `namespace [[foo]] A::B::inline C {` or82// `namespace MACRO1 MACRO2 A::B::inline C {`, returns "A::B::inline C".83// Peek for the first '::' (or '{' or '(')) and then return all tokens from84// one token before that up until the '{'. A '(' might be a macro with85// arguments.86const FormatToken *FirstNSTok = nullptr;87while (Tok && !Tok->isOneOf(tok::l_brace, tok::coloncolon, tok::l_paren)) {88if (FirstNSTok)89FirstNSName += FirstNSTok->TokenText;90FirstNSTok = Tok;91Tok = Tok->getNextNonComment();92}9394if (FirstNSTok)95Tok = FirstNSTok;96Tok = skipAttribute(Tok);9798FirstNSTok = nullptr;99// Add everything from '(' to ')'.100auto AddToken = [&name](const FormatToken *Tok) { name += Tok->TokenText; };101bool IsPrevColoncolon = false;102bool HasColoncolon = false;103bool IsPrevInline = false;104bool NameFinished = false;105// If we found '::' in name, then it's the name. Otherwise, we can't tell106// which one is name. For example, `namespace A B {`.107while (Tok && Tok->isNot(tok::l_brace)) {108if (FirstNSTok) {109if (!IsPrevInline && HasColoncolon && !IsPrevColoncolon) {110if (FirstNSTok->is(tok::l_paren)) {111FirstNSTok = Tok =112processTokens(FirstNSTok, tok::l_paren, tok::r_paren, AddToken);113continue;114}115if (FirstNSTok->isNot(tok::coloncolon)) {116NameFinished = true;117break;118}119}120name += FirstNSTok->TokenText;121IsPrevColoncolon = FirstNSTok->is(tok::coloncolon);122HasColoncolon = HasColoncolon || IsPrevColoncolon;123if (FirstNSTok->is(tok::kw_inline)) {124name += " ";125IsPrevInline = true;126}127}128FirstNSTok = Tok;129Tok = Tok->getNextNonComment();130const FormatToken *TokAfterAttr = skipAttribute(Tok);131if (TokAfterAttr != Tok)132FirstNSTok = Tok = TokAfterAttr;133}134if (!NameFinished && FirstNSTok && FirstNSTok->isNot(tok::l_brace))135name += FirstNSTok->TokenText;136if (FirstNSName.empty() || HasColoncolon)137return name;138return name.empty() ? FirstNSName : FirstNSName + " " + name;139}140141std::string computeEndCommentText(StringRef NamespaceName, bool AddNewline,142const FormatToken *NamespaceTok,143unsigned SpacesToAdd) {144std::string text = "//";145text.append(SpacesToAdd, ' ');146text += NamespaceTok->TokenText;147if (NamespaceTok->is(TT_NamespaceMacro))148text += "(";149else if (!NamespaceName.empty())150text += ' ';151text += NamespaceName;152if (NamespaceTok->is(TT_NamespaceMacro))153text += ")";154if (AddNewline)155text += '\n';156return text;157}158159bool hasEndComment(const FormatToken *RBraceTok) {160return RBraceTok->Next && RBraceTok->Next->is(tok::comment);161}162163bool validEndComment(const FormatToken *RBraceTok, StringRef NamespaceName,164const FormatToken *NamespaceTok) {165assert(hasEndComment(RBraceTok));166const FormatToken *Comment = RBraceTok->Next;167168// Matches a valid namespace end comment.169// Valid namespace end comments don't need to be edited.170static const llvm::Regex NamespaceCommentPattern =171llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"172"namespace( +([a-zA-Z0-9:_ ]+))?\\.? *(\\*/)?$",173llvm::Regex::IgnoreCase);174static const llvm::Regex NamespaceMacroCommentPattern =175llvm::Regex("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *"176"([a-zA-Z0-9_]+)\\(([a-zA-Z0-9:_]*|\".+\")\\)\\.? *(\\*/)?$",177llvm::Regex::IgnoreCase);178179SmallVector<StringRef, 8> Groups;180if (NamespaceTok->is(TT_NamespaceMacro) &&181NamespaceMacroCommentPattern.match(Comment->TokenText, &Groups)) {182StringRef NamespaceTokenText = Groups.size() > 4 ? Groups[4] : "";183// The name of the macro must be used.184if (NamespaceTokenText != NamespaceTok->TokenText)185return false;186} else if (NamespaceTok->isNot(tok::kw_namespace) ||187!NamespaceCommentPattern.match(Comment->TokenText, &Groups)) {188// Comment does not match regex.189return false;190}191StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5].rtrim() : "";192// Anonymous namespace comments must not mention a namespace name.193if (NamespaceName.empty() && !NamespaceNameInComment.empty())194return false;195StringRef AnonymousInComment = Groups.size() > 3 ? Groups[3] : "";196// Named namespace comments must not mention anonymous namespace.197if (!NamespaceName.empty() && !AnonymousInComment.empty())198return false;199if (NamespaceNameInComment == NamespaceName)200return true;201202// Has namespace comment flowed onto the next line.203// } // namespace204// // verylongnamespacenamethatdidnotfitonthepreviouscommentline205if (!(Comment->Next && Comment->Next->is(TT_LineComment)))206return false;207208static const llvm::Regex CommentPattern = llvm::Regex(209"^/[/*] *( +([a-zA-Z0-9:_]+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase);210211// Pull out just the comment text.212if (!CommentPattern.match(Comment->Next->TokenText, &Groups))213return false;214NamespaceNameInComment = Groups.size() > 2 ? Groups[2] : "";215216return NamespaceNameInComment == NamespaceName;217}218219void addEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,220const SourceManager &SourceMgr,221tooling::Replacements *Fixes) {222auto EndLoc = RBraceTok->Tok.getEndLoc();223auto Range = CharSourceRange::getCharRange(EndLoc, EndLoc);224auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));225if (Err) {226llvm::errs() << "Error while adding namespace end comment: "227<< llvm::toString(std::move(Err)) << "\n";228}229}230231void updateEndComment(const FormatToken *RBraceTok, StringRef EndCommentText,232const SourceManager &SourceMgr,233tooling::Replacements *Fixes) {234assert(hasEndComment(RBraceTok));235const FormatToken *Comment = RBraceTok->Next;236auto Range = CharSourceRange::getCharRange(Comment->getStartOfNonWhitespace(),237Comment->Tok.getEndLoc());238auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, EndCommentText));239if (Err) {240llvm::errs() << "Error while updating namespace end comment: "241<< llvm::toString(std::move(Err)) << "\n";242}243}244} // namespace245246const FormatToken *247getNamespaceToken(const AnnotatedLine *Line,248const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {249if (!Line->Affected || Line->InPPDirective || !Line->startsWith(tok::r_brace))250return nullptr;251size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex;252if (StartLineIndex == UnwrappedLine::kInvalidIndex)253return nullptr;254assert(StartLineIndex < AnnotatedLines.size());255const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First;256if (NamespaceTok->is(tok::l_brace)) {257// "namespace" keyword can be on the line preceding '{', e.g. in styles258// where BraceWrapping.AfterNamespace is true.259if (StartLineIndex > 0) {260NamespaceTok = AnnotatedLines[StartLineIndex - 1]->First;261if (AnnotatedLines[StartLineIndex - 1]->endsWith(tok::semi))262return nullptr;263}264}265266return NamespaceTok->getNamespaceToken();267}268269StringRef270getNamespaceTokenText(const AnnotatedLine *Line,271const SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {272const FormatToken *NamespaceTok = getNamespaceToken(Line, AnnotatedLines);273return NamespaceTok ? NamespaceTok->TokenText : StringRef();274}275276NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,277const FormatStyle &Style)278: TokenAnalyzer(Env, Style) {}279280std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(281TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,282FormatTokenLexer &Tokens) {283const SourceManager &SourceMgr = Env.getSourceManager();284AffectedRangeMgr.computeAffectedLines(AnnotatedLines);285tooling::Replacements Fixes;286287// Spin through the lines and ensure we have balanced braces.288int Braces = 0;289for (AnnotatedLine *Line : AnnotatedLines) {290FormatToken *Tok = Line->First;291while (Tok) {292Braces += Tok->is(tok::l_brace) ? 1 : Tok->is(tok::r_brace) ? -1 : 0;293Tok = Tok->Next;294}295}296// Don't attempt to comment unbalanced braces or this can297// lead to comments being placed on the closing brace which isn't298// the matching brace of the namespace. (occurs during incomplete editing).299if (Braces != 0)300return {Fixes, 0};301302std::string AllNamespaceNames;303size_t StartLineIndex = SIZE_MAX;304StringRef NamespaceTokenText;305unsigned int CompactedNamespacesCount = 0;306for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) {307const AnnotatedLine *EndLine = AnnotatedLines[I];308const FormatToken *NamespaceTok =309getNamespaceToken(EndLine, AnnotatedLines);310if (!NamespaceTok)311continue;312FormatToken *RBraceTok = EndLine->First;313if (RBraceTok->Finalized)314continue;315RBraceTok->Finalized = true;316const FormatToken *EndCommentPrevTok = RBraceTok;317// Namespaces often end with '};'. In that case, attach namespace end318// comments to the semicolon tokens.319if (RBraceTok->Next && RBraceTok->Next->is(tok::semi))320EndCommentPrevTok = RBraceTok->Next;321if (StartLineIndex == SIZE_MAX)322StartLineIndex = EndLine->MatchingOpeningBlockLineIndex;323std::string NamespaceName = computeName(NamespaceTok);324if (Style.CompactNamespaces) {325if (CompactedNamespacesCount == 0)326NamespaceTokenText = NamespaceTok->TokenText;327if ((I + 1 < E) &&328NamespaceTokenText ==329getNamespaceTokenText(AnnotatedLines[I + 1], AnnotatedLines) &&330StartLineIndex - CompactedNamespacesCount - 1 ==331AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex &&332!AnnotatedLines[I + 1]->First->Finalized) {333if (hasEndComment(EndCommentPrevTok)) {334// remove end comment, it will be merged in next one335updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes);336}337++CompactedNamespacesCount;338if (!NamespaceName.empty())339AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames;340continue;341}342NamespaceName += AllNamespaceNames;343CompactedNamespacesCount = 0;344AllNamespaceNames = std::string();345}346// The next token in the token stream after the place where the end comment347// token must be. This is either the next token on the current line or the348// first token on the next line.349const FormatToken *EndCommentNextTok = EndCommentPrevTok->Next;350if (EndCommentNextTok && EndCommentNextTok->is(tok::comment))351EndCommentNextTok = EndCommentNextTok->Next;352if (!EndCommentNextTok && I + 1 < E)353EndCommentNextTok = AnnotatedLines[I + 1]->First;354bool AddNewline = EndCommentNextTok &&355EndCommentNextTok->NewlinesBefore == 0 &&356EndCommentNextTok->isNot(tok::eof);357const std::string EndCommentText =358computeEndCommentText(NamespaceName, AddNewline, NamespaceTok,359Style.SpacesInLineCommentPrefix.Minimum);360if (!hasEndComment(EndCommentPrevTok)) {361unsigned LineCount = 0;362for (auto J = StartLineIndex + 1; J < I; ++J)363LineCount += AnnotatedLines[J]->size();364if (LineCount > Style.ShortNamespaceLines) {365addEndComment(EndCommentPrevTok,366std::string(Style.SpacesBeforeTrailingComments, ' ') +367EndCommentText,368SourceMgr, &Fixes);369}370} else if (!validEndComment(EndCommentPrevTok, NamespaceName,371NamespaceTok)) {372updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes);373}374StartLineIndex = SIZE_MAX;375}376return {Fixes, 0};377}378379} // namespace format380} // namespace clang381382383