77// ===----------------------------------------------------------------------===//
88
99#include " NamespaceCommentCheck.h"
10+ #include " ../utils/LexerUtils.h"
1011#include " clang/AST/ASTContext.h"
1112#include " clang/ASTMatchers/ASTMatchers.h"
1213#include " clang/Basic/SourceLocation.h"
14+ #include " clang/Basic/TokenKinds.h"
1315#include " clang/Lex/Lexer.h"
1416#include " llvm/ADT/StringExtras.h"
1517
@@ -46,30 +48,48 @@ static bool locationsInSameFile(const SourceManager &Sources,
4648 Sources.getFileID (Loc1) == Sources.getFileID (Loc2);
4749}
4850
49- static std::string getNamespaceComment (const NamespaceDecl *ND,
50- bool InsertLineBreak) {
51- std::string Fix = " // namespace" ;
52- if (!ND->isAnonymousNamespace ())
53- Fix.append (" " ).append (ND->getNameAsString ());
54- if (InsertLineBreak)
55- Fix.append (" \n " );
56- return Fix;
57- }
58-
59- static std::string getNamespaceComment (const std::string &NameSpaceName,
60- bool InsertLineBreak) {
61- std::string Fix = " // namespace " ;
62- Fix.append (NameSpaceName);
63- if (InsertLineBreak)
64- Fix.append (" \n " );
65- return Fix;
51+ static llvm::Optional<std::string>
52+ getNamespaceNameAsWritten (SourceLocation &Loc, const SourceManager &Sources,
53+ const LangOptions &LangOpts) {
54+ // Loc should be at the begin of the namespace decl (usually, `namespace`
55+ // token). We skip the first token right away, but in case of `inline
56+ // namespace` or `namespace a::inline b` we can see both `inline` and
57+ // `namespace` keywords, which we just ignore. Nested parens/squares before
58+ // the opening brace can result from attributes.
59+ std::string Result;
60+ int Nesting = 0 ;
61+ while (llvm::Optional<Token> T = utils::lexer::findNextTokenSkippingComments (
62+ Loc, Sources, LangOpts)) {
63+ Loc = T->getLocation ();
64+ if (T->is (tok::l_brace))
65+ break ;
66+
67+ if (T->isOneOf (tok::l_square, tok::l_paren)) {
68+ ++Nesting;
69+ } else if (T->isOneOf (tok::r_square, tok::r_paren)) {
70+ --Nesting;
71+ } else if (Nesting == 0 ) {
72+ if (T->is (tok::raw_identifier)) {
73+ StringRef ID = T->getRawIdentifier ();
74+ if (ID != " namespace" && ID != " inline" )
75+ Result.append (ID);
76+ } else if (T->is (tok::coloncolon)) {
77+ Result.append (" ::" );
78+ } else { // Any other kind of token is unexpected here.
79+ return llvm::None;
80+ }
81+ }
82+ }
83+ return Result;
6684}
6785
6886void NamespaceCommentCheck::check (const MatchFinder::MatchResult &Result) {
6987 const auto *ND = Result.Nodes .getNodeAs <NamespaceDecl>(" namespace" );
7088 const SourceManager &Sources = *Result.SourceManager ;
7189
72- if (!locationsInSameFile (Sources, ND->getBeginLoc (), ND->getRBraceLoc ()))
90+ // Ignore namespaces inside macros and namespaces split across files.
91+ if (ND->getBeginLoc ().isMacroID () ||
92+ !locationsInSameFile (Sources, ND->getBeginLoc (), ND->getRBraceLoc ()))
7393 return ;
7494
7595 // Don't require closing comments for namespaces spanning less than certain
@@ -80,44 +100,32 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) {
80100 return ;
81101
82102 // Find next token after the namespace closing brace.
83- SourceLocation AfterRBrace = ND->getRBraceLoc ().getLocWithOffset (1 );
103+ SourceLocation AfterRBrace = Lexer::getLocForEndOfToken (
104+ ND->getRBraceLoc (), /* Offset=*/ 0 , Sources, getLangOpts ());
84105 SourceLocation Loc = AfterRBrace;
85- Token Tok;
86- SourceLocation LBracketLocation = ND->getLocation ();
87- SourceLocation NestedNamespaceBegin = LBracketLocation;
106+ SourceLocation LBraceLoc = ND->getBeginLoc ();
88107
89108 // Currently for nested namepsace (n1::n2::...) the AST matcher will match foo
90109 // then bar instead of a single match. So if we got a nested namespace we have
91110 // to skip the next ones.
92111 for (const auto &EndOfNameLocation : Ends) {
93- if (Sources.isBeforeInTranslationUnit (NestedNamespaceBegin,
94- EndOfNameLocation))
112+ if (Sources.isBeforeInTranslationUnit (ND->getLocation (), EndOfNameLocation))
95113 return ;
96114 }
97115
98- // Ignore macros
99- if (!ND->getLocation ().isMacroID ()) {
100- while (Lexer::getRawToken (LBracketLocation, Tok, Sources, getLangOpts ()) ||
101- !Tok.is (tok::l_brace)) {
102- LBracketLocation = LBracketLocation.getLocWithOffset (1 );
103- }
116+ llvm::Optional<std::string> NamespaceNameAsWritten =
117+ getNamespaceNameAsWritten (LBraceLoc, Sources, getLangOpts ());
118+ if (!NamespaceNameAsWritten)
119+ return ;
120+
121+ if (NamespaceNameAsWritten->empty () != ND->isAnonymousNamespace ()) {
122+ // Apparently, we didn't find the correct namespace name. Give up.
123+ return ;
104124 }
105125
106- // FIXME: This probably breaks on comments between the namespace and its '{'.
107- auto TextRange =
108- Lexer::getAsCharRange (SourceRange (NestedNamespaceBegin, LBracketLocation),
109- Sources, getLangOpts ());
110- StringRef NestedNamespaceName =
111- Lexer::getSourceText (TextRange, Sources, getLangOpts ())
112- .rtrim (' {' ) // Drop the { itself.
113- .rtrim (); // Drop any whitespace before it.
114- bool IsNested = NestedNamespaceName.contains (' :' );
115-
116- if (IsNested)
117- Ends.push_back (LBracketLocation);
118- else
119- NestedNamespaceName = ND->getName ();
126+ Ends.push_back (LBraceLoc);
120127
128+ Token Tok;
121129 // Skip whitespace until we find the next token.
122130 while (Lexer::getRawToken (Loc, Tok, Sources, getLangOpts ()) ||
123131 Tok.is (tok::semi)) {
@@ -143,13 +151,9 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) {
143151 StringRef NamespaceNameInComment = Groups.size () > 5 ? Groups[5 ] : " " ;
144152 StringRef Anonymous = Groups.size () > 3 ? Groups[3 ] : " " ;
145153
146- if (IsNested && NestedNamespaceName == NamespaceNameInComment) {
147- // C++17 nested namespace.
148- return ;
149- } else if ((ND->isAnonymousNamespace () &&
150- NamespaceNameInComment.empty ()) ||
151- (ND->getNameAsString () == NamespaceNameInComment &&
152- Anonymous.empty ())) {
154+ if ((ND->isAnonymousNamespace () && NamespaceNameInComment.empty ()) ||
155+ (*NamespaceNameAsWritten == NamespaceNameInComment &&
156+ Anonymous.empty ())) {
153157 // Check if the namespace in the comment is the same.
154158 // FIXME: Maybe we need a strict mode, where we always fix namespace
155159 // comments with different format.
@@ -177,27 +181,29 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) {
177181 // multi-line or there may be other tokens behind it.
178182 }
179183
180- std::string NamespaceName =
181- ND->isAnonymousNamespace ()
182- ? " anonymous namespace"
183- : (" namespace '" + NestedNamespaceName.str () + " '" );
184+ std::string NamespaceNameForDiag =
185+ ND->isAnonymousNamespace () ? " anonymous namespace"
186+ : (" namespace '" + *NamespaceNameAsWritten + " '" );
187+
188+ std::string Fix (SpacesBeforeComments, ' ' );
189+ Fix.append (" // namespace" );
190+ if (!ND->isAnonymousNamespace ())
191+ Fix.append (" " ).append (*NamespaceNameAsWritten);
192+ if (NeedLineBreak)
193+ Fix.append (" \n " );
184194
185195 // Place diagnostic at an old comment, or closing brace if we did not have it.
186196 SourceLocation DiagLoc =
187197 OldCommentRange.getBegin () != OldCommentRange.getEnd ()
188198 ? OldCommentRange.getBegin ()
189199 : ND->getRBraceLoc ();
190200
191- diag (DiagLoc, Message)
192- << NamespaceName
193- << FixItHint::CreateReplacement (
194- CharSourceRange::getCharRange (OldCommentRange),
195- std::string (SpacesBeforeComments, ' ' ) +
196- (IsNested
197- ? getNamespaceComment (NestedNamespaceName, NeedLineBreak)
198- : getNamespaceComment (ND, NeedLineBreak)));
201+ diag (DiagLoc, Message) << NamespaceNameForDiag
202+ << FixItHint::CreateReplacement (
203+ CharSourceRange::getCharRange (OldCommentRange),
204+ Fix);
199205 diag (ND->getLocation (), " %0 starts here" , DiagnosticIDs::Note)
200- << NamespaceName ;
206+ << NamespaceNameForDiag ;
201207}
202208
203209} // namespace readability
0 commit comments