summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/unnecessaryparen.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/unnecessaryparen.cxx')
-rw-r--r--compilerplugins/clang/unnecessaryparen.cxx668
1 files changed, 668 insertions, 0 deletions
diff --git a/compilerplugins/clang/unnecessaryparen.cxx b/compilerplugins/clang/unnecessaryparen.cxx
new file mode 100644
index 000000000..e93dfa64b
--- /dev/null
+++ b/compilerplugins/clang/unnecessaryparen.cxx
@@ -0,0 +1,668 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef LO_CLANG_SHARED_PLUGINS
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include <unordered_set>
+
+#include <clang/AST/CXXInheritance.h>
+
+#include "config_clang.h"
+
+#include "compat.hxx"
+#include "plugin.hxx"
+
+/**
+look for unnecessary parentheses
+*/
+
+namespace {
+
+// Like clang::Stmt::IgnoreImplicit (lib/AST/Stmt.cpp), but also ignoring CXXConstructExpr and
+// looking through implicit UserDefinedConversion's member function call:
+Expr const * ignoreAllImplicit(Expr const * expr) {
+ while (true)
+ {
+ auto oldExpr = expr;
+ if (auto const e = dyn_cast<ExprWithCleanups>(expr)) {
+ expr = e->getSubExpr();
+ }
+ else if (auto const e = dyn_cast<CXXConstructExpr>(expr)) {
+ if (e->getNumArgs() == 1) {
+ expr = e->getArg(0);
+ }
+ }
+ else if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
+ expr = compat::getSubExpr(e);
+ }
+ else if (auto const e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
+ expr = e->getSubExpr();
+ }
+ else if (auto const e = dyn_cast<ImplicitCastExpr>(expr)) {
+ expr = e->getSubExpr();
+ if (e->getCastKind() == CK_UserDefinedConversion) {
+ auto const ce = cast<CXXMemberCallExpr>(expr);
+ assert(ce->getNumArgs() == 0);
+ expr = ce->getImplicitObjectArgument();
+ }
+ }
+#if CLANG_VERSION >= 80000
+ else if (auto const e = dyn_cast<ConstantExpr>(expr)) {
+ expr = e->getSubExpr();
+ }
+#endif
+ if (expr == oldExpr)
+ return expr;
+ }
+ return expr;
+}
+
+class UnnecessaryParen:
+ public loplugin::FilteringRewritePlugin<UnnecessaryParen>
+{
+public:
+ explicit UnnecessaryParen(loplugin::InstantiationData const & data):
+ FilteringRewritePlugin(data) {}
+
+ virtual bool preRun() override
+ {
+ StringRef fn(handler.getMainFileName());
+ // fixing this, makes the source in the .y files look horrible
+ if (loplugin::isSamePathname(fn, WORKDIR "/YaccTarget/unoidl/source/sourceprovider-parser.cxx"))
+ return false;
+ if (loplugin::isSamePathname(fn, WORKDIR "/YaccTarget/idlc/source/parser.cxx"))
+ return false;
+ return true;
+ }
+ virtual void run() override
+ {
+ if( preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitParenExpr(const ParenExpr *);
+ bool VisitIfStmt(const IfStmt *);
+ bool VisitDoStmt(const DoStmt *);
+ bool VisitWhileStmt(const WhileStmt *);
+ bool VisitForStmt(ForStmt const * stmt);
+ bool VisitSwitchStmt(const SwitchStmt *);
+ bool VisitCaseStmt(const CaseStmt *);
+ bool VisitReturnStmt(const ReturnStmt* );
+ bool VisitCallExpr(const CallExpr *);
+ bool VisitVarDecl(const VarDecl *);
+ bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *);
+ bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const *);
+ bool VisitConditionalOperator(ConditionalOperator const * expr);
+ bool VisitBinaryConditionalOperator(BinaryConditionalOperator const * expr);
+ bool VisitMemberExpr(const MemberExpr *f);
+ bool VisitCXXDeleteExpr(const CXXDeleteExpr *);
+
+ bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ if (expr->getCastKind() != CK_UserDefinedConversion) {
+ return true;
+ }
+ // Filter out a MemberExpr (resp. a ParenExpr sub-expr, if any, as would be found by
+ // VisitMemberExpr) that is part of a CXXMemberCallExpr which in turn is part of an
+ // ImplicitCastExpr, so that VisitMemberExpr doesn't erroneously pick it up (and note that
+ // CXXMemberCallExpr's getImplicitObjectArgument() skips past the underlying MemberExpr):
+ if (auto const e1 = dyn_cast<CXXMemberCallExpr>(expr->getSubExpr())) {
+ if (auto const e2 = dyn_cast<ParenExpr>(
+ e1->getImplicitObjectArgument()->IgnoreImpCasts()))
+ {
+ handled_.insert(e2);
+ }
+ }
+ return true;
+ }
+
+private:
+ void VisitSomeStmt(Stmt const * stmt, const Expr* cond, StringRef stmtName);
+
+ void handleUnreachableCodeConditionParens(Expr const * expr);
+
+ // Hack for libxml2's BAD_CAST object-like macro (expanding to "(xmlChar *)"), which is
+ // typically used as if it were a function-like macro, e.g., as "BAD_CAST(pName)" in
+ // SwNode::dumpAsXml (sw/source/core/docnode/node.cxx):
+ bool isPrecededBy_BAD_CAST(Expr const * expr);
+
+ bool badCombination(SourceLocation loc, int prevOffset, int nextOffset);
+
+ bool removeParens(ParenExpr const * expr);
+
+ std::unordered_set<ParenExpr const *> handled_;
+};
+
+bool UnnecessaryParen::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const * expr)
+{
+ if (expr->getKind() == UETT_SizeOf && !expr->isArgumentType()) {
+ if (auto const e = dyn_cast<ParenExpr>(ignoreAllImplicit(expr->getArgumentExpr()))) {
+ handled_.insert(e);
+ }
+ }
+ return true;
+}
+
+bool UnnecessaryParen::VisitConditionalOperator(ConditionalOperator const * expr) {
+ handleUnreachableCodeConditionParens(expr->getCond());
+ return true;
+}
+
+bool UnnecessaryParen::VisitBinaryConditionalOperator(BinaryConditionalOperator const * expr) {
+ handleUnreachableCodeConditionParens(expr->getCond());
+ return true;
+}
+
+bool UnnecessaryParen::VisitParenExpr(const ParenExpr* parenExpr)
+{
+ if (ignoreLocation(parenExpr))
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+ if (handled_.find(parenExpr) != handled_.end())
+ return true;
+
+ auto subExpr = ignoreAllImplicit(parenExpr->getSubExpr());
+
+ if (auto subParenExpr = dyn_cast<ParenExpr>(subExpr))
+ {
+ if (compat::getBeginLoc(subParenExpr).isMacroID())
+ return true;
+ report(
+ DiagnosticsEngine::Warning, "parentheses around parentheses",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(subParenExpr);
+ }
+
+ // Somewhat redundantly add parenExpr to handled_, so that issues within InitListExpr don't get
+ // reported twice (without having to change TraverseInitListExpr to only either traverse the
+ // syntactic or semantic form, as other plugins do):
+
+ if (isa<DeclRefExpr>(subExpr)) {
+ if (!isPrecededBy_BAD_CAST(parenExpr)) {
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around identifier",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+ } else if (isa<IntegerLiteral>(subExpr) || isa<CharacterLiteral>(subExpr)
+ || isa<FloatingLiteral>(subExpr) || isa<ImaginaryLiteral>(subExpr)
+ || isa<CXXBoolLiteralExpr>(subExpr) || isa<CXXNullPtrLiteralExpr>(subExpr)
+ || isa<ObjCBoolLiteralExpr>(subExpr))
+ {
+ auto const loc = compat::getBeginLoc(subExpr);
+ if (loc.isMacroID() && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(loc))
+ {
+ // just in case the macro could also expand to something that /would/ require
+ // parentheses here
+ return true;
+ }
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around literal",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ } else if (auto const e = dyn_cast<clang::StringLiteral>(subExpr)) {
+ if (e->getNumConcatenated() == 1 && !isPrecededBy_BAD_CAST(parenExpr)) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unnecessary parentheses around single-token string literal",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+ } else if (auto const e = dyn_cast<UnaryOperator>(subExpr)) {
+ auto const op = e->getOpcode();
+ if (op == UO_Plus || op == UO_Minus) {
+ auto const e2 = e->getSubExpr();
+ if (isa<IntegerLiteral>(e2) || isa<FloatingLiteral>(e2) || isa<ImaginaryLiteral>(e2)) {
+ report(
+ DiagnosticsEngine::Warning,
+ "unnecessary parentheses around signed numeric literal",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+ }
+ } else if (isa<CXXNamedCastExpr>(subExpr)) {
+ if (!removeParens(parenExpr)) {
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around cast",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ }
+ handled_.insert(parenExpr);
+ } else if (auto memberExpr = dyn_cast<MemberExpr>(subExpr)) {
+ if (isa<CXXThisExpr>(ignoreAllImplicit(memberExpr->getBase()))) {
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around member expr",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+ }
+
+ return true;
+}
+
+bool UnnecessaryParen::VisitIfStmt(const IfStmt* ifStmt)
+{
+ handleUnreachableCodeConditionParens(ifStmt->getCond());
+ VisitSomeStmt(ifStmt, ifStmt->getCond(), "if");
+ return true;
+}
+
+bool UnnecessaryParen::VisitDoStmt(const DoStmt* doStmt)
+{
+ VisitSomeStmt(doStmt, doStmt->getCond(), "do");
+ return true;
+}
+
+bool UnnecessaryParen::VisitWhileStmt(const WhileStmt* whileStmt)
+{
+ handleUnreachableCodeConditionParens(whileStmt->getCond());
+ VisitSomeStmt(whileStmt, whileStmt->getCond(), "while");
+ return true;
+}
+
+bool UnnecessaryParen::VisitForStmt(ForStmt const * stmt) {
+ if (auto const cond = stmt->getCond()) {
+ handleUnreachableCodeConditionParens(cond);
+ }
+ return true;
+}
+
+bool UnnecessaryParen::VisitSwitchStmt(const SwitchStmt* switchStmt)
+{
+ VisitSomeStmt(switchStmt, switchStmt->getCond(), "switch");
+ return true;
+}
+
+bool UnnecessaryParen::VisitCaseStmt(const CaseStmt* caseStmt)
+{
+ VisitSomeStmt(caseStmt, caseStmt->getLHS(), "case");
+ return true;
+}
+
+bool UnnecessaryParen::VisitReturnStmt(const ReturnStmt* returnStmt)
+{
+ if (ignoreLocation(returnStmt))
+ return true;
+
+ if (!returnStmt->getRetValue())
+ return true;
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(returnStmt->getRetValue()));
+ if (!parenExpr)
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+ // assignments need extra parentheses or they generate a compiler warning
+ auto binaryOp = dyn_cast<BinaryOperator>(parenExpr->getSubExpr());
+ if (binaryOp && binaryOp->getOpcode() == BO_Assign)
+ return true;
+
+ // only non-operator-calls for now
+ auto subExpr = ignoreAllImplicit(parenExpr->getSubExpr());
+ if (isa<CallExpr>(subExpr) && !isa<CXXOperatorCallExpr>(subExpr))
+ {
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside return statement",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+ return true;
+}
+
+void UnnecessaryParen::VisitSomeStmt(const Stmt * stmt, const Expr* cond, StringRef stmtName)
+{
+ if (ignoreLocation(stmt))
+ return;
+
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(cond));
+ if (parenExpr) {
+ if (handled_.find(parenExpr) != handled_.end()) {
+ return;
+ }
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return;
+ // assignments need extra parentheses or they generate a compiler warning
+ auto binaryOp = dyn_cast<BinaryOperator>(parenExpr->getSubExpr());
+ if (binaryOp && binaryOp->getOpcode() == BO_Assign)
+ return;
+ if (auto const opCall = dyn_cast<CXXOperatorCallExpr>(parenExpr->getSubExpr())) {
+ if (opCall->getOperator() == OO_Equal) {
+ return;
+ }
+ }
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside %0 statement",
+ compat::getBeginLoc(parenExpr))
+ << stmtName
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ }
+}
+
+bool UnnecessaryParen::VisitCallExpr(const CallExpr* callExpr)
+{
+ if (ignoreLocation(callExpr))
+ return true;
+ if (callExpr->getNumArgs() != 1 || isa<CXXOperatorCallExpr>(callExpr))
+ return true;
+
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(callExpr->getArg(0)));
+ if (!parenExpr)
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+ // assignments need extra parentheses or they generate a compiler warning
+ auto binaryOp = dyn_cast<BinaryOperator>(parenExpr->getSubExpr());
+ if (binaryOp && binaryOp->getOpcode() == BO_Assign)
+ return true;
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside single-arg call",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ return true;
+}
+
+bool UnnecessaryParen::VisitCXXDeleteExpr(const CXXDeleteExpr* deleteExpr)
+{
+ if (ignoreLocation(deleteExpr))
+ return true;
+
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(deleteExpr->getArgument()));
+ if (!parenExpr)
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+ // assignments need extra parentheses or they generate a compiler warning
+ auto binaryOp = dyn_cast<BinaryOperator>(parenExpr->getSubExpr());
+ if (binaryOp && binaryOp->getOpcode() == BO_Assign)
+ return true;
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside delete expr",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ return true;
+}
+
+bool UnnecessaryParen::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr* callExpr)
+{
+ if (ignoreLocation(callExpr))
+ return true;
+ if (callExpr->getNumArgs() != 2)
+ return true;
+
+ // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
+ // doesn't have yet.
+ auto Opc = callExpr->getOperator();
+ if (Opc != OO_Equal && Opc != OO_StarEqual &&
+ Opc != OO_SlashEqual && Opc != OO_PercentEqual &&
+ Opc != OO_PlusEqual && Opc != OO_MinusEqual &&
+ Opc != OO_LessLessEqual && Opc != OO_GreaterGreaterEqual &&
+ Opc != OO_AmpEqual && Opc != OO_CaretEqual &&
+ Opc != OO_PipeEqual)
+ return true;
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(callExpr->getArg(1)));
+ if (!parenExpr)
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+ // Sometimes parentheses make the RHS of an assignment easier to read by
+ // visually disambiguating the = from a call to ==
+ auto sub = parenExpr->getSubExpr();
+ if (auto subBinOp = dyn_cast<BinaryOperator>(sub))
+ {
+ if (!(subBinOp->isMultiplicativeOp() || subBinOp->isAdditiveOp() || subBinOp->isPtrMemOp()))
+ return true;
+ }
+ if (auto subOperatorCall = dyn_cast<CXXOperatorCallExpr>(sub))
+ {
+ auto op = subOperatorCall->getOperator();
+ if (!((op >= OO_Plus && op <= OO_Exclaim) || (op >= OO_ArrowStar && op <= OO_Subscript)))
+ return true;
+ }
+ if (isa<ConditionalOperator>(sub))
+ return true;
+
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside assignment",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ return true;
+}
+
+bool UnnecessaryParen::VisitVarDecl(const VarDecl* varDecl)
+{
+ if (ignoreLocation(varDecl))
+ return true;
+ if (!varDecl->getInit())
+ return true;
+
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(varDecl->getInit()));
+ if (!parenExpr)
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+
+ // Sometimes parentheses make the RHS of an assignment easier to read by
+ // visually disambiguating the = from a call to ==
+ auto sub = parenExpr->getSubExpr();
+#if CLANG_VERSION >= 100000
+ if (auto const e = dyn_cast<CXXRewrittenBinaryOperator>(sub)) {
+ sub = e->getDecomposedForm().InnerBinOp;
+ }
+#endif
+ if (auto subBinOp = dyn_cast<BinaryOperator>(sub))
+ {
+ if (!(subBinOp->isMultiplicativeOp() || subBinOp->isAdditiveOp() || subBinOp->isPtrMemOp()))
+ return true;
+ }
+ if (auto subOperatorCall = dyn_cast<CXXOperatorCallExpr>(sub))
+ {
+ auto op = subOperatorCall->getOperator();
+ if (!((op >= OO_Plus && op <= OO_Exclaim) || (op >= OO_ArrowStar && op <= OO_Subscript)))
+ return true;
+ }
+ if (isa<ConditionalOperator>(sub))
+ return true;
+
+ // these two are for "parentheses were disambiguated as a function declaration [-Werror,-Wvexing-parse]"
+ auto const sub2 = sub->IgnoreImplicit();
+ if (isa<CXXTemporaryObjectExpr>(sub2)
+ || isa<CXXFunctionalCastExpr>(sub2))
+ return true;
+
+ report(
+ DiagnosticsEngine::Warning, "parentheses immediately inside vardecl statement",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ return true;
+}
+
+bool UnnecessaryParen::VisitMemberExpr(const MemberExpr* memberExpr)
+{
+ if (ignoreLocation(memberExpr))
+ return true;
+
+ auto parenExpr = dyn_cast<ParenExpr>(ignoreAllImplicit(memberExpr->getBase()));
+ if (!parenExpr)
+ return true;
+ if (handled_.find(parenExpr) != handled_.end())
+ return true;
+ if (compat::getBeginLoc(parenExpr).isMacroID())
+ return true;
+
+ auto sub = parenExpr->getSubExpr();
+ if (isa<CallExpr>(sub)) {
+ if (isa<CXXOperatorCallExpr>(sub))
+ return true;
+ } else if (isa<CXXConstructExpr>(sub)) {
+ // warn
+ } else if (isa<MemberExpr>(sub)) {
+ // warn
+ } else if (isa<DeclRefExpr>(sub)) {
+ // warn
+ } else
+ return true;
+
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around member expr",
+ compat::getBeginLoc(parenExpr))
+ << parenExpr->getSourceRange();
+ handled_.insert(parenExpr);
+ return true;
+}
+
+// Conservatively assume any parenthesised integer or Boolean (incl. Objective-C ones) literal in
+// certain condition expressions (i.e., those for which handleUnreachableCodeConditionParens is
+// called) to be parenthesised to silence Clang -Wunreachable-code, if that is either the whole
+// condition expression or appears as a certain sub-expression (looking at what isConfigurationValue
+// in Clang's lib/Analysis/ReachableCode.cpp looks for, descending into certain unary and binary
+// operators):
+void UnnecessaryParen::handleUnreachableCodeConditionParens(Expr const * expr) {
+ // Cf. :
+ auto const e = ignoreAllImplicit(expr);
+ if (auto const e1 = dyn_cast<ParenExpr>(e)) {
+ auto const sub = e1->getSubExpr();
+ if (isa<IntegerLiteral>(sub) || isa<CXXBoolLiteralExpr>(sub)
+ || isa<ObjCBoolLiteralExpr>(sub))
+ {
+ handled_.insert(e1);
+ }
+ } else if (auto const e1 = dyn_cast<UnaryOperator>(e)) {
+ if (e1->getOpcode() == UO_LNot) {
+ handleUnreachableCodeConditionParens(e1->getSubExpr());
+ }
+ } else if (auto const e1 = dyn_cast<BinaryOperator>(e)) {
+ if (e1->isLogicalOp() || e1->isComparisonOp()) {
+ handleUnreachableCodeConditionParens(e1->getLHS());
+ handleUnreachableCodeConditionParens(e1->getRHS());
+ }
+ }
+}
+
+bool UnnecessaryParen::isPrecededBy_BAD_CAST(Expr const * expr) {
+ if (compat::getBeginLoc(expr).isMacroID()) {
+ return false;
+ }
+ SourceManager& SM = compiler.getSourceManager();
+ const char *p1 = SM.getCharacterData( compat::getBeginLoc(expr).getLocWithOffset(-10) );
+ const char *p2 = SM.getCharacterData( compat::getBeginLoc(expr) );
+ return std::string(p1, p2 - p1).find("BAD_CAST") != std::string::npos;
+}
+
+namespace {
+
+bool badCombinationChar(char c) {
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
+ || c == '+' || c == '-' || c == '\'' || c == '"';
+}
+
+}
+
+bool UnnecessaryParen::badCombination(SourceLocation loc, int prevOffset, int nextOffset) {
+ //TODO: check for start/end of file; take backslash-newline line concatenation into account
+ auto const c1
+ = compiler.getSourceManager().getCharacterData(loc.getLocWithOffset(prevOffset))[0];
+ auto const c2
+ = compiler.getSourceManager().getCharacterData(loc.getLocWithOffset(nextOffset))[0];
+ // An approximation of avoiding whatever combinations that would cause two adjacent tokens to be
+ // lexed differently, using, for now, letters (TODO: non-ASCII ones) and digits and '_'; '+' and
+ // '-' (to avoid ++, etc.); '\'' and '"' (to avoid u'x' or "foo"bar, etc.):
+ return badCombinationChar(c1) && badCombinationChar(c2);
+}
+
+bool UnnecessaryParen::removeParens(ParenExpr const * expr) {
+ if (rewriter == nullptr) {
+ return false;
+ }
+ auto const firstBegin = compat::getBeginLoc(expr);
+ auto secondBegin = compat::getEndLoc(expr);
+ if (firstBegin.isMacroID() || secondBegin.isMacroID()) {
+ return false;
+ }
+ unsigned firstLen = Lexer::MeasureTokenLength(
+ firstBegin, compiler.getSourceManager(), compiler.getLangOpts());
+ for (auto l = firstBegin.getLocWithOffset(std::max<unsigned>(firstLen, 1));;
+ l = l.getLocWithOffset(1))
+ {
+ unsigned n = Lexer::MeasureTokenLength(
+ l, compiler.getSourceManager(), compiler.getLangOpts());
+ if (n != 0) {
+ break;
+ }
+ ++firstLen;
+ }
+ unsigned secondLen = Lexer::MeasureTokenLength(
+ secondBegin, compiler.getSourceManager(), compiler.getLangOpts());
+ for (;;) {
+ auto l = secondBegin.getLocWithOffset(-1);
+ auto const c = compiler.getSourceManager().getCharacterData(l)[0];
+ if (c == '\n') {
+ if (compiler.getSourceManager().getCharacterData(l.getLocWithOffset(-1))[0] == '\\') {
+ break;
+ }
+ } else if (!(c == ' ' || c == '\t' || c == '\v' || c == '\f')) {
+ break;
+ }
+ secondBegin = l;
+ ++secondLen;
+ }
+ if (!replaceText(firstBegin, firstLen, badCombination(firstBegin, -1, firstLen) ? " " : "")) {
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ "TODO: cannot rewrite opening parenthesis, needs investigation",
+ firstBegin);
+ report(
+ DiagnosticsEngine::Note, "when removing these parentheses", expr->getExprLoc())
+ << expr->getSourceRange();
+ }
+ return false;
+ }
+ if (!replaceText(secondBegin, secondLen, badCombination(secondBegin, -1, secondLen) ? " " : ""))
+ {
+ //TODO: roll back first change
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ "TODO: cannot rewrite closing parenthesis, needs investigation",
+ secondBegin);
+ report(
+ DiagnosticsEngine::Note, "when removing these parentheses", expr->getExprLoc())
+ << expr->getSourceRange();
+ }
+ return false;
+ }
+ return true;
+}
+
+loplugin::Plugin::Registration< UnnecessaryParen > unnecessaryparen("unnecessaryparen", true);
+
+}
+
+#endif // LO_CLANG_SHARED_PLUGINS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */